Merge "Clarify UDP encapsulation socket API"
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 3a8a254..9d518e9 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -453,133 +453,177 @@
     public static final int TYPE_NONE        = -1;
 
     /**
-     * The Mobile data connection.  When active, all data traffic
-     * will use this network type's interface by default
-     * (it has a default route)
+     * A Mobile data connection. Devices may support more than one.
+     *
+     * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or
+     *         {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an
+     *         appropriate network. {@see NetworkCapabilities} for supported transports.
      */
+    @Deprecated
     public static final int TYPE_MOBILE      = 0;
+
     /**
-     * The WIFI data connection.  When active, all data traffic
-     * will use this network type's interface by default
-     * (it has a default route).
+     * A WIFI data connection. Devices may support more than one.
+     *
+     * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or
+     *         {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an
+     *         appropriate network. {@see NetworkCapabilities} for supported transports.
      */
+    @Deprecated
     public static final int TYPE_WIFI        = 1;
+
     /**
      * An MMS-specific Mobile data connection.  This network type may use the
      * same network interface as {@link #TYPE_MOBILE} or it may use a different
      * one.  This is used by applications needing to talk to the carrier's
      * Multimedia Messaging Service servers.
      *
-     * @deprecated Applications should instead use
+     * @deprecated Applications should instead use {@link NetworkCapabilities#hasCapability} or
      *         {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that
      *         provides the {@link NetworkCapabilities#NET_CAPABILITY_MMS} capability.
      */
     @Deprecated
     public static final int TYPE_MOBILE_MMS  = 2;
+
     /**
      * A SUPL-specific Mobile data connection.  This network type may use the
      * same network interface as {@link #TYPE_MOBILE} or it may use a different
      * one.  This is used by applications needing to talk to the carrier's
      * Secure User Plane Location servers for help locating the device.
      *
-     * @deprecated Applications should instead use
+     * @deprecated Applications should instead use {@link NetworkCapabilities#hasCapability} or
      *         {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that
      *         provides the {@link NetworkCapabilities#NET_CAPABILITY_SUPL} capability.
      */
     @Deprecated
     public static final int TYPE_MOBILE_SUPL = 3;
+
     /**
      * A DUN-specific Mobile data connection.  This network type may use the
      * same network interface as {@link #TYPE_MOBILE} or it may use a different
      * one.  This is sometimes by the system when setting up an upstream connection
      * for tethering so that the carrier is aware of DUN traffic.
+     *
+     * @deprecated Applications should instead use {@link NetworkCapabilities#hasCapability} or
+     *         {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that
+     *         provides the {@link NetworkCapabilities#NET_CAPABILITY_DUN} capability.
      */
+    @Deprecated
     public static final int TYPE_MOBILE_DUN  = 4;
+
     /**
      * A High Priority Mobile data connection.  This network type uses the
      * same network interface as {@link #TYPE_MOBILE} but the routing setup
      * is different.
      *
-     * @deprecated Applications should instead use
-     *         {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that
-     *         uses the {@link NetworkCapabilities#TRANSPORT_CELLULAR} transport.
+     * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or
+     *         {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an
+     *         appropriate network. {@see NetworkCapabilities} for supported transports.
      */
     @Deprecated
     public static final int TYPE_MOBILE_HIPRI = 5;
+
     /**
-     * The WiMAX data connection.  When active, all data traffic
-     * will use this network type's interface by default
-     * (it has a default route).
+     * A WiMAX data connection.
+     *
+     * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or
+     *         {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an
+     *         appropriate network. {@see NetworkCapabilities} for supported transports.
      */
+    @Deprecated
     public static final int TYPE_WIMAX       = 6;
 
     /**
-     * The Bluetooth data connection.  When active, all data traffic
-     * will use this network type's interface by default
-     * (it has a default route).
+     * A Bluetooth data connection.
+     *
+     * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or
+     *         {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an
+     *         appropriate network. {@see NetworkCapabilities} for supported transports.
      */
+    @Deprecated
     public static final int TYPE_BLUETOOTH   = 7;
 
     /**
      * Dummy data connection.  This should not be used on shipping devices.
+     * @deprecated This is not used any more.
      */
+    @Deprecated
     public static final int TYPE_DUMMY       = 8;
 
     /**
-     * The Ethernet data connection.  When active, all data traffic
-     * will use this network type's interface by default
-     * (it has a default route).
+     * An Ethernet data connection.
+     *
+     * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or
+     *         {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an
+     *         appropriate network. {@see NetworkCapabilities} for supported transports.
      */
+    @Deprecated
     public static final int TYPE_ETHERNET    = 9;
 
     /**
      * Over the air Administration.
+     * @deprecated Use {@link NetworkCapabilities} instead.
      * {@hide}
      */
+    @Deprecated
     public static final int TYPE_MOBILE_FOTA = 10;
 
     /**
      * IP Multimedia Subsystem.
+     * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_IMS} instead.
      * {@hide}
      */
+    @Deprecated
     public static final int TYPE_MOBILE_IMS  = 11;
 
     /**
      * Carrier Branded Services.
+     * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_CBS} instead.
      * {@hide}
      */
+    @Deprecated
     public static final int TYPE_MOBILE_CBS  = 12;
 
     /**
      * A Wi-Fi p2p connection. Only requesting processes will have access to
      * the peers connected.
+     * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_WIFI_P2P} instead.
      * {@hide}
      */
+    @Deprecated
     public static final int TYPE_WIFI_P2P    = 13;
 
     /**
      * The network to use for initially attaching to the network
+     * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_IA} instead.
      * {@hide}
      */
+    @Deprecated
     public static final int TYPE_MOBILE_IA = 14;
 
     /**
      * Emergency PDN connection for emergency services.  This
      * may include IMS and MMS in emergency situations.
+     * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_EIMS} instead.
      * {@hide}
      */
+    @Deprecated
     public static final int TYPE_MOBILE_EMERGENCY = 15;
 
     /**
      * The network that uses proxy to achieve connectivity.
+     * @deprecated Use {@link NetworkCapabilities} instead.
      * {@hide}
      */
+    @Deprecated
     public static final int TYPE_PROXY = 16;
 
     /**
      * A virtual network using one or more native bearers.
      * It may or may not be providing security services.
+     * @deprecated Applications should use {@link NetworkCapabilities#TRANSPORT_VPN} instead.
      */
+    @Deprecated
     public static final int TYPE_VPN = 17;
 
     /** {@hide} */
@@ -686,8 +730,10 @@
      * @param type the type needing naming
      * @return a String for the given type, or a string version of the type ("87")
      * if no name is known.
+     * @deprecated Types are deprecated. Use {@link NetworkCapabilities} instead.
      * {@hide}
      */
+    @Deprecated
     public static String getNetworkTypeName(int type) {
         switch (type) {
           case TYPE_NONE:
@@ -738,8 +784,10 @@
      * This should be replaced in the future by a network property.
      * @param networkType the type to check
      * @return a boolean - {@code true} if uses cellular network, else {@code false}
+     * @deprecated Types are deprecated. Use {@link NetworkCapabilities} instead.
      * {@hide}
      */
+    @Deprecated
     public static boolean isNetworkTypeMobile(int networkType) {
         switch (networkType) {
             case TYPE_MOBILE:
@@ -761,8 +809,10 @@
     /**
      * Checks if the given network type is backed by a Wi-Fi radio.
      *
+     * @deprecated Types are deprecated. Use {@link NetworkCapabilities} instead.
      * @hide
      */
+    @Deprecated
     public static boolean isNetworkTypeWifi(int networkType) {
         switch (networkType) {
             case TYPE_WIFI:
@@ -1529,6 +1579,8 @@
      * IllegalArgumentException if no mapping from the legacy type to
      * NetworkCapabilities is known.
      *
+     * @deprecated Types are deprecated. Use {@link NetworkCallback} or {@link NetworkRequest}
+     *     to find the network instead.
      * @hide
      */
     public static NetworkCapabilities networkCapabilitiesForType(int type) {
@@ -1925,13 +1977,6 @@
      * services.jar, possibly in com.android.server.net. */
 
     /** {@hide} */
-    public static final boolean checkChangePermission(Context context) {
-        int uid = Binder.getCallingUid();
-        return Settings.checkAndNoteChangeNetworkStateOperation(context, uid, Settings
-                .getPackageNameForUid(context, uid), false /* throwException */);
-    }
-
-    /** {@hide} */
     public static final void enforceChangePermission(Context context) {
         int uid = Binder.getCallingUid();
         Settings.checkAndNoteChangeNetworkStateOperation(context, uid, Settings
@@ -2380,6 +2425,7 @@
      *
      * @param networkType The type of network you want to report on
      * @param percentage The quality of the connection 0 is bad, 100 is good
+     * @deprecated Types are deprecated. Use {@link #reportNetworkConnectivity} instead.
      * {@hide}
      */
     public void reportInetCondition(int networkType, int percentage) {
@@ -2511,9 +2557,10 @@
      *
      * @param networkType The network type we'd like to check
      * @return {@code true} if supported, else {@code false}
-     *
+     * @deprecated Types are deprecated. Use {@link NetworkCapabilities} instead.
      * @hide
      */
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public boolean isNetworkSupported(int networkType) {
         try {
diff --git a/core/java/android/net/IpPrefix.java b/core/java/android/net/IpPrefix.java
index 6e2654e..4631c56 100644
--- a/core/java/android/net/IpPrefix.java
+++ b/core/java/android/net/IpPrefix.java
@@ -25,6 +25,7 @@
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.Arrays;
+import java.util.Comparator;
 
 /**
  * This class represents an IP prefix, i.e., a contiguous block of IP addresses aligned on a
@@ -187,6 +188,20 @@
     }
 
     /**
+     * Returns whether the specified prefix is entirely contained in this prefix.
+     *
+     * Note this is mathematical inclusion, so a prefix is always contained within itself.
+     * @param otherPrefix the prefix to test
+     * @hide
+     */
+    public boolean containsPrefix(IpPrefix otherPrefix) {
+        if (otherPrefix.getPrefixLength() < prefixLength) return false;
+        final byte[] otherAddress = otherPrefix.getRawAddress();
+        NetworkUtils.maskRawAddress(otherAddress, prefixLength);
+        return Arrays.equals(otherAddress, address);
+    }
+
+    /**
      * @hide
      */
     public boolean isIPv6() {
@@ -230,6 +245,38 @@
     }
 
     /**
+     * Returns a comparator ordering IpPrefixes by length, shorter to longer.
+     * Contents of the address will break ties.
+     * @hide
+     */
+    public static Comparator<IpPrefix> lengthComparator() {
+        return new Comparator<IpPrefix>() {
+            @Override
+            public int compare(IpPrefix prefix1, IpPrefix prefix2) {
+                if (prefix1.isIPv4()) {
+                    if (prefix2.isIPv6()) return -1;
+                } else {
+                    if (prefix2.isIPv4()) return 1;
+                }
+                final int p1len = prefix1.getPrefixLength();
+                final int p2len = prefix2.getPrefixLength();
+                if (p1len < p2len) return -1;
+                if (p2len < p1len) return 1;
+                final byte[] a1 = prefix1.address;
+                final byte[] a2 = prefix2.address;
+                final int len = a1.length < a2.length ? a1.length : a2.length;
+                for (int i = 0; i < len; ++i) {
+                    if (a1[i] < a2[i]) return -1;
+                    if (a1[i] > a2[i]) return 1;
+                }
+                if (a2.length < len) return 1;
+                if (a1.length < len) return -1;
+                return 0;
+            }
+        };
+    }
+
+    /**
      * Implement the Parcelable interface.
      */
     public static final Creator<IpPrefix> CREATOR =
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index c94ae93..22cffcc 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -68,6 +68,7 @@
             mSignalStrength = nc.mSignalStrength;
             mUids = nc.mUids;
             mEstablishingVpnAppUid = nc.mEstablishingVpnAppUid;
+            mUnwantedNetworkCapabilities = nc.mUnwantedNetworkCapabilities;
         }
     }
 
@@ -77,7 +78,7 @@
      * @hide
      */
     public void clearAll() {
-        mNetworkCapabilities = mTransportTypes = 0;
+        mNetworkCapabilities = mTransportTypes = mUnwantedNetworkCapabilities = 0;
         mLinkUpBandwidthKbps = mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED;
         mNetworkSpecifier = null;
         mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED;
@@ -91,6 +92,11 @@
      */
     private long mNetworkCapabilities;
 
+    /**
+     * If any capabilities specified here they must not exist in the matching Network.
+     */
+    private long mUnwantedNetworkCapabilities;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = { "NET_CAPABILITY_" }, value = {
@@ -116,6 +122,7 @@
             NET_CAPABILITY_FOREGROUND,
             NET_CAPABILITY_NOT_CONGESTED,
             NET_CAPABILITY_NOT_SUSPENDED,
+            NET_CAPABILITY_OEM_PAID,
     })
     public @interface NetCapability { }
 
@@ -263,8 +270,15 @@
      */
     public static final int NET_CAPABILITY_NOT_SUSPENDED = 21;
 
+    /**
+     * Indicates that traffic that goes through this network is paid by oem. For example,
+     * this network can be used by system apps to upload telemetry data.
+     * @hide
+     */
+    public static final int NET_CAPABILITY_OEM_PAID = 22;
+
     private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
-    private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_SUSPENDED;
+    private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_OEM_PAID;
 
     /**
      * Network capabilities that are expected to be mutable, i.e., can change while a particular
@@ -312,7 +326,8 @@
             (1 << NET_CAPABILITY_IA) |
             (1 << NET_CAPABILITY_IMS) |
             (1 << NET_CAPABILITY_RCS) |
-            (1 << NET_CAPABILITY_XCAP);
+            (1 << NET_CAPABILITY_XCAP) |
+            (1 << NET_CAPABILITY_OEM_PAID);
 
     /**
      * Capabilities that suggest that a network is unrestricted.
@@ -329,31 +344,55 @@
      * Adds the given capability to this {@code NetworkCapability} instance.
      * Multiple capabilities may be applied sequentially.  Note that when searching
      * for a network to satisfy a request, all capabilities requested must be satisfied.
+     * <p>
+     * If the given capability was previously added to the list of unwanted capabilities
+     * then the capability will also be removed from the list of unwanted capabilities.
      *
      * @param capability the capability to be added.
      * @return This NetworkCapabilities instance, to facilitate chaining.
      * @hide
      */
     public NetworkCapabilities addCapability(@NetCapability int capability) {
-        if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) {
-            throw new IllegalArgumentException("NetworkCapability out of range");
-        }
+        checkValidCapability(capability);
         mNetworkCapabilities |= 1 << capability;
+        mUnwantedNetworkCapabilities &= ~(1 << capability);  // remove from unwanted capability list
         return this;
     }
 
     /**
+     * Adds the given capability to the list of unwanted capabilities of this
+     * {@code NetworkCapability} instance.  Multiple unwanted capabilities may be applied
+     * sequentially.  Note that when searching for a network to satisfy a request, the network
+     * must not contain any capability from unwanted capability list.
+     * <p>
+     * If the capability was previously added to the list of required capabilities (for
+     * example, it was there by default or added using {@link #addCapability(int)} method), then
+     * it will be removed from the list of required capabilities as well.
+     *
+     * @see #addCapability(int)
+     * @hide
+     */
+    public void addUnwantedCapability(@NetCapability int capability) {
+        checkValidCapability(capability);
+        mUnwantedNetworkCapabilities |= 1 << capability;
+        mNetworkCapabilities &= ~(1 << capability);  // remove from requested capabilities
+    }
+
+    /**
      * Removes (if found) the given capability from this {@code NetworkCapability} instance.
+     * <p>
+     * Note that this method removes capabilities that were added via {@link #addCapability(int)},
+     * {@link #addUnwantedCapability(int)} or {@link #setCapabilities(int[], int[])} .
      *
      * @param capability the capability to be removed.
      * @return This NetworkCapabilities instance, to facilitate chaining.
      * @hide
      */
     public NetworkCapabilities removeCapability(@NetCapability int capability) {
-        if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) {
-            throw new IllegalArgumentException("NetworkCapability out of range");
-        }
-        mNetworkCapabilities &= ~(1 << capability);
+        checkValidCapability(capability);
+        final long mask = ~(1 << capability);
+        mNetworkCapabilities &= mask;
+        mUnwantedNetworkCapabilities &= mask;
         return this;
     }
 
@@ -383,30 +422,58 @@
     }
 
     /**
+     * Gets all the unwanted capabilities set on this {@code NetworkCapability} instance.
+     *
+     * @return an array of unwanted capability values for this instance.
+     * @hide
+     */
+    public @NetCapability int[] getUnwantedCapabilities() {
+        return BitUtils.unpackBits(mUnwantedNetworkCapabilities);
+    }
+
+
+    /**
      * Sets all the capabilities set on this {@code NetworkCapability} instance.
      * This overwrites any existing capabilities.
      *
      * @hide
      */
-    public void setCapabilities(@NetCapability int[] capabilities) {
+    public void setCapabilities(@NetCapability int[] capabilities,
+            @NetCapability int[] unwantedCapabilities) {
         mNetworkCapabilities = BitUtils.packBits(capabilities);
+        mUnwantedNetworkCapabilities = BitUtils.packBits(unwantedCapabilities);
     }
 
     /**
-     * Tests for the presence of a capabilitity on this instance.
+     * @deprecated use {@link #setCapabilities(int[], int[])}
+     * @hide
+     */
+    @Deprecated
+    public void setCapabilities(@NetCapability int[] capabilities) {
+        setCapabilities(capabilities, new int[] {});
+    }
+
+    /**
+     * Tests for the presence of a capability on this instance.
      *
      * @param capability the capabilities to be tested for.
      * @return {@code true} if set on this instance.
      */
     public boolean hasCapability(@NetCapability int capability) {
-        if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) {
-            return false;
-        }
-        return ((mNetworkCapabilities & (1 << capability)) != 0);
+        return isValidCapability(capability)
+                && ((mNetworkCapabilities & (1 << capability)) != 0);
     }
 
+    /** @hide */
+    public boolean hasUnwantedCapability(@NetCapability int capability) {
+        return isValidCapability(capability)
+                && ((mUnwantedNetworkCapabilities & (1 << capability)) != 0);
+    }
+
+    /** Note this method may result in having the same capability in wanted and unwanted lists. */
     private void combineNetCapabilities(NetworkCapabilities nc) {
         this.mNetworkCapabilities |= nc.mNetworkCapabilities;
+        this.mUnwantedNetworkCapabilities |= nc.mUnwantedNetworkCapabilities;
     }
 
     /**
@@ -417,7 +484,9 @@
      * @hide
      */
     public String describeFirstNonRequestableCapability() {
-        final long nonRequestable = (mNetworkCapabilities & NON_REQUESTABLE_CAPABILITIES);
+        final long nonRequestable = (mNetworkCapabilities | mUnwantedNetworkCapabilities)
+                & NON_REQUESTABLE_CAPABILITIES;
+
         if (nonRequestable != 0) {
             return capabilityNameOf(BitUtils.unpackBits(nonRequestable)[0]);
         }
@@ -427,21 +496,29 @@
     }
 
     private boolean satisfiedByNetCapabilities(NetworkCapabilities nc, boolean onlyImmutable) {
-        long networkCapabilities = this.mNetworkCapabilities;
+        long requestedCapabilities = mNetworkCapabilities;
+        long requestedUnwantedCapabilities = mUnwantedNetworkCapabilities;
+        long providedCapabilities = nc.mNetworkCapabilities;
+
         if (onlyImmutable) {
-            networkCapabilities = networkCapabilities & ~MUTABLE_CAPABILITIES;
+            requestedCapabilities &= ~MUTABLE_CAPABILITIES;
+            requestedUnwantedCapabilities &= ~MUTABLE_CAPABILITIES;
         }
-        return ((nc.mNetworkCapabilities & networkCapabilities) == networkCapabilities);
+        return ((providedCapabilities & requestedCapabilities) == requestedCapabilities)
+                && ((requestedUnwantedCapabilities & providedCapabilities) == 0);
     }
 
     /** @hide */
     public boolean equalsNetCapabilities(NetworkCapabilities nc) {
-        return (nc.mNetworkCapabilities == this.mNetworkCapabilities);
+        return (nc.mNetworkCapabilities == this.mNetworkCapabilities)
+                && (nc.mUnwantedNetworkCapabilities == this.mUnwantedNetworkCapabilities);
     }
 
     private boolean equalsNetCapabilitiesRequestable(NetworkCapabilities that) {
         return ((this.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES) ==
-                (that.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES));
+                (that.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES))
+                && ((this.mUnwantedNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES) ==
+                (that.mUnwantedNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES));
     }
 
     /**
@@ -881,7 +958,16 @@
     /**
      * List of UIDs this network applies to. No restriction if null.
      * <p>
-     * This is typically (and at this time, only) used by VPN. This network is only available to
+     * For networks, mUids represent the list of network this applies to, and null means this
+     * network applies to all UIDs.
+     * For requests, mUids is the list of UIDs this network MUST apply to to match ; ALL UIDs
+     * must be included in a network so that they match. As an exception to the general rule,
+     * a null mUids field for requests mean "no requirements" rather than what the general rule
+     * would suggest ("must apply to all UIDs") : this is because this has shown to be what users
+     * of this API expect in practice. A network that must match all UIDs can still be
+     * expressed with a set ranging the entire set of possible UIDs.
+     * <p>
+     * mUids is typically (and at this time, only) used by VPN. This network is only available to
      * the UIDs in this list, and it is their default network. Apps in this list that wish to
      * bypass the VPN can do so iff the VPN app allows them to or if they are privileged. If this
      * member is null, then the network is not restricted by app UID. If it's an empty list, then
@@ -1003,8 +1089,7 @@
      * @hide
      */
     public boolean satisfiedByUids(NetworkCapabilities nc) {
-        if (null == nc.mUids) return true; // The network satisfies everything.
-        if (null == mUids) return false; // Not everything allowed but requires everything
+        if (null == nc.mUids || null == mUids) return true; // The network satisfies everything.
         for (UidRange requiredRange : mUids) {
             if (requiredRange.contains(nc.mEstablishingVpnAppUid)) return true;
             if (!nc.appliesToUidRange(requiredRange)) {
@@ -1047,7 +1132,11 @@
     }
 
     /**
-     * Combine a set of Capabilities to this one.  Useful for coming up with the complete set
+     * Combine a set of Capabilities to this one.  Useful for coming up with the complete set.
+     * <p>
+     * Note that this method may break an invariant of having a particular capability in either
+     * wanted or unwanted lists but never in both.  Requests that have the same capability in
+     * both lists will never be satisfied.
      * @hide
      */
     public void combineCapabilities(NetworkCapabilities nc) {
@@ -1169,15 +1258,17 @@
 
     @Override
     public int hashCode() {
-        return ((int) (mNetworkCapabilities & 0xFFFFFFFF)
+        return (int) (mNetworkCapabilities & 0xFFFFFFFF)
                 + ((int) (mNetworkCapabilities >> 32) * 3)
-                + ((int) (mTransportTypes & 0xFFFFFFFF) * 5)
-                + ((int) (mTransportTypes >> 32) * 7)
-                + (mLinkUpBandwidthKbps * 11)
-                + (mLinkDownBandwidthKbps * 13)
-                + Objects.hashCode(mNetworkSpecifier) * 17
-                + (mSignalStrength * 19)
-                + Objects.hashCode(mUids) * 23);
+                + ((int) (mUnwantedNetworkCapabilities & 0xFFFFFFFF) * 5)
+                + ((int) (mUnwantedNetworkCapabilities >> 32) * 7)
+                + ((int) (mTransportTypes & 0xFFFFFFFF) * 11)
+                + ((int) (mTransportTypes >> 32) * 13)
+                + (mLinkUpBandwidthKbps * 17)
+                + (mLinkDownBandwidthKbps * 19)
+                + Objects.hashCode(mNetworkSpecifier) * 23
+                + (mSignalStrength * 29)
+                + Objects.hashCode(mUids) * 31;
     }
 
     @Override
@@ -1187,6 +1278,7 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeLong(mNetworkCapabilities);
+        dest.writeLong(mUnwantedNetworkCapabilities);
         dest.writeLong(mTransportTypes);
         dest.writeInt(mLinkUpBandwidthKbps);
         dest.writeInt(mLinkDownBandwidthKbps);
@@ -1202,6 +1294,7 @@
                 NetworkCapabilities netCap = new NetworkCapabilities();
 
                 netCap.mNetworkCapabilities = in.readLong();
+                netCap.mUnwantedNetworkCapabilities = in.readLong();
                 netCap.mTransportTypes = in.readLong();
                 netCap.mLinkUpBandwidthKbps = in.readInt();
                 netCap.mLinkDownBandwidthKbps = in.readInt();
@@ -1219,34 +1312,73 @@
 
     @Override
     public String toString() {
-        // TODO: enumerate bits for transports and capabilities instead of creating arrays.
-        // TODO: use a StringBuilder instead of string concatenation.
-        int[] types = getTransportTypes();
-        String transports = (types.length > 0) ? " Transports: " + transportNamesOf(types) : "";
-
-        types = getCapabilities();
-        String capabilities = (types.length > 0 ? " Capabilities: " : "");
-        for (int i = 0; i < types.length; ) {
-            capabilities += capabilityNameOf(types[i]);
-            if (++i < types.length) capabilities += "&";
+        final StringBuilder sb = new StringBuilder("[");
+        if (0 != mTransportTypes) {
+            sb.append(" Transports: ");
+            appendStringRepresentationOfBitMaskToStringBuilder(sb, mTransportTypes,
+                    NetworkCapabilities::transportNameOf, "|");
+        }
+        if (0 != mNetworkCapabilities) {
+            sb.append(" Capabilities: ");
+            appendStringRepresentationOfBitMaskToStringBuilder(sb, mNetworkCapabilities,
+                    NetworkCapabilities::capabilityNameOf, "&");
+        }
+        if (0 != mNetworkCapabilities) {
+            sb.append(" Unwanted: ");
+            appendStringRepresentationOfBitMaskToStringBuilder(sb, mUnwantedNetworkCapabilities,
+                    NetworkCapabilities::capabilityNameOf, "&");
+        }
+        if (mLinkUpBandwidthKbps > 0) {
+            sb.append(" LinkUpBandwidth>=").append(mLinkUpBandwidthKbps).append("Kbps");
+        }
+        if (mLinkDownBandwidthKbps > 0) {
+            sb.append(" LinkDnBandwidth>=").append(mLinkDownBandwidthKbps).append("Kbps");
+        }
+        if (mNetworkSpecifier != null) {
+            sb.append(" Specifier: <").append(mNetworkSpecifier).append(">");
+        }
+        if (hasSignalStrength()) {
+            sb.append(" SignalStrength: ").append(mSignalStrength);
         }
 
-        String upBand = ((mLinkUpBandwidthKbps > 0) ? " LinkUpBandwidth>=" +
-                mLinkUpBandwidthKbps + "Kbps" : "");
-        String dnBand = ((mLinkDownBandwidthKbps > 0) ? " LinkDnBandwidth>=" +
-                mLinkDownBandwidthKbps + "Kbps" : "");
+        if (null != mUids) {
+            if ((1 == mUids.size()) && (mUids.valueAt(0).count() == 1)) {
+                sb.append(" Uid: ").append(mUids.valueAt(0).start);
+            } else {
+                sb.append(" Uids: <").append(mUids).append(">");
+            }
+        }
+        if (mEstablishingVpnAppUid != INVALID_UID) {
+            sb.append(" EstablishingAppUid: ").append(mEstablishingVpnAppUid);
+        }
 
-        String specifier = (mNetworkSpecifier == null ?
-                "" : " Specifier: <" + mNetworkSpecifier + ">");
+        sb.append("]");
+        return sb.toString();
+    }
 
-        String signalStrength = (hasSignalStrength() ? " SignalStrength: " + mSignalStrength : "");
 
-        String uids = (null != mUids ? " Uids: <" + mUids + ">" : "");
-
-        String establishingAppUid = " EstablishingAppUid: " + mEstablishingVpnAppUid;
-
-        return "[" + transports + capabilities + upBand + dnBand + specifier + signalStrength
-            + uids + establishingAppUid + "]";
+    private interface NameOf {
+        String nameOf(int value);
+    }
+    /**
+     * @hide
+     */
+    public static void appendStringRepresentationOfBitMaskToStringBuilder(StringBuilder sb,
+            long bitMask, NameOf nameFetcher, String separator) {
+        int bitPos = 0;
+        boolean firstElementAdded = false;
+        while (bitMask != 0) {
+            if ((bitMask & 1) != 0) {
+                if (firstElementAdded) {
+                    sb.append(separator);
+                } else {
+                    firstElementAdded = true;
+                }
+                sb.append(nameFetcher.nameOf(bitPos));
+            }
+            bitMask >>= 1;
+            ++bitPos;
+        }
     }
 
     /**
@@ -1289,6 +1421,7 @@
             case NET_CAPABILITY_FOREGROUND:     return "FOREGROUND";
             case NET_CAPABILITY_NOT_CONGESTED:  return "NOT_CONGESTED";
             case NET_CAPABILITY_NOT_SUSPENDED:  return "NOT_SUSPENDED";
+            case NET_CAPABILITY_OEM_PAID:       return "OEM_PAID";
             default:                            return Integer.toString(capability);
         }
     }
@@ -1320,4 +1453,13 @@
         Preconditions.checkArgument(
                 isValidTransport(transport), "Invalid TransportType " + transport);
     }
+
+    private static boolean isValidCapability(@NetworkCapabilities.NetCapability int capability) {
+        return capability >= MIN_NET_CAPABILITY && capability <= MAX_NET_CAPABILITY;
+    }
+
+    private static void checkValidCapability(@NetworkCapabilities.NetCapability int capability) {
+        Preconditions.checkArgument(isValidCapability(capability),
+                "NetworkCapability " + capability + "out of range");
+    }
 }
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index e6ad89a..999771a 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -38,14 +38,18 @@
      * <table>
      * <tr><td><b>Detailed state</b></td><td><b>Coarse-grained state</b></td></tr>
      * <tr><td><code>IDLE</code></td><td><code>DISCONNECTED</code></td></tr>
-     * <tr><td><code>SCANNING</code></td><td><code>CONNECTING</code></td></tr>
+     * <tr><td><code>SCANNING</code></td><td><code>DISCONNECTED</code></td></tr>
      * <tr><td><code>CONNECTING</code></td><td><code>CONNECTING</code></td></tr>
      * <tr><td><code>AUTHENTICATING</code></td><td><code>CONNECTING</code></td></tr>
+     * <tr><td><code>OBTAINING_IPADDR</code></td><td><code>CONNECTING</code></td></tr>
+     * <tr><td><code>VERIFYING_POOR_LINK</code></td><td><code>CONNECTING</code></td></tr>
+     * <tr><td><code>CAPTIVE_PORTAL_CHECK</code></td><td><code>CONNECTING</code></td></tr>
      * <tr><td><code>CONNECTED</code></td><td><code>CONNECTED</code></td></tr>
+     * <tr><td><code>SUSPENDED</code></td><td><code>SUSPENDED</code></td></tr>
      * <tr><td><code>DISCONNECTING</code></td><td><code>DISCONNECTING</code></td></tr>
      * <tr><td><code>DISCONNECTED</code></td><td><code>DISCONNECTED</code></td></tr>
-     * <tr><td><code>UNAVAILABLE</code></td><td><code>DISCONNECTED</code></td></tr>
      * <tr><td><code>FAILED</code></td><td><code>DISCONNECTED</code></td></tr>
+     * <tr><td><code>BLOCKED</code></td><td><code>DISCONNECTED</code></td></tr>
      * </table>
      */
     public enum State {
@@ -163,8 +167,17 @@
      * @return one of {@link ConnectivityManager#TYPE_MOBILE}, {@link
      * ConnectivityManager#TYPE_WIFI}, {@link ConnectivityManager#TYPE_WIMAX}, {@link
      * ConnectivityManager#TYPE_ETHERNET},  {@link ConnectivityManager#TYPE_BLUETOOTH}, or other
-     * types defined by {@link ConnectivityManager}
+     * types defined by {@link ConnectivityManager}.
+     * @deprecated Callers should switch to checking {@link NetworkCapabilities#hasTransport}
+     *             instead with one of the NetworkCapabilities#TRANSPORT_* constants :
+     *             {@link #getType} and {@link #getTypeName} cannot account for networks using
+     *             multiple transports. Note that generally apps should not care about transport;
+     *             {@link NetworkCapabilities#NET_CAPABILITY_NOT_METERED} and
+     *             {@link NetworkCapabilities#getLinkDownstreamBandwidthKbps} are calls that
+     *             apps concerned with meteredness or bandwidth should be looking at, as they
+     *             offer this information with much better accuracy.
      */
+    @Deprecated
     public int getType() {
         synchronized (this) {
             return mNetworkType;
@@ -172,8 +185,10 @@
     }
 
     /**
+     * @deprecated Use {@link NetworkCapabilities} instead
      * @hide
      */
+    @Deprecated
     public void setType(int type) {
         synchronized (this) {
             mNetworkType = type;
@@ -205,7 +220,16 @@
      * Return a human-readable name describe the type of the network,
      * for example "WIFI" or "MOBILE".
      * @return the name of the network type
+     * @deprecated Callers should switch to checking {@link NetworkCapabilities#hasTransport}
+     *             instead with one of the NetworkCapabilities#TRANSPORT_* constants :
+     *             {@link #getType} and {@link #getTypeName} cannot account for networks using
+     *             multiple transports. Note that generally apps should not care about transport;
+     *             {@link NetworkCapabilities#NET_CAPABILITY_NOT_METERED} and
+     *             {@link NetworkCapabilities#getLinkDownstreamBandwidthKbps} are calls that
+     *             apps concerned with meteredness or bandwidth should be looking at, as they
+     *             offer this information with much better accuracy.
      */
+    @Deprecated
     public String getTypeName() {
         synchronized (this) {
             return mTypeName;
@@ -230,7 +254,15 @@
      * that the network is fully usable.
      * @return {@code true} if network connectivity exists or is in the process
      * of being established, {@code false} otherwise.
+     * @deprecated Apps should instead use the
+     *             {@link android.net.ConnectivityManager.NetworkCallback} API to
+     *             learn about connectivity changes.
+     *             {@link ConnectivityManager#registerDefaultNetworkCallback} and
+     *             {@link ConnectivityManager#registerNetworkCallback}. These will
+     *             give a more accurate picture of the connectivity state of
+     *             the device and let apps react more easily and quickly to changes.
      */
+    @Deprecated
     public boolean isConnectedOrConnecting() {
         synchronized (this) {
             return mState == State.CONNECTED || mState == State.CONNECTING;
@@ -259,8 +291,18 @@
      * data roaming has been disabled.</li>
      * <li>The device's radio is turned off, e.g., because airplane mode is enabled.</li>
      * </ul>
+     * Since Android L, this always returns {@code true}, because the system only
+     * returns info for available networks.
      * @return {@code true} if the network is available, {@code false} otherwise
+     * @deprecated Apps should instead use the
+     *             {@link android.net.ConnectivityManager.NetworkCallback} API to
+     *             learn about connectivity changes.
+     *             {@link ConnectivityManager#registerDefaultNetworkCallback} and
+     *             {@link ConnectivityManager#registerNetworkCallback}. These will
+     *             give a more accurate picture of the connectivity state of
+     *             the device and let apps react more easily and quickly to changes.
      */
+    @Deprecated
     public boolean isAvailable() {
         synchronized (this) {
             return mIsAvailable;
@@ -270,9 +312,11 @@
     /**
      * Sets if the network is available, ie, if the connectivity is possible.
      * @param isAvailable the new availability value.
+     * @deprecated Use {@link NetworkCapabilities} instead
      *
      * @hide
      */
+    @Deprecated
     public void setIsAvailable(boolean isAvailable) {
         synchronized (this) {
             mIsAvailable = isAvailable;
@@ -285,7 +329,10 @@
      * network following a disconnect from another network.
      * @return {@code true} if this is a failover attempt, {@code false}
      * otherwise.
+     * @deprecated This field is not populated in recent Android releases,
+     *             and does not make a lot of sense in a multi-network world.
      */
+    @Deprecated
     public boolean isFailover() {
         synchronized (this) {
             return mIsFailover;
@@ -296,8 +343,10 @@
      * Set the failover boolean.
      * @param isFailover {@code true} to mark the current connection attempt
      * as a failover.
+     * @deprecated This hasn't been set in any recent Android release.
      * @hide
      */
+    @Deprecated
     public void setFailover(boolean isFailover) {
         synchronized (this) {
             mIsFailover = isFailover;
@@ -322,7 +371,10 @@
         }
     }
 
-    /** {@hide} */
+    /**
+     * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_NOT_ROAMING} instead.
+     * {@hide}
+     */
     @VisibleForTesting
     @Deprecated
     public void setRoaming(boolean isRoaming) {
@@ -334,7 +386,15 @@
     /**
      * Reports the current coarse-grained state of the network.
      * @return the coarse-grained state
+     * @deprecated Apps should instead use the
+     *             {@link android.net.ConnectivityManager.NetworkCallback} API to
+     *             learn about connectivity changes.
+     *             {@link ConnectivityManager#registerDefaultNetworkCallback} and
+     *             {@link ConnectivityManager#registerNetworkCallback}. These will
+     *             give a more accurate picture of the connectivity state of
+     *             the device and let apps react more easily and quickly to changes.
      */
+    @Deprecated
     public State getState() {
         synchronized (this) {
             return mState;
@@ -358,8 +418,10 @@
      * if one was supplied. May be {@code null}.
      * @param extraInfo an optional {@code String} providing addditional network state
      * information passed up from the lower networking layers.
+     * @deprecated Use {@link NetworkCapabilities} instead.
      * @hide
      */
+    @Deprecated
     public void setDetailedState(DetailedState detailedState, String reason, String extraInfo) {
         synchronized (this) {
             this.mDetailedState = detailedState;
@@ -385,6 +447,8 @@
      * Report the reason an attempt to establish connectivity failed,
      * if one is available.
      * @return the reason for failure, or null if not available
+     * @deprecated This method does not have a consistent contract that could make it useful
+     *             to callers.
      */
     public String getReason() {
         synchronized (this) {
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 97ded2d..1ee0ed7 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -19,9 +19,11 @@
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.Process;
 import android.text.TextUtils;
 
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Defines a request for a network, made through {@link NetworkRequest.Builder} and used
@@ -131,12 +133,18 @@
      * needed in terms of {@link NetworkCapabilities} features
      */
     public static class Builder {
-        private final NetworkCapabilities mNetworkCapabilities = new NetworkCapabilities();
+        private final NetworkCapabilities mNetworkCapabilities;
 
         /**
          * Default constructor for Builder.
          */
-        public Builder() {}
+        public Builder() {
+            // By default, restrict this request to networks available to this app.
+            // Apps can rescind this restriction, but ConnectivityService will enforce
+            // it for apps that do not have the NETWORK_SETTINGS permission.
+            mNetworkCapabilities = new NetworkCapabilities();
+            mNetworkCapabilities.setSingleUid(Process.myUid());
+        }
 
         /**
          * Build {@link NetworkRequest} give the current set of capabilities.
@@ -157,6 +165,9 @@
          * the requested network's required capabilities.  Note that when searching
          * for a network to satisfy a request, all capabilities requested must be
          * satisfied.
+         * <p>
+         * If the given capability was previously added to the list of unwanted capabilities
+         * then the capability will also be removed from the list of unwanted capabilities.
          *
          * @param capability The capability to add.
          * @return The builder to facilitate chaining
@@ -168,7 +179,8 @@
         }
 
         /**
-         * Removes (if found) the given capability from this builder instance.
+         * Removes (if found) the given capability from this builder instance from both required
+         * and unwanted capabilities lists.
          *
          * @param capability The capability to remove.
          * @return The builder to facilitate chaining.
@@ -193,6 +205,37 @@
         }
 
         /**
+         * Set the watched UIDs for this request. This will be reset and wiped out unless
+         * the calling app holds the CHANGE_NETWORK_STATE permission.
+         *
+         * @param uids The watched UIDs as a set of UidRanges, or null for everything.
+         * @return The builder to facilitate chaining.
+         * @hide
+         */
+        public Builder setUids(Set<UidRange> uids) {
+            mNetworkCapabilities.setUids(uids);
+            return this;
+        }
+
+        /**
+         * Add a capability that must not exist in the requested network.
+         * <p>
+         * If the capability was previously added to the list of required capabilities (for
+         * example, it was there by default or added using {@link #addCapability(int)} method), then
+         * it will be removed from the list of required capabilities as well.
+         *
+         * @see #addCapability(int)
+         *
+         * @param capability The capability to add to unwanted capability list.
+         * @return The builder to facilitate chaining.
+         * @hide
+         */
+        public Builder addUnwantedCapability(@NetworkCapabilities.NetCapability int capability) {
+            mNetworkCapabilities.addUnwantedCapability(capability);
+            return this;
+        }
+
+        /**
          * Completely clears all the {@code NetworkCapabilities} from this builder instance,
          * removing even the capabilities that are set by default when the object is constructed.
          *
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index fe9563d..9a5d502 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -16,19 +16,20 @@
 
 package android.net;
 
-import java.io.FileDescriptor;
-import java.net.InetAddress;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.SocketException;
-import java.net.UnknownHostException;
-import java.util.Collection;
-import java.util.Locale;
-
 import android.os.Parcel;
 import android.util.Log;
 import android.util.Pair;
 
+import java.io.FileDescriptor;
+import java.math.BigInteger;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.Collection;
+import java.util.Locale;
+import java.util.TreeSet;
 
 /**
  * Native methods for managing network interfaces.
@@ -385,4 +386,72 @@
         result = builder.toString();
         return result;
     }
+
+    /**
+     * Returns a prefix set without overlaps.
+     *
+     * This expects the src set to be sorted from shorter to longer. Results are undefined
+     * failing this condition. The returned prefix set is sorted in the same order as the
+     * passed set, with the same comparator.
+     */
+    private static TreeSet<IpPrefix> deduplicatePrefixSet(final TreeSet<IpPrefix> src) {
+        final TreeSet<IpPrefix> dst = new TreeSet<>(src.comparator());
+        // Prefixes match addresses that share their upper part up to their length, therefore
+        // the only kind of possible overlap in two prefixes is strict inclusion of the longer
+        // (more restrictive) in the shorter (including equivalence if they have the same
+        // length).
+        // Because prefixes in the src set are sorted from shorter to longer, deduplicating
+        // is done by simply iterating in order, and not adding any longer prefix that is
+        // already covered by a shorter one.
+        newPrefixes:
+        for (IpPrefix newPrefix : src) {
+            for (IpPrefix existingPrefix : dst) {
+                if (existingPrefix.containsPrefix(newPrefix)) {
+                    continue newPrefixes;
+                }
+            }
+            dst.add(newPrefix);
+        }
+        return dst;
+    }
+
+    /**
+     * Returns how many IPv4 addresses match any of the prefixes in the passed ordered set.
+     *
+     * Obviously this returns an integral value between 0 and 2**32.
+     * The behavior is undefined if any of the prefixes is not an IPv4 prefix or if the
+     * set is not ordered smallest prefix to longer prefix.
+     *
+     * @param prefixes the set of prefixes, ordered by length
+     */
+    public static long routedIPv4AddressCount(final TreeSet<IpPrefix> prefixes) {
+        long routedIPCount = 0;
+        for (final IpPrefix prefix : deduplicatePrefixSet(prefixes)) {
+            if (!prefix.isIPv4()) {
+                Log.wtf(TAG, "Non-IPv4 prefix in routedIPv4AddressCount");
+            }
+            int rank = 32 - prefix.getPrefixLength();
+            routedIPCount += 1L << rank;
+        }
+        return routedIPCount;
+    }
+
+    /**
+     * Returns how many IPv6 addresses match any of the prefixes in the passed ordered set.
+     *
+     * This returns a BigInteger between 0 and 2**128.
+     * The behavior is undefined if any of the prefixes is not an IPv6 prefix or if the
+     * set is not ordered smallest prefix to longer prefix.
+     */
+    public static BigInteger routedIPv6AddressCount(final TreeSet<IpPrefix> prefixes) {
+        BigInteger routedIPCount = BigInteger.ZERO;
+        for (final IpPrefix prefix : deduplicatePrefixSet(prefixes)) {
+            if (!prefix.isIPv6()) {
+                Log.wtf(TAG, "Non-IPv6 prefix in routedIPv6AddressCount");
+            }
+            int rank = 128 - prefix.getPrefixLength();
+            routedIPCount = routedIPCount.add(BigInteger.ONE.shiftLeft(rank));
+        }
+        return routedIPCount;
+    }
 }
diff --git a/core/java/android/net/UidRange.java b/core/java/android/net/UidRange.java
index fd465d9..3164929 100644
--- a/core/java/android/net/UidRange.java
+++ b/core/java/android/net/UidRange.java
@@ -21,8 +21,6 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import java.lang.IllegalArgumentException;
-
 /**
  * An inclusive range of UIDs.
  *
@@ -53,6 +51,13 @@
     }
 
     /**
+     * Returns the count of UIDs in this range.
+     */
+    public int count() {
+        return 1 + stop - start;
+    }
+
+    /**
      * @return {@code true} if this range contains every UID contained by the {@param other} range.
      */
     public boolean containsRange(UidRange other) {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index fd2ef18..ae7ac8f 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -17,6 +17,7 @@
 package com.android.server;
 
 import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.NETID_UNSET;
 import static android.net.ConnectivityManager.TYPE_ETHERNET;
@@ -1329,9 +1330,8 @@
         if (nai != null) {
             synchronized (nai) {
                 if (nai.networkCapabilities != null) {
-                    // TODO : don't remove the UIDs when communicating with processes
-                    // that have the NETWORK_SETTINGS permission.
-                    return networkCapabilitiesWithoutUids(nai.networkCapabilities);
+                    return networkCapabilitiesWithoutUidsUnlessAllowed(nai.networkCapabilities,
+                            Binder.getCallingPid(), Binder.getCallingUid());
                 }
             }
         }
@@ -1344,10 +1344,24 @@
         return getNetworkCapabilitiesInternal(getNetworkAgentInfoForNetwork(network));
     }
 
-    private NetworkCapabilities networkCapabilitiesWithoutUids(NetworkCapabilities nc) {
+    private NetworkCapabilities networkCapabilitiesWithoutUidsUnlessAllowed(
+            NetworkCapabilities nc, int callerPid, int callerUid) {
+        if (checkSettingsPermission(callerPid, callerUid)) return new NetworkCapabilities(nc);
         return new NetworkCapabilities(nc).setUids(null);
     }
 
+    private void restrictRequestUidsForCaller(NetworkCapabilities nc) {
+        if (!checkSettingsPermission()) {
+            nc.setSingleUid(Binder.getCallingUid());
+        }
+    }
+
+    private void restrictBackgroundRequestForCaller(NetworkCapabilities nc) {
+        if (!mPermissionMonitor.hasUseBackgroundNetworksPermission(Binder.getCallingUid())) {
+            nc.addCapability(NET_CAPABILITY_FOREGROUND);
+        }
+    }
+
     @Override
     public NetworkState[] getAllNetworkState() {
         // Require internal since we're handing out IMSI details
@@ -1546,6 +1560,16 @@
                 "ConnectivityService");
     }
 
+    private boolean checkSettingsPermission() {
+        return PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.NETWORK_SETTINGS);
+    }
+
+    private boolean checkSettingsPermission(int pid, int uid) {
+        return PERMISSION_GRANTED == mContext.checkPermission(
+                android.Manifest.permission.NETWORK_SETTINGS, pid, uid);
+    }
+
     private void enforceTetherAccessPermission() {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.ACCESS_NETWORK_STATE,
@@ -4213,13 +4237,12 @@
             enforceMeteredApnPolicy(networkCapabilities);
         }
         ensureRequestableCapabilities(networkCapabilities);
-        // Set the UID range for this request to the single UID of the requester.
+        // Set the UID range for this request to the single UID of the requester, or to an empty
+        // set of UIDs if the caller has the appropriate permission and UIDs have not been set.
         // This will overwrite any allowed UIDs in the requested capabilities. Though there
         // are no visible methods to set the UIDs, an app could use reflection to try and get
         // networks for other apps so it's essential that the UIDs are overwritten.
-        // TODO : don't forcefully set the UID when communicating with processes
-        // that have the NETWORK_SETTINGS permission.
-        networkCapabilities.setSingleUid(Binder.getCallingUid());
+        restrictRequestUidsForCaller(networkCapabilities);
 
         if (timeoutMs < 0) {
             throw new IllegalArgumentException("Bad timeout specified");
@@ -4293,9 +4316,7 @@
         enforceMeteredApnPolicy(networkCapabilities);
         ensureRequestableCapabilities(networkCapabilities);
         ensureValidNetworkSpecifier(networkCapabilities);
-        // TODO : don't forcefully set the UID when communicating with processes
-        // that have the NETWORK_SETTINGS permission.
-        networkCapabilities.setSingleUid(Binder.getCallingUid());
+        restrictRequestUidsForCaller(networkCapabilities);
 
         NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
                 nextNetworkRequestId(), NetworkRequest.Type.REQUEST);
@@ -4349,18 +4370,14 @@
         }
 
         NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
-        // TODO : don't forcefully set the UIDs when communicating with processes
-        // that have the NETWORK_SETTINGS permission.
-        nc.setSingleUid(Binder.getCallingUid());
-        if (!ConnectivityManager.checkChangePermission(mContext)) {
-            // Apps without the CHANGE_NETWORK_STATE permission can't use background networks, so
-            // make all their listens include NET_CAPABILITY_FOREGROUND. That way, they will get
-            // onLost and onAvailable callbacks when networks move in and out of the background.
-            // There is no need to do this for requests because an app without CHANGE_NETWORK_STATE
-            // can't request networks.
-            nc.addCapability(NET_CAPABILITY_FOREGROUND);
-        }
-        ensureValidNetworkSpecifier(networkCapabilities);
+        restrictRequestUidsForCaller(nc);
+        // Apps without the CHANGE_NETWORK_STATE permission can't use background networks, so
+        // make all their listens include NET_CAPABILITY_FOREGROUND. That way, they will get
+        // onLost and onAvailable callbacks when networks move in and out of the background.
+        // There is no need to do this for requests because an app without CHANGE_NETWORK_STATE
+        // can't request networks.
+        restrictBackgroundRequestForCaller(nc);
+        ensureValidNetworkSpecifier(nc);
 
         NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
                 NetworkRequest.Type.LISTEN);
@@ -4381,9 +4398,7 @@
         ensureValidNetworkSpecifier(networkCapabilities);
 
         final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
-        // TODO : don't forcefully set the UIDs when communicating with processes
-        // that have the NETWORK_SETTINGS permission.
-        nc.setSingleUid(Binder.getCallingUid());
+        restrictRequestUidsForCaller(nc);
 
         NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
                 NetworkRequest.Type.LISTEN);
@@ -4520,17 +4535,17 @@
         return nai.network.netId;
     }
 
-    private void handleRegisterNetworkAgent(NetworkAgentInfo na) {
+    private void handleRegisterNetworkAgent(NetworkAgentInfo nai) {
         if (VDBG) log("Got NetworkAgent Messenger");
-        mNetworkAgentInfos.put(na.messenger, na);
+        mNetworkAgentInfos.put(nai.messenger, nai);
         synchronized (mNetworkForNetId) {
-            mNetworkForNetId.put(na.network.netId, na);
+            mNetworkForNetId.put(nai.network.netId, nai);
         }
-        na.asyncChannel.connect(mContext, mTrackerHandler, na.messenger);
-        NetworkInfo networkInfo = na.networkInfo;
-        na.networkInfo = null;
-        updateNetworkInfo(na, networkInfo);
-        updateUids(na, null, na.networkCapabilities);
+        nai.asyncChannel.connect(mContext, mTrackerHandler, nai.messenger);
+        NetworkInfo networkInfo = nai.networkInfo;
+        nai.networkInfo = null;
+        updateNetworkInfo(nai, networkInfo);
+        updateUids(nai, null, nai.networkCapabilities);
     }
 
     private void updateLinkProperties(NetworkAgentInfo networkAgent, LinkProperties oldLp) {
@@ -4947,8 +4962,8 @@
             }
             case ConnectivityManager.CALLBACK_CAP_CHANGED: {
                 // networkAgent can't be null as it has been accessed a few lines above.
-                final NetworkCapabilities nc =
-                        networkCapabilitiesWithoutUids(networkAgent.networkCapabilities);
+                final NetworkCapabilities nc = networkCapabilitiesWithoutUidsUnlessAllowed(
+                        networkAgent.networkCapabilities, nri.mPid, nri.mUid);
                 putParcelable(bundle, nc);
                 break;
             }
@@ -5257,7 +5272,6 @@
                 for (LinkProperties stacked : newNetwork.linkProperties.getStackedLinks()) {
                     final String stackedIface = stacked.getInterfaceName();
                     bs.noteNetworkInterfaceType(stackedIface, type);
-                    NetworkStatsFactory.noteStackedIface(stackedIface, baseIface);
                 }
             } catch (RemoteException ignored) {
             }
@@ -5428,8 +5442,11 @@
         if (!networkAgent.everConnected && state == NetworkInfo.State.CONNECTED) {
             networkAgent.everConnected = true;
 
+            if (networkAgent.linkProperties == null) {
+                Slog.wtf(TAG, networkAgent.name() + " connected with null LinkProperties");
+            }
+
             updateLinkProperties(networkAgent, null);
-            notifyIfacesChangedForNetworkStats();
 
             networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
             scheduleUnvalidatedPrompt(networkAgent);
diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java
index a1c54bd..36f5a6c 100644
--- a/services/core/java/com/android/server/connectivity/DnsManager.java
+++ b/services/core/java/com/android/server/connectivity/DnsManager.java
@@ -187,11 +187,17 @@
         Slog.w(TAG, "updatePrivateDns(" + network + ", " + cfg + ")");
         return (cfg != null)
                 ? mPrivateDnsMap.put(network.netId, cfg)
-                : mPrivateDnsMap.remove(network);
+                : mPrivateDnsMap.remove(network.netId);
     }
 
     public void setDnsConfigurationForNetwork(
             int netId, LinkProperties lp, boolean isDefaultNetwork) {
+        final String[] assignedServers = NetworkUtils.makeStrings(lp.getDnsServers());
+        final String[] domainStrs = getDomainStrings(lp.getDomains());
+
+        updateParametersSettings();
+        final int[] params = { mSampleValidity, mSuccessThreshold, mMinSamples, mMaxSamples };
+
         // We only use the PrivateDnsConfig data pushed to this class instance
         // from ConnectivityService because it works in coordination with
         // NetworkMonitor to decide which networks need validation and runs the
@@ -204,23 +210,20 @@
         final boolean useTls = (privateDnsCfg != null) && privateDnsCfg.useTls;
         final boolean strictMode = (privateDnsCfg != null) && privateDnsCfg.inStrictMode();
         final String tlsHostname = strictMode ? privateDnsCfg.hostname : "";
-
-        final String[] serverStrs = NetworkUtils.makeStrings(
-                strictMode ? Arrays.stream(privateDnsCfg.ips)
-                                   .filter((ip) -> lp.isReachable(ip))
-                                   .collect(Collectors.toList())
-                           : lp.getDnsServers());
-        final String[] domainStrs = getDomainStrings(lp.getDomains());
-
-        updateParametersSettings();
-        final int[] params = { mSampleValidity, mSuccessThreshold, mMinSamples, mMaxSamples };
+        final String[] tlsServers =
+                strictMode ? NetworkUtils.makeStrings(
+                        Arrays.stream(privateDnsCfg.ips)
+                              .filter((ip) -> lp.isReachable(ip))
+                              .collect(Collectors.toList()))
+                : useTls ? assignedServers  // Opportunistic
+                : new String[0];            // Off
 
         Slog.d(TAG, String.format("setDnsConfigurationForNetwork(%d, %s, %s, %s, %s, %s)",
-                netId, Arrays.toString(serverStrs), Arrays.toString(domainStrs),
-                Arrays.toString(params), useTls, tlsHostname));
+                netId, Arrays.toString(assignedServers), Arrays.toString(domainStrs),
+                Arrays.toString(params), tlsHostname, Arrays.toString(tlsServers)));
         try {
             mNMS.setDnsConfigurationForNetwork(
-                    netId, serverStrs, domainStrs, params, useTls, tlsHostname);
+                    netId, assignedServers, domainStrs, params, tlsHostname, tlsServers);
         } catch (Exception e) {
             Slog.e(TAG, "Error setting DNS configuration: " + e);
             return;
diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
index e084ff8..d578e95 100644
--- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java
+++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.CHANGE_NETWORK_STATE;
 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
 import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
+import static android.Manifest.permission.NETWORK_STACK;
 import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
 import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
@@ -27,6 +28,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -39,6 +41,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -150,7 +154,14 @@
         update(mUsers, mApps, true);
     }
 
-    private boolean hasPermission(PackageInfo app, String permission) {
+    @VisibleForTesting
+    boolean isPreinstalledSystemApp(PackageInfo app) {
+        int flags = app.applicationInfo != null ? app.applicationInfo.flags : 0;
+        return (flags & (FLAG_SYSTEM | FLAG_UPDATED_SYSTEM_APP)) != 0;
+    }
+
+    @VisibleForTesting
+    boolean hasPermission(PackageInfo app, String permission) {
         if (app.requestedPermissions != null) {
             for (String p : app.requestedPermissions) {
                 if (permission.equals(p)) {
@@ -166,14 +177,40 @@
     }
 
     private boolean hasRestrictedNetworkPermission(PackageInfo app) {
-        int flags = app.applicationInfo != null ? app.applicationInfo.flags : 0;
-        if ((flags & FLAG_SYSTEM) != 0 || (flags & FLAG_UPDATED_SYSTEM_APP) != 0) {
-            return true;
-        }
+        if (isPreinstalledSystemApp(app)) return true;
         return hasPermission(app, CONNECTIVITY_INTERNAL)
                 || hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS);
     }
 
+    private boolean hasUseBackgroundNetworksPermission(PackageInfo app) {
+        // This function defines what it means to hold the permission to use
+        // background networks.
+        return hasPermission(app, CHANGE_NETWORK_STATE)
+                || hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS)
+                || hasPermission(app, CONNECTIVITY_INTERNAL)
+                || hasPermission(app, NETWORK_STACK)
+                // TODO : remove this check (b/31479477). Not all preinstalled apps should
+                // have access to background networks, they should just request the appropriate
+                // permission for their use case from the list above.
+                || isPreinstalledSystemApp(app);
+    }
+
+    public boolean hasUseBackgroundNetworksPermission(int uid) {
+        final String[] names = mPackageManager.getPackagesForUid(uid);
+        if (null == names || names.length == 0) return false;
+        try {
+            // Only using the first package name. There may be multiple names if multiple
+            // apps share the same UID, but in that case they also share permissions so
+            // querying with any of the names will return the same results.
+            final PackageInfo app = mPackageManager.getPackageInfo(names[0], GET_PERMISSIONS);
+            return hasUseBackgroundNetworksPermission(app);
+        } catch (NameNotFoundException e) {
+            // App not found.
+            loge("NameNotFoundException " + names[0], e);
+            return false;
+        }
+    }
+
     private int[] toIntArray(List<Integer> list) {
         int[] array = new int[list.size()];
         for (int i = 0; i < list.size(); i++) {
@@ -308,4 +345,8 @@
     private static void loge(String s) {
         Log.e(TAG, s);
     }
+
+    private static void loge(String s, Throwable e) {
+        Log.e(TAG, s, e);
+    }
 }
diff --git a/tests/net/java/android/app/usage/NetworkStatsManagerTest.java b/tests/net/java/android/app/usage/NetworkStatsManagerTest.java
new file mode 100644
index 0000000..25e1474
--- /dev/null
+++ b/tests/net/java/android/app/usage/NetworkStatsManagerTest.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2018 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.app.usage;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.net.ConnectivityManager;
+import android.net.INetworkStatsService;
+import android.net.INetworkStatsSession;
+import android.net.NetworkStats.Entry;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkStatsManagerTest {
+
+    private @Mock INetworkStatsService mService;
+    private @Mock INetworkStatsSession mStatsSession;
+
+    private NetworkStatsManager mManager;
+
+    // TODO: change to NetworkTemplate.MATCH_MOBILE once internal constant rename is merged to aosp.
+    private static final int MATCH_MOBILE_ALL = 1;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mManager = new NetworkStatsManager(InstrumentationRegistry.getContext(), mService);
+    }
+
+    @Test
+    public void testQueryDetails() throws RemoteException {
+        final String subscriberId = "subid";
+        final long startTime = 1;
+        final long endTime = 100;
+        final int uid1 = 10001;
+        final int uid2 = 10002;
+        final int uid3 = 10003;
+
+        Entry uid1Entry1 = new Entry("if1", uid1,
+                android.net.NetworkStats.SET_DEFAULT, android.net.NetworkStats.TAG_NONE,
+                100, 10, 200, 20, 0);
+
+        Entry uid1Entry2 = new Entry(
+                "if2", uid1,
+                android.net.NetworkStats.SET_DEFAULT, android.net.NetworkStats.TAG_NONE,
+                100, 10, 200, 20, 0);
+
+        Entry uid2Entry1 = new Entry("if1", uid2,
+                android.net.NetworkStats.SET_DEFAULT, android.net.NetworkStats.TAG_NONE,
+                150, 10, 250, 20, 0);
+
+        Entry uid2Entry2 = new Entry(
+                "if2", uid2,
+                android.net.NetworkStats.SET_DEFAULT, android.net.NetworkStats.TAG_NONE,
+                150, 10, 250, 20, 0);
+
+        NetworkStatsHistory history1 = new NetworkStatsHistory(10, 2);
+        history1.recordData(10, 20, uid1Entry1);
+        history1.recordData(20, 30, uid1Entry2);
+
+        NetworkStatsHistory history2 = new NetworkStatsHistory(10, 2);
+        history1.recordData(30, 40, uid2Entry1);
+        history1.recordData(35, 45, uid2Entry2);
+
+
+        when(mService.openSessionForUsageStats(anyInt(), anyString())).thenReturn(mStatsSession);
+        when(mStatsSession.getRelevantUids()).thenReturn(new int[] { uid1, uid2, uid3 });
+
+        when(mStatsSession.getHistoryIntervalForUid(any(NetworkTemplate.class),
+                eq(uid1), eq(android.net.NetworkStats.SET_ALL),
+                eq(android.net.NetworkStats.TAG_NONE),
+                eq(NetworkStatsHistory.FIELD_ALL), eq(startTime), eq(endTime)))
+                .then((InvocationOnMock inv) -> {
+                    NetworkTemplate template = inv.getArgument(0);
+                    assertEquals(MATCH_MOBILE_ALL, template.getMatchRule());
+                    assertEquals(subscriberId, template.getSubscriberId());
+                    return history1;
+                });
+
+        when(mStatsSession.getHistoryIntervalForUid(any(NetworkTemplate.class),
+                eq(uid2), eq(android.net.NetworkStats.SET_ALL),
+                eq(android.net.NetworkStats.TAG_NONE),
+                eq(NetworkStatsHistory.FIELD_ALL), eq(startTime), eq(endTime)))
+                .then((InvocationOnMock inv) -> {
+                    NetworkTemplate template = inv.getArgument(0);
+                    assertEquals(MATCH_MOBILE_ALL, template.getMatchRule());
+                    assertEquals(subscriberId, template.getSubscriberId());
+                    return history2;
+                });
+
+
+        NetworkStats stats = mManager.queryDetails(
+                ConnectivityManager.TYPE_MOBILE, subscriberId, startTime, endTime);
+
+        NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+
+        // First 2 buckets exactly match entry timings
+        assertTrue(stats.getNextBucket(bucket));
+        assertEquals(10, bucket.getStartTimeStamp());
+        assertEquals(20, bucket.getEndTimeStamp());
+        assertBucketMatches(uid1Entry1, bucket);
+
+        assertTrue(stats.getNextBucket(bucket));
+        assertEquals(20, bucket.getStartTimeStamp());
+        assertEquals(30, bucket.getEndTimeStamp());
+        assertBucketMatches(uid1Entry2, bucket);
+
+        // 30 -> 40: contains uid2Entry1 and half of uid2Entry2
+        assertTrue(stats.getNextBucket(bucket));
+        assertEquals(30, bucket.getStartTimeStamp());
+        assertEquals(40, bucket.getEndTimeStamp());
+        assertEquals(225, bucket.getRxBytes());
+        assertEquals(15, bucket.getRxPackets());
+        assertEquals(375, bucket.getTxBytes());
+        assertEquals(30, bucket.getTxPackets());
+
+        // 40 -> 50: contains half of uid2Entry2
+        assertTrue(stats.getNextBucket(bucket));
+        assertEquals(40, bucket.getStartTimeStamp());
+        assertEquals(50, bucket.getEndTimeStamp());
+        assertEquals(75, bucket.getRxBytes());
+        assertEquals(5, bucket.getRxPackets());
+        assertEquals(125, bucket.getTxBytes());
+        assertEquals(10, bucket.getTxPackets());
+
+        assertFalse(stats.hasNextBucket());
+    }
+
+    @Test
+    public void testQueryDetails_NoSubscriberId() throws RemoteException {
+        final long startTime = 1;
+        final long endTime = 100;
+        final int uid1 = 10001;
+        final int uid2 = 10002;
+
+        when(mService.openSessionForUsageStats(anyInt(), anyString())).thenReturn(mStatsSession);
+        when(mStatsSession.getRelevantUids()).thenReturn(new int[] { uid1, uid2 });
+
+        NetworkStats stats = mManager.queryDetails(
+                ConnectivityManager.TYPE_MOBILE, null, startTime, endTime);
+
+        when(mStatsSession.getHistoryIntervalForUid(any(NetworkTemplate.class),
+                anyInt(), anyInt(), anyInt(), anyInt(), anyLong(), anyLong()))
+                .thenReturn(new NetworkStatsHistory(10, 0));
+
+        verify(mStatsSession, times(1)).getHistoryIntervalForUid(
+                argThat((NetworkTemplate t) ->
+                        // No subscriberId: MATCH_MOBILE_WILDCARD template
+                        t.getMatchRule() == NetworkTemplate.MATCH_MOBILE_WILDCARD),
+                eq(uid1), eq(android.net.NetworkStats.SET_ALL),
+                eq(android.net.NetworkStats.TAG_NONE),
+                eq(NetworkStatsHistory.FIELD_ALL), eq(startTime), eq(endTime));
+
+        verify(mStatsSession, times(1)).getHistoryIntervalForUid(
+                argThat((NetworkTemplate t) ->
+                        // No subscriberId: MATCH_MOBILE_WILDCARD template
+                        t.getMatchRule() == NetworkTemplate.MATCH_MOBILE_WILDCARD),
+                eq(uid2), eq(android.net.NetworkStats.SET_ALL),
+                eq(android.net.NetworkStats.TAG_NONE),
+                eq(NetworkStatsHistory.FIELD_ALL), eq(startTime), eq(endTime));
+
+        assertFalse(stats.hasNextBucket());
+    }
+
+    private void assertBucketMatches(Entry expected,
+            NetworkStats.Bucket actual) {
+        assertEquals(expected.uid, actual.getUid());
+        assertEquals(expected.rxBytes, actual.getRxBytes());
+        assertEquals(expected.rxPackets, actual.getRxPackets());
+        assertEquals(expected.txBytes, actual.getTxBytes());
+        assertEquals(expected.txPackets, actual.getTxPackets());
+    }
+}
diff --git a/tests/net/java/android/net/IpPrefixTest.java b/tests/net/java/android/net/IpPrefixTest.java
index b5b2c07..1f1ba2e 100644
--- a/tests/net/java/android/net/IpPrefixTest.java
+++ b/tests/net/java/android/net/IpPrefixTest.java
@@ -223,14 +223,14 @@
     }
 
     @Test
-    public void testContains() {
+    public void testContainsInetAddress() {
         IpPrefix p = new IpPrefix("2001:db8:f00::ace:d00d/127");
         assertTrue(p.contains(Address("2001:db8:f00::ace:d00c")));
         assertTrue(p.contains(Address("2001:db8:f00::ace:d00d")));
         assertFalse(p.contains(Address("2001:db8:f00::ace:d00e")));
         assertFalse(p.contains(Address("2001:db8:f00::bad:d00d")));
         assertFalse(p.contains(Address("2001:4868:4860::8888")));
-        assertFalse(p.contains(null));
+        assertFalse(p.contains((InetAddress)null));
         assertFalse(p.contains(Address("8.8.8.8")));
 
         p = new IpPrefix("192.0.2.0/23");
@@ -251,6 +251,53 @@
     }
 
     @Test
+    public void testContainsIpPrefix() {
+        assertTrue(new IpPrefix("0.0.0.0/0").containsPrefix(new IpPrefix("0.0.0.0/0")));
+        assertTrue(new IpPrefix("0.0.0.0/0").containsPrefix(new IpPrefix("1.2.3.4/0")));
+        assertTrue(new IpPrefix("0.0.0.0/0").containsPrefix(new IpPrefix("1.2.3.4/8")));
+        assertTrue(new IpPrefix("0.0.0.0/0").containsPrefix(new IpPrefix("1.2.3.4/24")));
+        assertTrue(new IpPrefix("0.0.0.0/0").containsPrefix(new IpPrefix("1.2.3.4/23")));
+
+        assertTrue(new IpPrefix("1.2.3.4/8").containsPrefix(new IpPrefix("1.2.3.4/8")));
+        assertTrue(new IpPrefix("1.2.3.4/8").containsPrefix(new IpPrefix("1.254.12.9/8")));
+        assertTrue(new IpPrefix("1.2.3.4/21").containsPrefix(new IpPrefix("1.2.3.4/21")));
+        assertTrue(new IpPrefix("1.2.3.4/32").containsPrefix(new IpPrefix("1.2.3.4/32")));
+
+        assertTrue(new IpPrefix("1.2.3.4/20").containsPrefix(new IpPrefix("1.2.3.0/24")));
+
+        assertFalse(new IpPrefix("1.2.3.4/32").containsPrefix(new IpPrefix("1.2.3.5/32")));
+        assertFalse(new IpPrefix("1.2.3.4/8").containsPrefix(new IpPrefix("2.2.3.4/8")));
+        assertFalse(new IpPrefix("0.0.0.0/16").containsPrefix(new IpPrefix("0.0.0.0/15")));
+        assertFalse(new IpPrefix("100.0.0.0/8").containsPrefix(new IpPrefix("99.0.0.0/8")));
+
+        assertTrue(new IpPrefix("::/0").containsPrefix(new IpPrefix("::/0")));
+        assertTrue(new IpPrefix("::/0").containsPrefix(new IpPrefix("2001:db8::f00/1")));
+        assertTrue(new IpPrefix("::/0").containsPrefix(new IpPrefix("3d8a:661:a0::770/8")));
+        assertTrue(new IpPrefix("::/0").containsPrefix(new IpPrefix("2001:db8::f00/8")));
+        assertTrue(new IpPrefix("::/0").containsPrefix(new IpPrefix("2001:db8::f00/64")));
+        assertTrue(new IpPrefix("::/0").containsPrefix(new IpPrefix("2001:db8::f00/113")));
+        assertTrue(new IpPrefix("::/0").containsPrefix(new IpPrefix("2001:db8::f00/128")));
+
+        assertTrue(new IpPrefix("2001:db8:f00::ace:d00d/64").containsPrefix(
+                new IpPrefix("2001:db8:f00::ace:d00d/64")));
+        assertTrue(new IpPrefix("2001:db8:f00::ace:d00d/64").containsPrefix(
+                new IpPrefix("2001:db8:f00::ace:d00d/120")));
+        assertFalse(new IpPrefix("2001:db8:f00::ace:d00d/64").containsPrefix(
+                new IpPrefix("2001:db8:f00::ace:d00d/32")));
+        assertFalse(new IpPrefix("2001:db8:f00::ace:d00d/64").containsPrefix(
+                new IpPrefix("2006:db8:f00::ace:d00d/96")));
+
+        assertTrue(new IpPrefix("2001:db8:f00::ace:d00d/128").containsPrefix(
+                new IpPrefix("2001:db8:f00::ace:d00d/128")));
+        assertTrue(new IpPrefix("2001:db8:f00::ace:d00d/100").containsPrefix(
+                new IpPrefix("2001:db8:f00::ace:ccaf/110")));
+
+        assertFalse(new IpPrefix("2001:db8:f00::ace:d00d/128").containsPrefix(
+                new IpPrefix("2001:db8:f00::ace:d00e/128")));
+        assertFalse(new IpPrefix("::/30").containsPrefix(new IpPrefix("::/29")));
+    }
+
+    @Test
     public void testHashCode() {
         IpPrefix p = new IpPrefix(new byte[4], 0);
         Random random = new Random();
diff --git a/tests/net/java/android/net/IpSecManagerTest.java b/tests/net/java/android/net/IpSecManagerTest.java
index d88f8af..970596d 100644
--- a/tests/net/java/android/net/IpSecManagerTest.java
+++ b/tests/net/java/android/net/IpSecManagerTest.java
@@ -50,13 +50,18 @@
 
     private static final int TEST_UDP_ENCAP_PORT = 34567;
     private static final int DROID_SPI = 0xD1201D;
+    private static final int DUMMY_RESOURCE_ID = 0x1234;
 
     private static final InetAddress GOOGLE_DNS_4;
+    private static final String VTI_INTF_NAME = "ipsec_test";
+    private static final InetAddress VTI_LOCAL_ADDRESS;
+    private static final LinkAddress VTI_INNER_ADDRESS = new LinkAddress("10.0.1.1/24");
 
     static {
         try {
             // Google Public DNS Addresses;
             GOOGLE_DNS_4 = InetAddress.getByName("8.8.8.8");
+            VTI_LOCAL_ADDRESS = InetAddress.getByName("8.8.4.4");
         } catch (UnknownHostException e) {
             throw new RuntimeException("Could not resolve DNS Addresses", e);
         }
@@ -77,9 +82,8 @@
      */
     @Test
     public void testAllocSpi() throws Exception {
-        int resourceId = 1;
         IpSecSpiResponse spiResp =
-                new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, DROID_SPI);
+                new IpSecSpiResponse(IpSecManager.Status.OK, DUMMY_RESOURCE_ID, DROID_SPI);
         when(mMockIpSecService.allocateSecurityParameterIndex(
                         eq(GOOGLE_DNS_4.getHostAddress()),
                         eq(DROID_SPI),
@@ -92,14 +96,13 @@
 
         droidSpi.close();
 
-        verify(mMockIpSecService).releaseSecurityParameterIndex(resourceId);
+        verify(mMockIpSecService).releaseSecurityParameterIndex(DUMMY_RESOURCE_ID);
     }
 
     @Test
     public void testAllocRandomSpi() throws Exception {
-        int resourceId = 1;
         IpSecSpiResponse spiResp =
-                new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, DROID_SPI);
+                new IpSecSpiResponse(IpSecManager.Status.OK, DUMMY_RESOURCE_ID, DROID_SPI);
         when(mMockIpSecService.allocateSecurityParameterIndex(
                         eq(GOOGLE_DNS_4.getHostAddress()),
                         eq(IpSecManager.INVALID_SECURITY_PARAMETER_INDEX),
@@ -113,7 +116,7 @@
 
         randomSpi.close();
 
-        verify(mMockIpSecService).releaseSecurityParameterIndex(resourceId);
+        verify(mMockIpSecService).releaseSecurityParameterIndex(DUMMY_RESOURCE_ID);
     }
 
     /*
@@ -165,11 +168,10 @@
 
     @Test
     public void testOpenEncapsulationSocket() throws Exception {
-        int resourceId = 1;
         IpSecUdpEncapResponse udpEncapResp =
                 new IpSecUdpEncapResponse(
                         IpSecManager.Status.OK,
-                        resourceId,
+                        DUMMY_RESOURCE_ID,
                         TEST_UDP_ENCAP_PORT,
                         Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP));
         when(mMockIpSecService.openUdpEncapsulationSocket(eq(TEST_UDP_ENCAP_PORT), anyObject()))
@@ -182,16 +184,15 @@
 
         encapSocket.close();
 
-        verify(mMockIpSecService).closeUdpEncapsulationSocket(resourceId);
+        verify(mMockIpSecService).closeUdpEncapsulationSocket(DUMMY_RESOURCE_ID);
     }
 
     @Test
     public void testOpenEncapsulationSocketOnRandomPort() throws Exception {
-        int resourceId = 1;
         IpSecUdpEncapResponse udpEncapResp =
                 new IpSecUdpEncapResponse(
                         IpSecManager.Status.OK,
-                        resourceId,
+                        DUMMY_RESOURCE_ID,
                         TEST_UDP_ENCAP_PORT,
                         Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP));
 
@@ -206,7 +207,7 @@
 
         encapSocket.close();
 
-        verify(mMockIpSecService).closeUdpEncapsulationSocket(resourceId);
+        verify(mMockIpSecService).closeUdpEncapsulationSocket(DUMMY_RESOURCE_ID);
     }
 
     @Test
@@ -219,4 +220,45 @@
     }
 
     // TODO: add test when applicable transform builder interface is available
-}
+
+    private IpSecManager.IpSecTunnelInterface createAndValidateVti(int resourceId, String intfName)
+            throws Exception {
+        IpSecTunnelInterfaceResponse dummyResponse =
+                new IpSecTunnelInterfaceResponse(IpSecManager.Status.OK, resourceId, intfName);
+        when(mMockIpSecService.createTunnelInterface(
+                eq(VTI_LOCAL_ADDRESS.getHostAddress()), eq(GOOGLE_DNS_4.getHostAddress()),
+                anyObject(), anyObject()))
+                        .thenReturn(dummyResponse);
+
+        IpSecManager.IpSecTunnelInterface tunnelIntf = mIpSecManager.createIpSecTunnelInterface(
+                VTI_LOCAL_ADDRESS, GOOGLE_DNS_4, mock(Network.class));
+
+        assertNotNull(tunnelIntf);
+        return tunnelIntf;
+    }
+
+    @Test
+    public void testCreateVti() throws Exception {
+        IpSecManager.IpSecTunnelInterface tunnelIntf =
+                createAndValidateVti(DUMMY_RESOURCE_ID, VTI_INTF_NAME);
+
+        assertEquals(VTI_INTF_NAME, tunnelIntf.getInterfaceName());
+
+        tunnelIntf.close();
+        verify(mMockIpSecService).deleteTunnelInterface(eq(DUMMY_RESOURCE_ID));
+    }
+
+    @Test
+    public void testAddRemoveAddressesFromVti() throws Exception {
+        IpSecManager.IpSecTunnelInterface tunnelIntf =
+                createAndValidateVti(DUMMY_RESOURCE_ID, VTI_INTF_NAME);
+
+        tunnelIntf.addAddress(VTI_INNER_ADDRESS);
+        verify(mMockIpSecService)
+                .addAddressToTunnelInterface(eq(DUMMY_RESOURCE_ID), eq(VTI_INNER_ADDRESS));
+
+        tunnelIntf.removeAddress(VTI_INNER_ADDRESS);
+        verify(mMockIpSecService)
+                .addAddressToTunnelInterface(eq(DUMMY_RESOURCE_ID), eq(VTI_INNER_ADDRESS));
+    }
+}
\ No newline at end of file
diff --git a/tests/net/java/android/net/NetworkCapabilitiesTest.java b/tests/net/java/android/net/NetworkCapabilitiesTest.java
index 4c6a644..69edc0c 100644
--- a/tests/net/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/java/android/net/NetworkCapabilitiesTest.java
@@ -17,18 +17,25 @@
 package android.net;
 
 import static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
 import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkCapabilities.UNRESTRICTED_CAPABILITIES;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -222,7 +229,9 @@
         assertFalse(netCap.appliesToUidRange(new UidRange(60, 3400)));
 
         NetworkCapabilities netCap2 = new NetworkCapabilities();
-        assertFalse(netCap2.satisfiedByUids(netCap));
+        // A new netcap object has null UIDs, so anything will satisfy it.
+        assertTrue(netCap2.satisfiedByUids(netCap));
+        // Still not equal though.
         assertFalse(netCap2.equalsUids(netCap));
         netCap2.setUids(uids);
         assertTrue(netCap2.satisfiedByUids(netCap));
@@ -239,7 +248,7 @@
         assertTrue(netCap.appliesToUid(650));
         assertFalse(netCap.appliesToUid(500));
 
-        assertFalse(new NetworkCapabilities().satisfiedByUids(netCap));
+        assertTrue(new NetworkCapabilities().satisfiedByUids(netCap));
         netCap.combineCapabilities(new NetworkCapabilities());
         assertTrue(netCap.appliesToUid(500));
         assertTrue(netCap.appliesToUidRange(new UidRange(1, 100000)));
@@ -261,6 +270,130 @@
         assertEqualsThroughMarshalling(netCap);
     }
 
+    @Test
+    public void testOemPaid() {
+        NetworkCapabilities nc = new NetworkCapabilities();
+        nc.maybeMarkCapabilitiesRestricted();
+        assertFalse(nc.hasCapability(NET_CAPABILITY_OEM_PAID));
+        assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+
+        nc.addCapability(NET_CAPABILITY_OEM_PAID);
+        nc.maybeMarkCapabilitiesRestricted();
+        assertTrue(nc.hasCapability(NET_CAPABILITY_OEM_PAID));
+        assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+    }
+
+    @Test
+    public void testUnwantedCapabilities() {
+        NetworkCapabilities network = new NetworkCapabilities();
+
+        NetworkCapabilities request = new NetworkCapabilities();
+        assertTrue("Request: " + request + ", Network:" + network,
+                request.satisfiedByNetworkCapabilities(network));
+
+        // Requesting absence of capabilities that network doesn't have. Request should satisfy.
+        request.addUnwantedCapability(NET_CAPABILITY_WIFI_P2P);
+        request.addUnwantedCapability(NET_CAPABILITY_NOT_METERED);
+        assertTrue(request.satisfiedByNetworkCapabilities(network));
+        assertArrayEquals(new int[] {NET_CAPABILITY_WIFI_P2P, NET_CAPABILITY_NOT_METERED},
+                request.getUnwantedCapabilities());
+
+        // This is a default capability, just want to make sure its there because we use it below.
+        assertTrue(network.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+
+        // Verify that adding unwanted capability will effectively remove it from capability list.
+        request.addUnwantedCapability(NET_CAPABILITY_NOT_RESTRICTED);
+        assertTrue(request.hasUnwantedCapability(NET_CAPABILITY_NOT_RESTRICTED));
+        assertFalse(request.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+
+        // Now this request won't be satisfied because network contains NOT_RESTRICTED.
+        assertFalse(request.satisfiedByNetworkCapabilities(network));
+        network.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+        assertTrue(request.satisfiedByNetworkCapabilities(network));
+
+        // Verify that adding capability will effectively remove it from unwanted list
+        request.addCapability(NET_CAPABILITY_NOT_RESTRICTED);
+        assertTrue(request.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+        assertFalse(request.hasUnwantedCapability(NET_CAPABILITY_NOT_RESTRICTED));
+
+        assertFalse(request.satisfiedByNetworkCapabilities(network));
+        network.addCapability(NET_CAPABILITY_NOT_RESTRICTED);
+        assertTrue(request.satisfiedByNetworkCapabilities(network));
+    }
+
+    @Test
+    public void testEqualsNetCapabilities() {
+        NetworkCapabilities nc1 = new NetworkCapabilities();
+        NetworkCapabilities nc2 = new NetworkCapabilities();
+        assertTrue(nc1.equalsNetCapabilities(nc2));
+        assertEquals(nc1, nc2);
+
+        nc1.addCapability(NET_CAPABILITY_MMS);
+        assertFalse(nc1.equalsNetCapabilities(nc2));
+        assertNotEquals(nc1, nc2);
+        nc2.addCapability(NET_CAPABILITY_MMS);
+        assertTrue(nc1.equalsNetCapabilities(nc2));
+        assertEquals(nc1, nc2);
+
+        nc1.addUnwantedCapability(NET_CAPABILITY_INTERNET);
+        assertFalse(nc1.equalsNetCapabilities(nc2));
+        nc2.addUnwantedCapability(NET_CAPABILITY_INTERNET);
+        assertTrue(nc1.equalsNetCapabilities(nc2));
+
+        nc1.removeCapability(NET_CAPABILITY_INTERNET);
+        assertFalse(nc1.equalsNetCapabilities(nc2));
+        nc2.removeCapability(NET_CAPABILITY_INTERNET);
+        assertTrue(nc1.equalsNetCapabilities(nc2));
+    }
+
+    @Test
+    public void testCombineCapabilities() {
+        NetworkCapabilities nc1 = new NetworkCapabilities();
+        NetworkCapabilities nc2 = new NetworkCapabilities();
+
+        nc1.addUnwantedCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
+        nc1.addCapability(NET_CAPABILITY_NOT_ROAMING);
+        assertNotEquals(nc1, nc2);
+        nc2.combineCapabilities(nc1);
+        assertEquals(nc1, nc2);
+        assertTrue(nc2.hasCapability(NET_CAPABILITY_NOT_ROAMING));
+        assertTrue(nc2.hasUnwantedCapability(NET_CAPABILITY_CAPTIVE_PORTAL));
+
+        // This will effectively move NOT_ROAMING capability from required to unwanted for nc1.
+        nc1.addUnwantedCapability(NET_CAPABILITY_NOT_ROAMING);
+
+        nc2.combineCapabilities(nc1);
+        // We will get this capability in both requested and unwanted lists thus this request
+        // will never be satisfied.
+        assertTrue(nc2.hasCapability(NET_CAPABILITY_NOT_ROAMING));
+        assertTrue(nc2.hasUnwantedCapability(NET_CAPABILITY_NOT_ROAMING));
+    }
+
+    @Test
+    public void testSetCapabilities() {
+        final int[] REQUIRED_CAPABILITIES = new int[] {
+                NET_CAPABILITY_INTERNET, NET_CAPABILITY_NOT_VPN };
+        final int[] UNWANTED_CAPABILITIES = new int[] {
+                NET_CAPABILITY_NOT_RESTRICTED, NET_CAPABILITY_NOT_METERED
+        };
+
+        NetworkCapabilities nc1 = new NetworkCapabilities();
+        NetworkCapabilities nc2 = new NetworkCapabilities();
+
+        nc1.setCapabilities(REQUIRED_CAPABILITIES, UNWANTED_CAPABILITIES);
+        assertArrayEquals(REQUIRED_CAPABILITIES, nc1.getCapabilities());
+
+        // Verify that setting and adding capabilities leads to the same object state.
+        nc2.clearAll();
+        for (int cap : REQUIRED_CAPABILITIES) {
+            nc2.addCapability(cap);
+        }
+        for (int cap : UNWANTED_CAPABILITIES) {
+            nc2.addUnwantedCapability(cap);
+        }
+        assertEquals(nc1, nc2);
+    }
+
     private void assertEqualsThroughMarshalling(NetworkCapabilities netCap) {
         Parcel p = Parcel.obtain();
         netCap.writeToParcel(p, /* flags */ 0);
diff --git a/tests/net/java/android/net/NetworkStatsTest.java b/tests/net/java/android/net/NetworkStatsTest.java
index 035a4cd..8f18d07 100644
--- a/tests/net/java/android/net/NetworkStatsTest.java
+++ b/tests/net/java/android/net/NetworkStatsTest.java
@@ -19,6 +19,7 @@
 import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
 import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
 import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
+import static android.net.NetworkStats.INTERFACES_ALL;
 import static android.net.NetworkStats.METERED_ALL;
 import static android.net.NetworkStats.METERED_NO;
 import static android.net.NetworkStats.METERED_YES;
@@ -31,14 +32,17 @@
 import static android.net.NetworkStats.SET_DBG_VPN_OUT;
 import static android.net.NetworkStats.SET_ALL;
 import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.TAG_ALL;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.os.Process;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.test.filters.SmallTest;
+import android.util.ArrayMap;
 
 import com.google.android.collect.Sets;
 
@@ -641,6 +645,218 @@
                 ROAMING_ALL, DEFAULT_NETWORK_ALL, 50500L, 27L, 100200L, 55, 0);
     }
 
+    @Test
+    public void testFilter_NoFilter() {
+        NetworkStats.Entry entry1 = new NetworkStats.Entry(
+                "test1", 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+        NetworkStats.Entry entry2 = new NetworkStats.Entry(
+                "test2", 10101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+        NetworkStats.Entry entry3 = new NetworkStats.Entry(
+                "test2", 10101, SET_DEFAULT, 123, METERED_NO, ROAMING_NO,
+                DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+        NetworkStats stats = new NetworkStats(TEST_START, 3)
+                .addValues(entry1)
+                .addValues(entry2)
+                .addValues(entry3);
+
+        stats.filter(UID_ALL, INTERFACES_ALL, TAG_ALL);
+        assertEquals(3, stats.size());
+        assertEquals(entry1, stats.getValues(0, null));
+        assertEquals(entry2, stats.getValues(1, null));
+        assertEquals(entry3, stats.getValues(2, null));
+    }
+
+    @Test
+    public void testFilter_UidFilter() {
+        final int testUid = 10101;
+        NetworkStats.Entry entry1 = new NetworkStats.Entry(
+                "test1", 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+        NetworkStats.Entry entry2 = new NetworkStats.Entry(
+                "test2", testUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+        NetworkStats.Entry entry3 = new NetworkStats.Entry(
+                "test2", testUid, SET_DEFAULT, 123, METERED_NO, ROAMING_NO,
+                DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+        NetworkStats stats = new NetworkStats(TEST_START, 3)
+                .addValues(entry1)
+                .addValues(entry2)
+                .addValues(entry3);
+
+        stats.filter(testUid, INTERFACES_ALL, TAG_ALL);
+        assertEquals(2, stats.size());
+        assertEquals(entry2, stats.getValues(0, null));
+        assertEquals(entry3, stats.getValues(1, null));
+    }
+
+    @Test
+    public void testFilter_InterfaceFilter() {
+        final String testIf1 = "testif1";
+        final String testIf2 = "testif2";
+        NetworkStats.Entry entry1 = new NetworkStats.Entry(
+                testIf1, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+        NetworkStats.Entry entry2 = new NetworkStats.Entry(
+                "otherif", 10101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+        NetworkStats.Entry entry3 = new NetworkStats.Entry(
+                testIf1, 10101, SET_DEFAULT, 123, METERED_NO, ROAMING_NO,
+                DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+        NetworkStats.Entry entry4 = new NetworkStats.Entry(
+                testIf2, 10101, SET_DEFAULT, 123, METERED_NO, ROAMING_NO,
+                DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+        NetworkStats stats = new NetworkStats(TEST_START, 4)
+                .addValues(entry1)
+                .addValues(entry2)
+                .addValues(entry3)
+                .addValues(entry4);
+
+        stats.filter(UID_ALL, new String[] { testIf1, testIf2 }, TAG_ALL);
+        assertEquals(3, stats.size());
+        assertEquals(entry1, stats.getValues(0, null));
+        assertEquals(entry3, stats.getValues(1, null));
+        assertEquals(entry4, stats.getValues(2, null));
+    }
+
+    @Test
+    public void testFilter_EmptyInterfaceFilter() {
+        NetworkStats.Entry entry1 = new NetworkStats.Entry(
+                "if1", 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+        NetworkStats.Entry entry2 = new NetworkStats.Entry(
+                "if2", 10101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+        NetworkStats stats = new NetworkStats(TEST_START, 3)
+                .addValues(entry1)
+                .addValues(entry2);
+
+        stats.filter(UID_ALL, new String[] { }, TAG_ALL);
+        assertEquals(0, stats.size());
+    }
+
+    @Test
+    public void testFilter_TagFilter() {
+        final int testTag = 123;
+        final int otherTag = 456;
+        NetworkStats.Entry entry1 = new NetworkStats.Entry(
+                "test1", 10100, SET_DEFAULT, testTag, METERED_NO, ROAMING_NO,
+                DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+        NetworkStats.Entry entry2 = new NetworkStats.Entry(
+                "test2", 10101, SET_DEFAULT, testTag, METERED_NO, ROAMING_NO,
+                DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+        NetworkStats.Entry entry3 = new NetworkStats.Entry(
+                "test2", 10101, SET_DEFAULT, otherTag, METERED_NO, ROAMING_NO,
+                DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+        NetworkStats stats = new NetworkStats(TEST_START, 3)
+                .addValues(entry1)
+                .addValues(entry2)
+                .addValues(entry3);
+
+        stats.filter(UID_ALL, INTERFACES_ALL, testTag);
+        assertEquals(2, stats.size());
+        assertEquals(entry1, stats.getValues(0, null));
+        assertEquals(entry2, stats.getValues(1, null));
+    }
+
+    @Test
+    public void testApply464xlatAdjustments() {
+        final String v4Iface = "v4-wlan0";
+        final String baseIface = "wlan0";
+        final String otherIface = "other";
+        final int appUid = 10001;
+        final int rootUid = Process.ROOT_UID;
+        ArrayMap<String, String> stackedIface = new ArrayMap<>();
+        stackedIface.put(v4Iface, baseIface);
+
+        NetworkStats.Entry otherEntry = new NetworkStats.Entry(
+                otherIface, appUid, SET_DEFAULT, TAG_NONE,
+                2600  /* rxBytes */,
+                2 /* rxPackets */,
+                3800 /* txBytes */,
+                3 /* txPackets */,
+                0 /* operations */);
+
+        NetworkStats stats = new NetworkStats(TEST_START, 3)
+                .addValues(v4Iface, appUid, SET_DEFAULT, TAG_NONE,
+                        30501490  /* rxBytes */,
+                        22401 /* rxPackets */,
+                        876235 /* txBytes */,
+                        13805 /* txPackets */,
+                        0 /* operations */)
+                .addValues(baseIface, rootUid, SET_DEFAULT, TAG_NONE,
+                        31113087,
+                        22588,
+                        1169942,
+                        13902,
+                        0)
+                .addValues(otherEntry);
+
+        stats.apply464xlatAdjustments(stackedIface);
+
+        assertEquals(3, stats.size());
+        assertValues(stats, 0, v4Iface, appUid, SET_DEFAULT, TAG_NONE,
+                METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO,
+                30949510,
+                22401,
+                1152335,
+                13805,
+                0);
+        assertValues(stats, 1, baseIface, 0, SET_DEFAULT, TAG_NONE,
+                METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO,
+                163577,
+                187,
+                17607,
+                97,
+                0);
+        assertEquals(otherEntry, stats.getValues(2, null));
+    }
+
+    @Test
+    public void testApply464xlatAdjustments_noStackedIface() {
+        NetworkStats.Entry firstEntry = new NetworkStats.Entry(
+                "if1", 10002, SET_DEFAULT, TAG_NONE,
+                2600  /* rxBytes */,
+                2 /* rxPackets */,
+                3800 /* txBytes */,
+                3 /* txPackets */,
+                0 /* operations */);
+        NetworkStats.Entry secondEntry = new NetworkStats.Entry(
+                "if2", 10002, SET_DEFAULT, TAG_NONE,
+                5000  /* rxBytes */,
+                3 /* rxPackets */,
+                6000 /* txBytes */,
+                4 /* txPackets */,
+                0 /* operations */);
+
+        NetworkStats stats = new NetworkStats(TEST_START, 2)
+                .addValues(firstEntry)
+                .addValues(secondEntry);
+
+        // Empty map: no adjustment
+        stats.apply464xlatAdjustments(new ArrayMap<>());
+
+        assertEquals(2, stats.size());
+        assertEquals(firstEntry, stats.getValues(0, null));
+        assertEquals(secondEntry, stats.getValues(1, null));
+    }
+
     private static void assertContains(NetworkStats stats,  String iface, int uid, int set,
             int tag, int metered, int roaming, int defaultNetwork, long rxBytes, long rxPackets,
             long txBytes, long txPackets, long operations) {
diff --git a/tests/net/java/android/net/NetworkUtilsTest.java b/tests/net/java/android/net/NetworkUtilsTest.java
index 8d51c3b..a5ee8e3 100644
--- a/tests/net/java/android/net/NetworkUtilsTest.java
+++ b/tests/net/java/android/net/NetworkUtilsTest.java
@@ -19,8 +19,10 @@
 import android.net.NetworkUtils;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import java.math.BigInteger;
 import java.net.Inet4Address;
 import java.net.InetAddress;
+import java.util.TreeSet;
 
 import junit.framework.TestCase;
 
@@ -67,4 +69,101 @@
         assertInvalidNetworkMask(IPv4Address("255.255.255.253"));
         assertInvalidNetworkMask(IPv4Address("255.255.0.255"));
     }
+
+    @SmallTest
+    public void testRoutedIPv4AddressCount() {
+        final TreeSet<IpPrefix> set = new TreeSet<>(IpPrefix.lengthComparator());
+        // No routes routes to no addresses.
+        assertEquals(0, NetworkUtils.routedIPv4AddressCount(set));
+
+        set.add(new IpPrefix("0.0.0.0/0"));
+        assertEquals(1l << 32, NetworkUtils.routedIPv4AddressCount(set));
+
+        set.add(new IpPrefix("20.18.0.0/16"));
+        set.add(new IpPrefix("20.18.0.0/24"));
+        set.add(new IpPrefix("20.18.0.0/8"));
+        // There is a default route, still covers everything
+        assertEquals(1l << 32, NetworkUtils.routedIPv4AddressCount(set));
+
+        set.clear();
+        set.add(new IpPrefix("20.18.0.0/24"));
+        set.add(new IpPrefix("20.18.0.0/8"));
+        // The 8-length includes the 24-length prefix
+        assertEquals(1l << 24, NetworkUtils.routedIPv4AddressCount(set));
+
+        set.add(new IpPrefix("10.10.10.126/25"));
+        // The 8-length does not include this 25-length prefix
+        assertEquals((1l << 24) + (1 << 7), NetworkUtils.routedIPv4AddressCount(set));
+
+        set.clear();
+        set.add(new IpPrefix("1.2.3.4/32"));
+        set.add(new IpPrefix("1.2.3.4/32"));
+        set.add(new IpPrefix("1.2.3.4/32"));
+        set.add(new IpPrefix("1.2.3.4/32"));
+        assertEquals(1l, NetworkUtils.routedIPv4AddressCount(set));
+
+        set.add(new IpPrefix("1.2.3.5/32"));
+        set.add(new IpPrefix("1.2.3.6/32"));
+
+        set.add(new IpPrefix("1.2.3.7/32"));
+        set.add(new IpPrefix("1.2.3.8/32"));
+        set.add(new IpPrefix("1.2.3.9/32"));
+        set.add(new IpPrefix("1.2.3.0/32"));
+        assertEquals(7l, NetworkUtils.routedIPv4AddressCount(set));
+
+        // 1.2.3.4/30 eats 1.2.3.{4-7}/32
+        set.add(new IpPrefix("1.2.3.4/30"));
+        set.add(new IpPrefix("6.2.3.4/28"));
+        set.add(new IpPrefix("120.2.3.4/16"));
+        assertEquals(7l - 4 + 4 + 16 + 65536, NetworkUtils.routedIPv4AddressCount(set));
+    }
+
+    @SmallTest
+    public void testRoutedIPv6AddressCount() {
+        final TreeSet<IpPrefix> set = new TreeSet<>(IpPrefix.lengthComparator());
+        // No routes routes to no addresses.
+        assertEquals(BigInteger.ZERO, NetworkUtils.routedIPv6AddressCount(set));
+
+        set.add(new IpPrefix("::/0"));
+        assertEquals(BigInteger.ONE.shiftLeft(128), NetworkUtils.routedIPv6AddressCount(set));
+
+        set.add(new IpPrefix("1234:622a::18/64"));
+        set.add(new IpPrefix("add4:f00:80:f7:1111::6adb/96"));
+        set.add(new IpPrefix("add4:f00:80:f7:1111::6adb/8"));
+        // There is a default route, still covers everything
+        assertEquals(BigInteger.ONE.shiftLeft(128), NetworkUtils.routedIPv6AddressCount(set));
+
+        set.clear();
+        set.add(new IpPrefix("add4:f00:80:f7:1111::6adb/96"));
+        set.add(new IpPrefix("add4:f00:80:f7:1111::6adb/8"));
+        // The 8-length includes the 96-length prefix
+        assertEquals(BigInteger.ONE.shiftLeft(120), NetworkUtils.routedIPv6AddressCount(set));
+
+        set.add(new IpPrefix("10::26/64"));
+        // The 8-length does not include this 64-length prefix
+        assertEquals(BigInteger.ONE.shiftLeft(120).add(BigInteger.ONE.shiftLeft(64)),
+                NetworkUtils.routedIPv6AddressCount(set));
+
+        set.clear();
+        set.add(new IpPrefix("add4:f00:80:f7:1111::6ad4/128"));
+        set.add(new IpPrefix("add4:f00:80:f7:1111::6ad4/128"));
+        set.add(new IpPrefix("add4:f00:80:f7:1111::6ad4/128"));
+        set.add(new IpPrefix("add4:f00:80:f7:1111::6ad4/128"));
+        assertEquals(BigInteger.ONE, NetworkUtils.routedIPv6AddressCount(set));
+
+        set.add(new IpPrefix("add4:f00:80:f7:1111::6ad5/128"));
+        set.add(new IpPrefix("add4:f00:80:f7:1111::6ad6/128"));
+        set.add(new IpPrefix("add4:f00:80:f7:1111::6ad7/128"));
+        set.add(new IpPrefix("add4:f00:80:f7:1111::6ad8/128"));
+        set.add(new IpPrefix("add4:f00:80:f7:1111::6ad9/128"));
+        set.add(new IpPrefix("add4:f00:80:f7:1111::6ad0/128"));
+        assertEquals(BigInteger.valueOf(7), NetworkUtils.routedIPv6AddressCount(set));
+
+        // add4:f00:80:f7:1111::6ad4/126 eats add4:f00:8[:f7:1111::6ad{4-7}/128
+        set.add(new IpPrefix("add4:f00:80:f7:1111::6ad4/126"));
+        set.add(new IpPrefix("d00d:f00:80:f7:1111::6ade/124"));
+        set.add(new IpPrefix("f00b:a33::/112"));
+        assertEquals(BigInteger.valueOf(7l - 4 + 4 + 16 + 65536),
+                NetworkUtils.routedIPv6AddressCount(set));
+    }
 }
diff --git a/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java b/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java
index b14f550..fc46b9c 100644
--- a/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java
+++ b/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java
@@ -184,7 +184,7 @@
         assertStatsEntry(stats, "dummy0", 0, SET_DEFAULT, 0x0, 0L, 168L);
         assertStatsEntry(stats, "lo", 0, SET_DEFAULT, 0x0, 1288L, 1288L);
 
-        NetworkStatsFactory.noteStackedIface("v4-wlan0", null);
+        NetworkStatsFactory.clearStackedIfaces();
     }
 
     @Test
@@ -212,7 +212,7 @@
         assertStatsEntry(stats, "v4-wlan0", 10106, SET_FOREGROUND, 0x0, appRxBytesAfter, 7867488L);
         assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, rootRxBytesAfter, 647587L);
 
-        NetworkStatsFactory.noteStackedIface("v4-wlan0", null);
+        NetworkStatsFactory.clearStackedIfaces();
     }
 
     /**
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 28f8122..82b7bec 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -63,6 +63,7 @@
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -133,6 +134,7 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.connectivity.ConnectivityConstants;
 import com.android.server.connectivity.DefaultNetworkMetrics;
+import com.android.server.connectivity.DnsManager;
 import com.android.server.connectivity.IpConnectivityMetrics;
 import com.android.server.connectivity.MockableSystemProperties;
 import com.android.server.connectivity.NetworkAgentInfo;
@@ -387,6 +389,7 @@
                     mScore = 20;
                     break;
                 case TRANSPORT_VPN:
+                    mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_VPN);
                     mScore = ConnectivityConstants.VPN_DEFAULT_SCORE;
                     break;
                 default:
@@ -748,6 +751,7 @@
 
     // NetworkMonitor implementation allowing overriding of Internet connectivity probe result.
     private class WrappedNetworkMonitor extends NetworkMonitor {
+        public Handler connectivityHandler;
         // HTTP response code fed back to NetworkMonitor for Internet connectivity probe.
         public int gen204ProbeResult = 500;
         public String gen204ProbeRedirectUrl = null;
@@ -757,6 +761,7 @@
                 IpConnectivityLog log) {
             super(context, handler, networkAgentInfo, defaultRequest, log,
                     NetworkMonitor.NetworkMonitorSettings.DEFAULT);
+            connectivityHandler = handler;
         }
 
         @Override
@@ -3663,18 +3668,29 @@
 
     @Test
     public void testBasicDnsConfigurationPushed() throws Exception {
+        final String IFNAME = "test_rmnet_data0";
+        final String[] EMPTY_TLS_SERVERS = new String[0];
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         waitForIdle();
         verify(mNetworkManagementService, never()).setDnsConfigurationForNetwork(
-                anyInt(), any(), any(), any(), anyBoolean(), anyString());
+                anyInt(), any(), any(), any(), anyString(), eq(EMPTY_TLS_SERVERS));
 
         final LinkProperties cellLp = new LinkProperties();
-        cellLp.setInterfaceName("test_rmnet_data0");
+        cellLp.setInterfaceName(IFNAME);
+        // Add IPv4 and IPv6 default routes, because DNS-over-TLS code does
+        // "is-reachable" testing in order to not program netd with unreachable
+        // nameservers that it might try repeated to validate.
+        cellLp.addLinkAddress(new LinkAddress("192.0.2.4/24"));
+        cellLp.addRoute(new RouteInfo((IpPrefix) null, InetAddress.getByName("192.0.2.4"), IFNAME));
+        cellLp.addLinkAddress(new LinkAddress("2001:db8:1::1/64"));
+        cellLp.addRoute(
+                new RouteInfo((IpPrefix) null, InetAddress.getByName("2001:db8:1::1"), IFNAME));
         mCellNetworkAgent.sendLinkProperties(cellLp);
         mCellNetworkAgent.connect(false);
         waitForIdle();
         verify(mNetworkManagementService, times(1)).setDnsConfigurationForNetwork(
-                anyInt(), mStringArrayCaptor.capture(), any(), any(), anyBoolean(), anyString());
+                anyInt(), mStringArrayCaptor.capture(), any(), any(),
+                anyString(), eq(EMPTY_TLS_SERVERS));
         // CS tells netd about the empty DNS config for this network.
         assertEmpty(mStringArrayCaptor.getValue());
         reset(mNetworkManagementService);
@@ -3683,7 +3699,8 @@
         mCellNetworkAgent.sendLinkProperties(cellLp);
         waitForIdle();
         verify(mNetworkManagementService, times(1)).setDnsConfigurationForNetwork(
-                anyInt(), mStringArrayCaptor.capture(), any(), any(), anyBoolean(), anyString());
+                anyInt(), mStringArrayCaptor.capture(), any(), any(),
+                anyString(), eq(EMPTY_TLS_SERVERS));
         assertEquals(1, mStringArrayCaptor.getValue().length);
         assertTrue(ArrayUtils.contains(mStringArrayCaptor.getValue(), "2001:db8::1"));
         reset(mNetworkManagementService);
@@ -3692,7 +3709,26 @@
         mCellNetworkAgent.sendLinkProperties(cellLp);
         waitForIdle();
         verify(mNetworkManagementService, times(1)).setDnsConfigurationForNetwork(
-                anyInt(), mStringArrayCaptor.capture(), any(), any(), anyBoolean(), anyString());
+                anyInt(), mStringArrayCaptor.capture(), any(), any(),
+                anyString(), eq(EMPTY_TLS_SERVERS));
+        assertEquals(2, mStringArrayCaptor.getValue().length);
+        assertTrue(ArrayUtils.containsAll(mStringArrayCaptor.getValue(),
+                new String[]{"2001:db8::1", "192.0.2.1"}));
+        reset(mNetworkManagementService);
+
+        final String TLS_SPECIFIER = "tls.example.com";
+        final String TLS_SERVER6 = "2001:db8:53::53";
+        final InetAddress[] TLS_IPS = new InetAddress[]{ InetAddress.getByName(TLS_SERVER6) };
+        final String[] TLS_SERVERS = new String[]{ TLS_SERVER6 };
+        final Handler h = mCellNetworkAgent.getWrappedNetworkMonitor().connectivityHandler;
+        h.sendMessage(h.obtainMessage(
+                NetworkMonitor.EVENT_PRIVATE_DNS_CONFIG_RESOLVED, 0,
+                mCellNetworkAgent.getNetwork().netId,
+                new DnsManager.PrivateDnsConfig(TLS_SPECIFIER, TLS_IPS)));
+        waitForIdle();
+        verify(mNetworkManagementService, times(1)).setDnsConfigurationForNetwork(
+                anyInt(), mStringArrayCaptor.capture(), any(), any(),
+                eq(TLS_SPECIFIER), eq(TLS_SERVERS));
         assertEquals(2, mStringArrayCaptor.getValue().length);
         assertTrue(ArrayUtils.containsAll(mStringArrayCaptor.getValue(),
                 new String[]{"2001:db8::1", "192.0.2.1"}));
@@ -3744,14 +3780,19 @@
         final int uid = Process.myUid();
 
         final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback();
+        final TestNetworkCallback genericNotVpnNetworkCallback = new TestNetworkCallback();
         final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback();
         final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback();
-        final NetworkRequest genericRequest = new NetworkRequest.Builder().build();
+        final NetworkRequest genericNotVpnRequest = new NetworkRequest.Builder().build();
+        final NetworkRequest genericRequest = new NetworkRequest.Builder()
+                .removeCapability(NET_CAPABILITY_NOT_VPN).build();
         final NetworkRequest wifiRequest = new NetworkRequest.Builder()
                 .addTransportType(TRANSPORT_WIFI).build();
         final NetworkRequest vpnNetworkRequest = new NetworkRequest.Builder()
+                .removeCapability(NET_CAPABILITY_NOT_VPN)
                 .addTransportType(TRANSPORT_VPN).build();
         mCm.registerNetworkCallback(genericRequest, genericNetworkCallback);
+        mCm.registerNetworkCallback(genericNotVpnRequest, genericNotVpnNetworkCallback);
         mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);
         mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback);
 
@@ -3759,6 +3800,7 @@
         mWiFiNetworkAgent.connect(false);
 
         genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        genericNotVpnNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         vpnNetworkCallback.assertNoCallback();
 
@@ -3773,16 +3815,19 @@
         vpnNetworkAgent.connect(false);
 
         genericNetworkCallback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent);
+        genericNotVpnNetworkCallback.assertNoCallback();
         wifiNetworkCallback.assertNoCallback();
         vpnNetworkCallback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent);
 
         genericNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent);
+        genericNotVpnNetworkCallback.assertNoCallback();
         vpnNetworkCallback.expectCapabilitiesLike(nc -> null == nc.getUids(), vpnNetworkAgent);
 
         ranges.clear();
         vpnNetworkAgent.setUids(ranges);
 
         genericNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+        genericNotVpnNetworkCallback.assertNoCallback();
         wifiNetworkCallback.assertNoCallback();
         vpnNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
 
@@ -3790,18 +3835,21 @@
         vpnNetworkAgent.setUids(ranges);
 
         genericNetworkCallback.expectAvailableCallbacksValidated(vpnNetworkAgent);
+        genericNotVpnNetworkCallback.assertNoCallback();
         wifiNetworkCallback.assertNoCallback();
         vpnNetworkCallback.expectAvailableCallbacksValidated(vpnNetworkAgent);
 
         mWiFiNetworkAgent.disconnect();
 
         genericNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        genericNotVpnNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         wifiNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         vpnNetworkCallback.assertNoCallback();
 
         vpnNetworkAgent.disconnect();
 
         genericNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+        genericNotVpnNetworkCallback.assertNoCallback();
         wifiNetworkCallback.assertNoCallback();
         vpnNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
 
diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
index 66e0955..410f754 100644
--- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -17,11 +17,13 @@
 package com.android.server;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.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;
 
@@ -32,6 +34,9 @@
 import android.net.IpSecManager;
 import android.net.IpSecSpiResponse;
 import android.net.IpSecTransformResponse;
+import android.net.IpSecTunnelInterfaceResponse;
+import android.net.LinkAddress;
+import android.net.Network;
 import android.net.NetworkUtils;
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
@@ -56,10 +61,15 @@
 
     private final String mDestinationAddr;
     private final String mSourceAddr;
+    private final LinkAddress mLocalInnerAddress;
 
     @Parameterized.Parameters
     public static Collection ipSecConfigs() {
-        return Arrays.asList(new Object[][] {{"1.2.3.4", "8.8.4.4"}, {"2601::2", "2601::10"}});
+        return Arrays.asList(
+                new Object[][] {
+                {"1.2.3.4", "8.8.4.4", "10.0.1.1/24"},
+                {"2601::2", "2601::10", "2001:db8::1/64"}
+        });
     }
 
     private static final byte[] AEAD_KEY = {
@@ -86,6 +96,7 @@
     INetd mMockNetd;
     IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig;
     IpSecService mIpSecService;
+    Network fakeNetwork = new Network(0xAB);
 
     private static final IpSecAlgorithm AUTH_ALGO =
             new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4);
@@ -94,9 +105,11 @@
     private static final IpSecAlgorithm AEAD_ALGO =
             new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
 
-    public IpSecServiceParameterizedTest(String sourceAddr, String destAddr) {
+    public IpSecServiceParameterizedTest(
+            String sourceAddr, String destAddr, String localInnerAddr) {
         mSourceAddr = sourceAddr;
         mDestinationAddr = destAddr;
+        mLocalInnerAddress = new LinkAddress(localInnerAddr);
     }
 
     @Before
@@ -281,6 +294,7 @@
                         anyInt());
     }
 
+    @Test
     public void testCreateTwoTransformsWithSameSpis() throws Exception {
         IpSecConfig ipSecConfig = new IpSecConfig();
         addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
@@ -307,6 +321,30 @@
     }
 
     @Test
+    public void testReleaseOwnedSpi() throws Exception {
+        IpSecConfig ipSecConfig = new IpSecConfig();
+        addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+        addAuthAndCryptToIpSecConfig(ipSecConfig);
+
+        IpSecTransformResponse createTransformResp =
+                mIpSecService.createTransform(ipSecConfig, new Binder());
+        IpSecService.UserRecord userRecord =
+                mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid());
+        assertEquals(1, userRecord.mSpiQuotaTracker.mCurrent);
+        mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
+        verify(mMockNetd, times(0))
+                .ipSecDeleteSecurityAssociation(
+                        eq(createTransformResp.resourceId),
+                        anyString(),
+                        anyString(),
+                        eq(TEST_SPI),
+                        anyInt(),
+                        anyInt());
+        // quota is not released until the SPI is released by the Transform
+        assertEquals(1, userRecord.mSpiQuotaTracker.mCurrent);
+    }
+
+    @Test
     public void testDeleteTransform() throws Exception {
         IpSecConfig ipSecConfig = new IpSecConfig();
         addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
@@ -316,7 +354,7 @@
                 mIpSecService.createTransform(ipSecConfig, new Binder());
         mIpSecService.deleteTransform(createTransformResp.resourceId);
 
-        verify(mMockNetd)
+        verify(mMockNetd, times(1))
                 .ipSecDeleteSecurityAssociation(
                         eq(createTransformResp.resourceId),
                         anyString(),
@@ -329,6 +367,21 @@
         IpSecService.UserRecord userRecord =
                 mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid());
         assertEquals(0, userRecord.mTransformQuotaTracker.mCurrent);
+        assertEquals(1, userRecord.mSpiQuotaTracker.mCurrent);
+
+        mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
+        // Verify that ipSecDeleteSa was not called when the SPI was released because the
+        // ownedByTransform property should prevent it; (note, the called count is cumulative).
+        verify(mMockNetd, times(1))
+                .ipSecDeleteSecurityAssociation(
+                        anyInt(),
+                        anyString(),
+                        anyString(),
+                        anyInt(),
+                        anyInt(),
+                        anyInt());
+        assertEquals(0, userRecord.mSpiQuotaTracker.mCurrent);
+
         try {
             userRecord.mTransformRecords.getRefcountedResourceOrThrow(
                     createTransformResp.resourceId);
@@ -405,4 +458,103 @@
 
         verify(mMockNetd).ipSecRemoveTransportModeTransform(pfd.getFileDescriptor());
     }
+
+    private IpSecTunnelInterfaceResponse createAndValidateTunnel(
+            String localAddr, String remoteAddr) {
+        IpSecTunnelInterfaceResponse createTunnelResp =
+                mIpSecService.createTunnelInterface(
+                        mSourceAddr, mDestinationAddr, fakeNetwork, new Binder());
+
+        assertNotNull(createTunnelResp);
+        assertEquals(IpSecManager.Status.OK, createTunnelResp.status);
+        return createTunnelResp;
+    }
+
+    @Test
+    public void testCreateTunnelInterface() throws Exception {
+        IpSecTunnelInterfaceResponse createTunnelResp =
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr);
+
+        // Check that we have stored the tracking object, and retrieve it
+        IpSecService.UserRecord userRecord =
+                mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid());
+        IpSecService.RefcountedResource refcountedRecord =
+                userRecord.mTunnelInterfaceRecords.getRefcountedResourceOrThrow(
+                        createTunnelResp.resourceId);
+
+        assertEquals(1, userRecord.mTunnelQuotaTracker.mCurrent);
+        verify(mMockNetd)
+                .addVirtualTunnelInterface(
+                        eq(createTunnelResp.interfaceName),
+                        eq(mSourceAddr),
+                        eq(mDestinationAddr),
+                        anyInt(),
+                        anyInt());
+    }
+
+    @Test
+    public void testDeleteTunnelInterface() throws Exception {
+        IpSecTunnelInterfaceResponse createTunnelResp =
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr);
+
+        IpSecService.UserRecord userRecord =
+                mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid());
+
+        mIpSecService.deleteTunnelInterface(createTunnelResp.resourceId);
+
+        // Verify quota and RefcountedResource objects cleaned up
+        assertEquals(0, userRecord.mTunnelQuotaTracker.mCurrent);
+        verify(mMockNetd).removeVirtualTunnelInterface(eq(createTunnelResp.interfaceName));
+        try {
+            userRecord.mTunnelInterfaceRecords.getRefcountedResourceOrThrow(
+                    createTunnelResp.resourceId);
+            fail("Expected IllegalArgumentException on attempt to access deleted resource");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testTunnelInterfaceBinderDeath() throws Exception {
+        IpSecTunnelInterfaceResponse createTunnelResp =
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr);
+
+        IpSecService.UserRecord userRecord =
+                mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid());
+        IpSecService.RefcountedResource refcountedRecord =
+                userRecord.mTunnelInterfaceRecords.getRefcountedResourceOrThrow(
+                        createTunnelResp.resourceId);
+
+        refcountedRecord.binderDied();
+
+        // Verify quota and RefcountedResource objects cleaned up
+        assertEquals(0, userRecord.mTunnelQuotaTracker.mCurrent);
+        verify(mMockNetd).removeVirtualTunnelInterface(eq(createTunnelResp.interfaceName));
+        try {
+            userRecord.mTunnelInterfaceRecords.getRefcountedResourceOrThrow(
+                    createTunnelResp.resourceId);
+            fail("Expected IllegalArgumentException on attempt to access deleted resource");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testAddRemoveAddressFromTunnelInterface() throws Exception {
+        IpSecTunnelInterfaceResponse createTunnelResp =
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr);
+
+        mIpSecService.addAddressToTunnelInterface(createTunnelResp.resourceId, mLocalInnerAddress);
+        verify(mMockNetd)
+                .interfaceAddAddress(
+                        eq(createTunnelResp.interfaceName),
+                        eq(mLocalInnerAddress.getAddress().getHostAddress()),
+                        eq(mLocalInnerAddress.getPrefixLength()));
+
+        mIpSecService.removeAddressFromTunnelInterface(
+                createTunnelResp.resourceId, mLocalInnerAddress);
+        verify(mMockNetd)
+                .interfaceDelAddress(
+                        eq(createTunnelResp.interfaceName),
+                        eq(mLocalInnerAddress.getAddress().getHostAddress()),
+                        eq(mLocalInnerAddress.getPrefixLength()));
+    }
 }
diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
new file mode 100644
index 0000000..4a83d1b
--- /dev/null
+++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.connectivity;
+
+import static android.Manifest.permission.CHANGE_NETWORK_STATE;
+import static android.Manifest.permission.CHANGE_WIFI_STATE;
+import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
+import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
+import static android.Manifest.permission.NETWORK_STACK;
+import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PermissionMonitorTest {
+    private static final int MOCK_UID = 10001;
+    private static final String[] MOCK_PACKAGE_NAMES = new String[] { "com.foo.bar" };
+
+    @Mock private Context mContext;
+    @Mock private PackageManager mPackageManager;
+
+    private PermissionMonitor mPermissionMonitor;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.getPackagesForUid(MOCK_UID)).thenReturn(MOCK_PACKAGE_NAMES);
+        mPermissionMonitor = new PermissionMonitor(mContext, null);
+    }
+
+    private void expectPermission(String[] permissions, boolean preinstalled) throws Exception {
+        final PackageInfo packageInfo = packageInfoWithPermissions(permissions, preinstalled);
+        when(mPackageManager.getPackageInfo(MOCK_PACKAGE_NAMES[0], GET_PERMISSIONS))
+                .thenReturn(packageInfo);
+    }
+
+    private PackageInfo packageInfoWithPermissions(String[] permissions, boolean preinstalled) {
+        final PackageInfo packageInfo = new PackageInfo();
+        packageInfo.requestedPermissions = permissions;
+        packageInfo.applicationInfo = new ApplicationInfo();
+        packageInfo.applicationInfo.flags = preinstalled ? FLAG_SYSTEM : 0;
+        return packageInfo;
+    }
+
+    @Test
+    public void testHasPermission() {
+        PackageInfo app = packageInfoWithPermissions(new String[] {}, false);
+        assertFalse(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE));
+        assertFalse(mPermissionMonitor.hasPermission(app, NETWORK_STACK));
+        assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
+        assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_INTERNAL));
+
+        app = packageInfoWithPermissions(new String[] {
+                CHANGE_NETWORK_STATE, NETWORK_STACK
+            }, false);
+        assertTrue(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE));
+        assertTrue(mPermissionMonitor.hasPermission(app, NETWORK_STACK));
+        assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
+        assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_INTERNAL));
+
+        app = packageInfoWithPermissions(new String[] {
+                CONNECTIVITY_USE_RESTRICTED_NETWORKS, CONNECTIVITY_INTERNAL
+            }, false);
+        assertFalse(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE));
+        assertFalse(mPermissionMonitor.hasPermission(app, NETWORK_STACK));
+        assertTrue(mPermissionMonitor.hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
+        assertTrue(mPermissionMonitor.hasPermission(app, CONNECTIVITY_INTERNAL));
+    }
+
+    @Test
+    public void testIsPreinstalledSystemApp() {
+        PackageInfo app = packageInfoWithPermissions(new String[] {}, false);
+        assertFalse(mPermissionMonitor.isPreinstalledSystemApp(app));
+
+        app = packageInfoWithPermissions(new String[] {}, true);
+        assertTrue(mPermissionMonitor.isPreinstalledSystemApp(app));
+    }
+
+    @Test
+    public void testHasUseBackgroundNetworksPermission() throws Exception {
+        expectPermission(new String[] { CHANGE_NETWORK_STATE }, false);
+        assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID));
+
+        expectPermission(new String[] { NETWORK_STACK, CONNECTIVITY_INTERNAL }, false);
+        assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID));
+
+        // TODO : make this false when b/31479477 is fixed
+        expectPermission(new String[] {}, true);
+        assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID));
+        expectPermission(new String[] { CHANGE_WIFI_STATE }, true);
+        assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID));
+
+        expectPermission(new String[] { NETWORK_STACK, CONNECTIVITY_INTERNAL }, true);
+        assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID));
+
+        expectPermission(new String[] {}, false);
+        assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID));
+
+        expectPermission(new String[] { CHANGE_WIFI_STATE }, false);
+        assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID));
+    }
+}
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 1dbf9b2..f59850d 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -57,9 +57,13 @@
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
+import android.net.IConnectivityManager;
+import android.net.IpPrefix;
+import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo.DetailedState;
+import android.net.RouteInfo;
 import android.net.UidRange;
 import android.net.VpnService;
 import android.os.Build.VERSION_CODES;
@@ -90,7 +94,8 @@
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
-
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * Tests for {@link Vpn}.
@@ -563,4 +568,75 @@
             return networks.get(network);
         }).when(mConnectivityManager).getNetworkCapabilities(any());
     }
+
+    // Need multiple copies of this, but Java's Stream objects can't be reused or
+    // duplicated.
+    private Stream<String> publicIpV4Routes() {
+        return Stream.of(
+                "0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4",
+                "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6",
+                "172.0.0.0/12", "172.32.0.0/11", "172.64.0.0/10", "172.128.0.0/9",
+                "173.0.0.0/8", "174.0.0.0/7", "176.0.0.0/4", "192.0.0.0/9", "192.128.0.0/11",
+                "192.160.0.0/13", "192.169.0.0/16", "192.170.0.0/15", "192.172.0.0/14",
+                "192.176.0.0/12", "192.192.0.0/10", "193.0.0.0/8", "194.0.0.0/7",
+                "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4");
+    }
+
+    private Stream<String> publicIpV6Routes() {
+        return Stream.of(
+                "::/1", "8000::/2", "c000::/3", "e000::/4", "f000::/5", "f800::/6",
+                "fe00::/8", "2605:ef80:e:af1d::/64");
+    }
+
+    @Test
+    public void testProvidesRoutesToMostDestinations() {
+        final LinkProperties lp = new LinkProperties();
+
+        // Default route provides routes to all IPv4 destinations.
+        lp.addRoute(new RouteInfo(new IpPrefix("0.0.0.0/0")));
+        assertTrue(Vpn.providesRoutesToMostDestinations(lp));
+
+        // Empty LP provides routes to no destination
+        lp.clear();
+        assertFalse(Vpn.providesRoutesToMostDestinations(lp));
+
+        // All IPv4 routes except for local networks. This is the case most relevant
+        // to this function. It provides routes to almost the entire space.
+        // (clone the stream so that we can reuse it later)
+        publicIpV4Routes().forEach(s -> lp.addRoute(new RouteInfo(new IpPrefix(s))));
+        assertTrue(Vpn.providesRoutesToMostDestinations(lp));
+
+        // Removing a 16-bit prefix, which is 65536 addresses. This is still enough to
+        // provide routes to "most" destinations.
+        lp.removeRoute(new RouteInfo(new IpPrefix("192.169.0.0/16")));
+        assertTrue(Vpn.providesRoutesToMostDestinations(lp));
+
+        // Remove the /2 route, which represent a quarter of the available routing space.
+        // This LP does not provides routes to "most" destinations any more.
+        lp.removeRoute(new RouteInfo(new IpPrefix("64.0.0.0/2")));
+        assertFalse(Vpn.providesRoutesToMostDestinations(lp));
+
+        lp.clear();
+        publicIpV6Routes().forEach(s -> lp.addRoute(new RouteInfo(new IpPrefix(s))));
+        assertTrue(Vpn.providesRoutesToMostDestinations(lp));
+
+        lp.removeRoute(new RouteInfo(new IpPrefix("::/1")));
+        assertFalse(Vpn.providesRoutesToMostDestinations(lp));
+
+        // V6 does not provide sufficient coverage but v4 does
+        publicIpV4Routes().forEach(s -> lp.addRoute(new RouteInfo(new IpPrefix(s))));
+        assertTrue(Vpn.providesRoutesToMostDestinations(lp));
+
+        // V4 still does
+        lp.removeRoute(new RouteInfo(new IpPrefix("192.169.0.0/16")));
+        assertTrue(Vpn.providesRoutesToMostDestinations(lp));
+
+        // V4 does not any more
+        lp.removeRoute(new RouteInfo(new IpPrefix("64.0.0.0/2")));
+        assertFalse(Vpn.providesRoutesToMostDestinations(lp));
+
+        // V4 does not, but V6 has sufficient coverage again
+        lp.addRoute(new RouteInfo(new IpPrefix("::/1")));
+        assertTrue(Vpn.providesRoutesToMostDestinations(lp));
+    }
 }
diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index 47c3455..17ca651 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -25,6 +25,7 @@
 import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
 import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
 import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.INTERFACES_ALL;
 import static android.net.NetworkStats.METERED_ALL;
 import static android.net.NetworkStats.METERED_NO;
 import static android.net.NetworkStats.METERED_YES;
@@ -58,6 +59,9 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -95,6 +99,7 @@
 import android.util.TrustedTime;
 
 import com.android.internal.net.VpnInfo;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
@@ -668,6 +673,94 @@
     }
 
     @Test
+    public void testDetailedUidStats() throws Exception {
+        // pretend that network comes online
+        expectDefaultSettings();
+        expectNetworkState(buildWifiState());
+        expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(buildEmptyStats());
+        expectBandwidthControlCheck();
+
+        mService.forceUpdateIfaces(NETWORKS_WIFI);
+
+        NetworkStats.Entry entry1 = new NetworkStats.Entry(
+                TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L);
+        NetworkStats.Entry entry2 = new NetworkStats.Entry(
+                TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 50L, 5L, 50L, 5L, 0L);
+        NetworkStats.Entry entry3 = new NetworkStats.Entry(
+                TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xBEEF, 1024L, 8L, 512L, 4L, 0L);
+
+        incrementCurrentTime(HOUR_IN_MILLIS);
+        expectDefaultSettings();
+        expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
+                .addValues(entry1)
+                .addValues(entry2)
+                .addValues(entry3));
+        mService.incrementOperationCount(UID_RED, 0xF00D, 1);
+
+        NetworkStats stats = mService.getDetailedUidStats(INTERFACES_ALL);
+
+        assertEquals(3, stats.size());
+        entry1.operations = 1;
+        assertEquals(entry1, stats.getValues(0, null));
+        entry2.operations = 1;
+        assertEquals(entry2, stats.getValues(1, null));
+        assertEquals(entry3, stats.getValues(2, null));
+    }
+
+    @Test
+    public void testDetailedUidStats_Filtered() throws Exception {
+        // pretend that network comes online
+        expectDefaultSettings();
+
+        final String stackedIface = "stacked-test0";
+        final LinkProperties stackedProp = new LinkProperties();
+        stackedProp.setInterfaceName(stackedIface);
+        final NetworkState wifiState = buildWifiState();
+        wifiState.linkProperties.addStackedLink(stackedProp);
+        expectNetworkState(wifiState);
+
+        expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(buildEmptyStats());
+        expectBandwidthControlCheck();
+
+        mService.forceUpdateIfaces(NETWORKS_WIFI);
+
+        NetworkStats.Entry uidStats = new NetworkStats.Entry(
+                TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xF00D, 1024L, 8L, 512L, 4L, 0L);
+        // Stacked on matching interface
+        NetworkStats.Entry tetheredStats1 = new NetworkStats.Entry(
+                stackedIface, UID_BLUE, SET_DEFAULT, 0xF00D, 1024L, 8L, 512L, 4L, 0L);
+        // Different interface
+        NetworkStats.Entry tetheredStats2 = new NetworkStats.Entry(
+                "otherif", UID_BLUE, SET_DEFAULT, 0xF00D, 1024L, 8L, 512L, 4L, 0L);
+
+        final String[] ifaceFilter = new String[] { TEST_IFACE };
+        incrementCurrentTime(HOUR_IN_MILLIS);
+        expectDefaultSettings();
+        expectNetworkStatsSummary(buildEmptyStats());
+        when(mNetManager.getNetworkStatsUidDetail(eq(UID_ALL), any()))
+                .thenReturn(new NetworkStats(getElapsedRealtime(), 1)
+                        .addValues(uidStats));
+        when(mNetManager.getNetworkStatsTethering(STATS_PER_UID))
+                .thenReturn(new NetworkStats(getElapsedRealtime(), 2)
+                        .addValues(tetheredStats1)
+                        .addValues(tetheredStats2));
+
+        NetworkStats stats = mService.getDetailedUidStats(ifaceFilter);
+
+        verify(mNetManager, times(1)).getNetworkStatsUidDetail(eq(UID_ALL), argThat(ifaces ->
+                ifaces != null && ifaces.length == 2
+                        && ArrayUtils.contains(ifaces, TEST_IFACE)
+                        && ArrayUtils.contains(ifaces, stackedIface)));
+
+        assertEquals(2, stats.size());
+        assertEquals(uidStats, stats.getValues(0, null));
+        assertEquals(tetheredStats1, stats.getValues(1, null));
+    }
+
+    @Test
     public void testForegroundBackground() throws Exception {
         // pretend that network comes online
         expectCurrentTime();
@@ -1056,7 +1149,7 @@
 
     private void expectNetworkStatsUidDetail(NetworkStats detail, NetworkStats tetherStats)
             throws Exception {
-        when(mNetManager.getNetworkStatsUidDetail(UID_ALL)).thenReturn(detail);
+        when(mNetManager.getNetworkStatsUidDetail(UID_ALL, INTERFACES_ALL)).thenReturn(detail);
 
         // also include tethering details, since they are folded into UID
         when(mNetManager.getNetworkStatsTethering(STATS_PER_UID)).thenReturn(tetherStats);