DO NOT MERGE ANYWHERE ConnectivityService: move reportNetworkConnectivity to handler am: 1681f064ff  -s ours
am: 552b3aae6e  -s ours

Change-Id: I6d788c5b7cd5c6877799e5a3e0ad865996147b70
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 2b5afa7..e64cbcd 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -19,9 +19,11 @@
 
 import android.annotation.IntDef;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
+import android.annotation.SystemService;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
@@ -46,6 +48,7 @@
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 
 import com.android.internal.telephony.ITelephony;
 import com.android.internal.telephony.PhoneConstants;
@@ -62,9 +65,7 @@
 
 /**
  * Class that answers queries about the state of network connectivity. It also
- * notifies applications when network connectivity changes. Get an instance
- * of this class by calling
- * {@link android.content.Context#getSystemService(String) Context.getSystemService(Context.CONNECTIVITY_SERVICE)}.
+ * notifies applications when network connectivity changes.
  * <p>
  * The primary responsibilities of this class are to:
  * <ol>
@@ -78,6 +79,7 @@
  * traffic</li>
  * </ol>
  */
+@SystemService(Context.CONNECTIVITY_SERVICE)
 public class ConnectivityManager {
     private static final String TAG = "ConnectivityManager";
 
@@ -300,7 +302,8 @@
     /**
      * Broadcast Action: A tetherable connection has come or gone.
      * Uses {@code ConnectivityManager.EXTRA_AVAILABLE_TETHER},
-     * {@code ConnectivityManager.EXTRA_ACTIVE_TETHER} and
+     * {@code ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY},
+     * {@code ConnectivityManager.EXTRA_ACTIVE_TETHER}, and
      * {@code ConnectivityManager.EXTRA_ERRORED_TETHER} to indicate
      * the current state of tethering.  Each include a list of
      * interface names in that state (may be empty).
@@ -319,10 +322,17 @@
 
     /**
      * @hide
-     * gives a String[] listing all the interfaces currently tethered
-     * (ie, has dhcp support and packets potentially forwarded/NATed)
+     * gives a String[] listing all the interfaces currently in local-only
+     * mode (ie, has DHCPv4+IPv6-ULA support and no packet forwarding)
      */
-    public static final String EXTRA_ACTIVE_TETHER = "activeArray";
+    public static final String EXTRA_ACTIVE_LOCAL_ONLY = "localOnlyArray";
+
+    /**
+     * @hide
+     * gives a String[] listing all the interfaces currently tethered
+     * (ie, has DHCPv4 support and packets potentially forwarded/NATed)
+     */
+    public static final String EXTRA_ACTIVE_TETHER = "tetherArray";
 
     /**
      * @hide
@@ -458,6 +468,7 @@
      *         {@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
@@ -469,6 +480,7 @@
      *         {@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
@@ -486,6 +498,7 @@
      *         {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that
      *         uses the {@link NetworkCapabilities#TRANSPORT_CELLULAR} transport.
      */
+    @Deprecated
     public static final int TYPE_MOBILE_HIPRI = 5;
     /**
      * The WiMAX data connection.  When active, all data traffic
@@ -616,6 +629,7 @@
      * @deprecated All APIs accepting a network type are deprecated. There should be no need to
      *             validate a network type.
      */
+    @Deprecated
     public static boolean isNetworkTypeValid(int networkType) {
         return networkType >= 0 && networkType <= MAX_NETWORK_TYPE;
     }
@@ -723,13 +737,12 @@
      *             preference.  Instead we use dynamic network properties of
      *             the networks to describe their precedence.
      */
+    @Deprecated
     public void setNetworkPreference(int preference) {
     }
 
     /**
      * Retrieves the current preferred network type.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      *
      * @return an integer representing the preferred network type
      *
@@ -738,6 +751,8 @@
      *             preference.  Instead we use dynamic network properties of
      *             the networks to describe their precedence.
      */
+    @Deprecated
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public int getNetworkPreference() {
         return TYPE_NONE;
     }
@@ -748,12 +763,11 @@
      * You should always check {@link NetworkInfo#isConnected()} before initiating
      * network traffic. This may return {@code null} when there is no default
      * network.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      *
      * @return a {@link NetworkInfo} object for the current default network
      *        or {@code null} if no default network is currently active
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public NetworkInfo getActiveNetworkInfo() {
         try {
             return mService.getActiveNetworkInfo();
@@ -768,12 +782,11 @@
      * network disconnects, the returned {@code Network} object will no longer
      * be usable.  This will return {@code null} when there is no default
      * network.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      *
      * @return a {@link Network} object for the current default network or
      *        {@code null} if no default network is currently active
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public Network getActiveNetwork() {
         try {
             return mService.getActiveNetwork();
@@ -788,14 +801,13 @@
      * network disconnects, the returned {@code Network} object will no longer
      * be usable.  This will return {@code null} when there is no default
      * network for the UID.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}.
      *
      * @return a {@link Network} object for the current default network for the
      *         given UID or {@code null} if no default network is currently active
      *
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL)
     public Network getActiveNetworkForUid(int uid) {
         return getActiveNetworkForUid(uid, false);
     }
@@ -856,8 +868,6 @@
      * Returns details about the currently active default data network
      * for a given uid.  This is for internal use only to avoid spying
      * other apps.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}
      *
      * @return a {@link NetworkInfo} object for the current default network
      *        for the given uid or {@code null} if no default network is
@@ -865,6 +875,7 @@
      *
      * {@hide}
      */
+    @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL)
     public NetworkInfo getActiveNetworkInfoForUid(int uid) {
         return getActiveNetworkInfoForUid(uid, false);
     }
@@ -881,8 +892,6 @@
     /**
      * Returns connection status information about a particular
      * network type.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      *
      * @param networkType integer specifying which networkType in
      *        which you're interested.
@@ -894,6 +903,8 @@
      *             of the same type. Use {@link #getAllNetworks} and
      *             {@link #getNetworkInfo(android.net.Network)} instead.
      */
+    @Deprecated
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public NetworkInfo getNetworkInfo(int networkType) {
         try {
             return mService.getNetworkInfo(networkType);
@@ -905,8 +916,6 @@
     /**
      * Returns connection status information about a particular
      * Network.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      *
      * @param network {@link Network} specifying which network
      *        in which you're interested.
@@ -914,6 +923,7 @@
      *        network or {@code null} if the {@code Network}
      *        is not valid.
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public NetworkInfo getNetworkInfo(Network network) {
         return getNetworkInfoForUid(network, Process.myUid(), false);
     }
@@ -930,8 +940,6 @@
     /**
      * Returns connection status information about all network
      * types supported by the device.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      *
      * @return an array of {@link NetworkInfo} objects.  Check each
      * {@link NetworkInfo#getType} for which type each applies.
@@ -940,6 +948,8 @@
      *             of the same type. Use {@link #getAllNetworks} and
      *             {@link #getNetworkInfo(android.net.Network)} instead.
      */
+    @Deprecated
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public NetworkInfo[] getAllNetworkInfo() {
         try {
             return mService.getAllNetworkInfo();
@@ -952,14 +962,13 @@
      * Returns the {@link Network} object currently serving a given type, or
      * null if the given type is not connected.
      *
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
-     *
      * @hide
      * @deprecated This method does not support multiple connected networks
      *             of the same type. Use {@link #getAllNetworks} and
      *             {@link #getNetworkInfo(android.net.Network)} instead.
      */
+    @Deprecated
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public Network getNetworkForType(int networkType) {
         try {
             return mService.getNetworkForType(networkType);
@@ -971,11 +980,10 @@
     /**
      * Returns an array of all {@link Network} currently tracked by the
      * framework.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      *
      * @return an array of {@link Network} objects.
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public Network[] getAllNetworks() {
         try {
             return mService.getAllNetworks();
@@ -999,8 +1007,6 @@
 
     /**
      * Returns the IP information for the current default network.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      *
      * @return a {@link LinkProperties} object describing the IP info
      *        for the current default network, or {@code null} if there
@@ -1008,6 +1014,7 @@
      *
      * {@hide}
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public LinkProperties getActiveLinkProperties() {
         try {
             return mService.getActiveLinkProperties();
@@ -1018,8 +1025,6 @@
 
     /**
      * Returns the IP information for a given network type.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      *
      * @param networkType the network type of interest.
      * @return a {@link LinkProperties} object describing the IP info
@@ -1032,6 +1037,8 @@
      *             {@link #getNetworkInfo(android.net.Network)}, and
      *             {@link #getLinkProperties(android.net.Network)} instead.
      */
+    @Deprecated
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public LinkProperties getLinkProperties(int networkType) {
         try {
             return mService.getLinkPropertiesForType(networkType);
@@ -1043,12 +1050,11 @@
     /**
      * Get the {@link LinkProperties} for the given {@link Network}.  This
      * will return {@code null} if the network is unknown.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      *
      * @param network The {@link Network} object identifying the network in question.
      * @return The {@link LinkProperties} for the network, or {@code null}.
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public LinkProperties getLinkProperties(Network network) {
         try {
             return mService.getLinkProperties(network);
@@ -1058,34 +1064,13 @@
     }
 
     /**
-     * Request that this callback be invoked at ConnectivityService's earliest
-     * convenience with the current satisfying network's LinkProperties.
-     * If no such network exists no callback invocation is performed.
-     *
-     * The callback must have been registered with #requestNetwork() or
-     * #registerDefaultNetworkCallback(); callbacks registered with
-     * registerNetworkCallback() are not specific to any particular Network so
-     * do not cause any updates.
-     *
-     * @hide
-     */
-    public void requestLinkProperties(NetworkCallback networkCallback) {
-        try {
-            mService.requestLinkProperties(networkCallback.networkRequest);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Get the {@link android.net.NetworkCapabilities} for the given {@link Network}.  This
      * will return {@code null} if the network is unknown.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      *
      * @param network The {@link Network} object identifying the network in question.
      * @return The {@link android.net.NetworkCapabilities} for the network, or {@code null}.
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public NetworkCapabilities getNetworkCapabilities(Network network) {
         try {
             return mService.getNetworkCapabilities(network);
@@ -1095,26 +1080,6 @@
     }
 
     /**
-     * Request that this callback be invoked at ConnectivityService's earliest
-     * convenience with the current satisfying network's NetworkCapabilities.
-     * If no such network exists no callback invocation is performed.
-     *
-     * The callback must have been registered with #requestNetwork() or
-     * #registerDefaultNetworkCallback(); callbacks registered with
-     * registerNetworkCallback() are not specific to any particular Network so
-     * do not cause any updates.
-     *
-     * @hide
-     */
-    public void requestNetworkCapabilities(NetworkCallback networkCallback) {
-        try {
-            mService.requestNetworkCapabilities(networkCallback.networkRequest);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Gets the URL that should be used for resolving whether a captive portal is present.
      * 1. This URL should respond with a 204 response to a GET request to indicate no captive
      *    portal is present.
@@ -1153,7 +1118,9 @@
      *             {@link #requestNetwork(NetworkRequest, NetworkCallback)} API.
      *             In {@link VERSION_CODES#M}, and above, this method is unsupported and will
      *             throw {@code UnsupportedOperationException} if called.
+     * @removed
      */
+    @Deprecated
     public int startUsingNetworkFeature(int networkType, String feature) {
         checkLegacyRoutingApiAccess();
         NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature);
@@ -1208,7 +1175,9 @@
      *             {@link #unregisterNetworkCallback(NetworkCallback)} API.
      *             In {@link VERSION_CODES#M}, and above, this method is unsupported and will
      *             throw {@code UnsupportedOperationException} if called.
+     * @removed
      */
+    @Deprecated
     public int stopUsingNetworkFeature(int networkType, String feature) {
         checkLegacyRoutingApiAccess();
         NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature);
@@ -1226,36 +1195,27 @@
 
     private NetworkCapabilities networkCapabilitiesForFeature(int networkType, String feature) {
         if (networkType == TYPE_MOBILE) {
-            int cap = -1;
-            if ("enableMMS".equals(feature)) {
-                cap = NetworkCapabilities.NET_CAPABILITY_MMS;
-            } else if ("enableSUPL".equals(feature)) {
-                cap = NetworkCapabilities.NET_CAPABILITY_SUPL;
-            } else if ("enableDUN".equals(feature) || "enableDUNAlways".equals(feature)) {
-                cap = NetworkCapabilities.NET_CAPABILITY_DUN;
-            } else if ("enableHIPRI".equals(feature)) {
-                cap = NetworkCapabilities.NET_CAPABILITY_INTERNET;
-            } else if ("enableFOTA".equals(feature)) {
-                cap = NetworkCapabilities.NET_CAPABILITY_FOTA;
-            } else if ("enableIMS".equals(feature)) {
-                cap = NetworkCapabilities.NET_CAPABILITY_IMS;
-            } else if ("enableCBS".equals(feature)) {
-                cap = NetworkCapabilities.NET_CAPABILITY_CBS;
-            } else {
-                return null;
+            switch (feature) {
+                case "enableCBS":
+                    return networkCapabilitiesForType(TYPE_MOBILE_CBS);
+                case "enableDUN":
+                case "enableDUNAlways":
+                    return networkCapabilitiesForType(TYPE_MOBILE_DUN);
+                case "enableFOTA":
+                    return networkCapabilitiesForType(TYPE_MOBILE_FOTA);
+                case "enableHIPRI":
+                    return networkCapabilitiesForType(TYPE_MOBILE_HIPRI);
+                case "enableIMS":
+                    return networkCapabilitiesForType(TYPE_MOBILE_IMS);
+                case "enableMMS":
+                    return networkCapabilitiesForType(TYPE_MOBILE_MMS);
+                case "enableSUPL":
+                    return networkCapabilitiesForType(TYPE_MOBILE_SUPL);
+                default:
+                    return null;
             }
-            NetworkCapabilities netCap = new NetworkCapabilities();
-            netCap.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR).addCapability(cap);
-            netCap.maybeMarkCapabilitiesRestricted();
-            return netCap;
-        } else if (networkType == TYPE_WIFI) {
-            if ("p2p".equals(feature)) {
-                NetworkCapabilities netCap = new NetworkCapabilities();
-                netCap.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
-                netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_WIFI_P2P);
-                netCap.maybeMarkCapabilitiesRestricted();
-                return netCap;
-            }
+        } else if (networkType == TYPE_WIFI && "p2p".equals(feature)) {
+            return networkCapabilitiesForType(TYPE_WIFI_P2P);
         }
         return null;
     }
@@ -1436,8 +1396,8 @@
         l.networkCapabilities = netCap;
         l.delay = delay;
         l.expireSequenceNumber = 0;
-        l.networkRequest = sendRequestForNetwork(netCap, l.networkCallback, 0,
-                REQUEST, type);
+        l.networkRequest = sendRequestForNetwork(
+                netCap, l.networkCallback, 0, REQUEST, type, getDefaultHandler());
         if (l.networkRequest == null) return null;
         sLegacyRequests.put(netCap, l);
         sendExpireMsgForFeature(netCap, l.expireSequenceNumber, delay);
@@ -1447,8 +1407,9 @@
     private void sendExpireMsgForFeature(NetworkCapabilities netCap, int seqNum, int delay) {
         if (delay >= 0) {
             Log.d(TAG, "sending expire msg with seqNum " + seqNum + " and delay " + delay);
-            Message msg = sCallbackHandler.obtainMessage(EXPIRE_LEGACY_REQUEST, seqNum, 0, netCap);
-            sCallbackHandler.sendMessageDelayed(msg, delay);
+            CallbackHandler handler = getDefaultHandler();
+            Message msg = handler.obtainMessage(EXPIRE_LEGACY_REQUEST, seqNum, 0, netCap);
+            handler.sendMessageDelayed(msg, delay);
         }
     }
 
@@ -1463,6 +1424,59 @@
         return true;
     }
 
+    private static final SparseIntArray sLegacyTypeToTransport = new SparseIntArray();
+    static {
+        sLegacyTypeToTransport.put(TYPE_MOBILE,       NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_MOBILE_CBS,   NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_MOBILE_DUN,   NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_MOBILE_FOTA,  NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_MOBILE_HIPRI, NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_MOBILE_IMS,   NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_MOBILE_MMS,   NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_MOBILE_SUPL,  NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_WIFI,         NetworkCapabilities.TRANSPORT_WIFI);
+        sLegacyTypeToTransport.put(TYPE_WIFI_P2P,     NetworkCapabilities.TRANSPORT_WIFI);
+        sLegacyTypeToTransport.put(TYPE_BLUETOOTH,    NetworkCapabilities.TRANSPORT_BLUETOOTH);
+        sLegacyTypeToTransport.put(TYPE_ETHERNET,     NetworkCapabilities.TRANSPORT_ETHERNET);
+    }
+
+    private static final SparseIntArray sLegacyTypeToCapability = new SparseIntArray();
+    static {
+        sLegacyTypeToCapability.put(TYPE_MOBILE_CBS,  NetworkCapabilities.NET_CAPABILITY_CBS);
+        sLegacyTypeToCapability.put(TYPE_MOBILE_DUN,  NetworkCapabilities.NET_CAPABILITY_DUN);
+        sLegacyTypeToCapability.put(TYPE_MOBILE_FOTA, NetworkCapabilities.NET_CAPABILITY_FOTA);
+        sLegacyTypeToCapability.put(TYPE_MOBILE_IMS,  NetworkCapabilities.NET_CAPABILITY_IMS);
+        sLegacyTypeToCapability.put(TYPE_MOBILE_MMS,  NetworkCapabilities.NET_CAPABILITY_MMS);
+        sLegacyTypeToCapability.put(TYPE_MOBILE_SUPL, NetworkCapabilities.NET_CAPABILITY_SUPL);
+        sLegacyTypeToCapability.put(TYPE_WIFI_P2P,    NetworkCapabilities.NET_CAPABILITY_WIFI_P2P);
+    }
+
+    /**
+     * Given a legacy type (TYPE_WIFI, ...) returns a NetworkCapabilities
+     * instance suitable for registering a request or callback.  Throws an
+     * IllegalArgumentException if no mapping from the legacy type to
+     * NetworkCapabilities is known.
+     *
+     * @hide
+     */
+    public static NetworkCapabilities networkCapabilitiesForType(int type) {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+
+        // Map from type to transports.
+        final int NOT_FOUND = -1;
+        final int transport = sLegacyTypeToTransport.get(type, NOT_FOUND);
+        if (transport == NOT_FOUND) {
+            throw new IllegalArgumentException("unknown legacy type: " + type);
+        }
+        nc.addTransportType(transport);
+
+        // Map from type to capabilities.
+        nc.addCapability(sLegacyTypeToCapability.get(
+                type, NetworkCapabilities.NET_CAPABILITY_INTERNET));
+        nc.maybeMarkCapabilitiesRestricted();
+        return nc;
+    }
+
     /** @hide */
     public static class PacketKeepaliveCallback {
         /** The requested keepalive was successfully started. */
@@ -1621,7 +1635,9 @@
      *             {@link #bindProcessToNetwork} and {@link Network#getSocketFactory} API.
      *             In {@link VERSION_CODES#M}, and above, this method is unsupported and will
      *             throw {@code UnsupportedOperationException} if called.
+     * @removed
      */
+    @Deprecated
     public boolean requestRouteToHost(int networkType, int hostAddress) {
         return requestRouteToHostAddress(networkType, NetworkUtils.intToInetAddress(hostAddress));
     }
@@ -1644,6 +1660,7 @@
      * @deprecated Deprecated in favor of the {@link #requestNetwork} and
      *             {@link #bindProcessToNetwork} API.
      */
+    @Deprecated
     public boolean requestRouteToHostAddress(int networkType, InetAddress hostAddress) {
         checkLegacyRoutingApiAccess();
         try {
@@ -1696,11 +1713,9 @@
      * network is active. Quota status can change rapidly, so these values
      * shouldn't be cached.
      *
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
-     *
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public NetworkQuotaInfo getActiveNetworkQuotaInfo() {
         try {
             return mService.getActiveNetworkQuotaInfo();
@@ -1713,6 +1728,7 @@
      * @hide
      * @deprecated Talk to TelephonyManager directly
      */
+    @Deprecated
     public boolean getMobileDataEnabled() {
         IBinder b = ServiceManager.getService(Context.TELEPHONY_SERVICE);
         if (b != null) {
@@ -1876,6 +1892,7 @@
      *               situations where a Context pointer is unavailable.
      * @hide
      */
+    @Deprecated
     static ConnectivityManager getInstanceOrNull() {
         return sInstance;
     }
@@ -1885,6 +1902,7 @@
      *               situations where a Context pointer is unavailable.
      * @hide
      */
+    @Deprecated
     private static ConnectivityManager getInstance() {
         if (getInstanceOrNull() == null) {
             throw new IllegalStateException("No ConnectivityManager yet constructed");
@@ -1895,13 +1913,12 @@
     /**
      * Get the set of tetherable, available interfaces.  This list is limited by
      * device configuration and current interface existence.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      *
      * @return an array of 0 or more Strings of tetherable interface names.
      *
      * {@hide}
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public String[] getTetherableIfaces() {
         try {
             return mService.getTetherableIfaces();
@@ -1912,13 +1929,12 @@
 
     /**
      * Get the set of tethered interfaces.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      *
      * @return an array of 0 or more String of currently tethered interface names.
      *
      * {@hide}
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public String[] getTetheredIfaces() {
         try {
             return mService.getTetheredIfaces();
@@ -1934,14 +1950,13 @@
      * may cause them to reset to the available state.
      * {@link ConnectivityManager#getLastTetherError} can be used to get more
      * information on the cause of the errors.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      *
      * @return an array of 0 or more String indicating the interface names
      *        which failed to tether.
      *
      * {@hide}
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public String[] getTetheringErroredIfaces() {
         try {
             return mService.getTetheringErroredIfaces();
@@ -2026,14 +2041,13 @@
      * Check if the device allows for tethering.  It may be disabled via
      * {@code ro.tether.denied} system property, Settings.TETHER_SUPPORTED or
      * due to device configuration.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      *
      * @return a boolean - {@code true} indicating Tethering is supported.
      *
      * {@hide}
      */
     @SystemApi
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public boolean isTetheringSupported() {
         try {
             return mService.isTetheringSupported();
@@ -2091,8 +2105,11 @@
      * @hide
      */
     @SystemApi
+    @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
     public void startTethering(int type, boolean showProvisioningUi,
             final OnStartTetheringCallback callback, Handler handler) {
+        checkNotNull(callback, "OnStartTetheringCallback cannot be null.");
+
         ResultReceiver wrappedCallback = new ResultReceiver(handler) {
             @Override
             protected void onReceiveResult(int resultCode, Bundle resultData) {
@@ -2103,6 +2120,7 @@
                 }
             }
         };
+
         try {
             mService.startTethering(type, wrappedCallback, showProvisioningUi);
         } catch (RemoteException e) {
@@ -2122,6 +2140,7 @@
      * @hide
      */
     @SystemApi
+    @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
     public void stopTethering(int type) {
         try {
             mService.stopTethering(type);
@@ -2134,14 +2153,13 @@
      * Get the list of regular expressions that define any tetherable
      * USB network interfaces.  If USB tethering is not supported by the
      * device, this list should be empty.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      *
      * @return an array of 0 or more regular expression Strings defining
      *        what interfaces are considered tetherable usb interfaces.
      *
      * {@hide}
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public String[] getTetherableUsbRegexs() {
         try {
             return mService.getTetherableUsbRegexs();
@@ -2154,14 +2172,13 @@
      * Get the list of regular expressions that define any tetherable
      * Wifi network interfaces.  If Wifi tethering is not supported by the
      * device, this list should be empty.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      *
      * @return an array of 0 or more regular expression Strings defining
      *        what interfaces are considered tetherable wifi interfaces.
      *
      * {@hide}
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public String[] getTetherableWifiRegexs() {
         try {
             return mService.getTetherableWifiRegexs();
@@ -2174,14 +2191,13 @@
      * Get the list of regular expressions that define any tetherable
      * Bluetooth network interfaces.  If Bluetooth tethering is not supported by the
      * device, this list should be empty.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      *
      * @return an array of 0 or more regular expression Strings defining
      *        what interfaces are considered tetherable bluetooth interfaces.
      *
      * {@hide}
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public String[] getTetherableBluetoothRegexs() {
         try {
             return mService.getTetherableBluetoothRegexs();
@@ -2243,8 +2259,6 @@
     /**
      * Get a more detailed error code after a Tethering or Untethering
      * request asynchronously failed.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      *
      * @param iface The name of the interface of interest
      * @return error The error code of the last error tethering or untethering the named
@@ -2252,6 +2266,7 @@
      *
      * {@hide}
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public int getLastTetherError(String iface) {
         try {
             return mService.getLastTetherError(iface);
@@ -2289,6 +2304,7 @@
      * @deprecated Use {@link #reportNetworkConnectivity} which allows reporting both
      *             working and non-working connectivity.
      */
+    @Deprecated
     public void reportBadNetwork(Network network) {
         try {
             // One of these will be ignored because it matches system's current state.
@@ -2324,13 +2340,12 @@
      * for typical HTTP proxies - they are general network dependent.  However if you're
      * doing something unusual like general internal filtering this may be useful.  On
      * a private network where the proxy is not accessible, you may break HTTP using this.
-     * <p>This method requires the caller to hold the permission
-     * android.Manifest.permission#CONNECTIVITY_INTERNAL.
      *
      * @param p A {@link ProxyInfo} object defining the new global
      *        HTTP proxy.  A {@code null} value will clear the global HTTP proxy.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL)
     public void setGlobalProxy(ProxyInfo p) {
         try {
             mService.setGlobalProxy(p);
@@ -2396,14 +2411,13 @@
      * hardware supports it.  For example a GSM phone without a SIM
      * should still return {@code true} for mobile data, but a wifi only
      * tablet would return {@code false}.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      *
      * @param networkType The network type we'd like to check
      * @return {@code true} if supported, else {@code false}
      *
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public boolean isNetworkSupported(int networkType) {
         try {
             return mService.isNetworkSupported(networkType);
@@ -2419,12 +2433,11 @@
      * battery/performance issues. You should check this before doing large
      * data transfers, and warn the user or delay the operation until another
      * network is available.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      *
      * @return {@code true} if large transfers should be avoided, otherwise
      *        {@code false}.
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public boolean isActiveNetworkMetered() {
         try {
             return mService.isActiveNetworkMetered();
@@ -2491,6 +2504,7 @@
      * {@hide}
      * @deprecated Doesn't properly deal with multiple connected networks of the same type.
      */
+    @Deprecated
     public void setProvisioningNotificationVisible(boolean visible, int networkType,
             String action) {
         try {
@@ -2502,13 +2516,12 @@
 
     /**
      * Set the value for enabling/disabling airplane mode
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}.
      *
      * @param enable whether to enable airplane mode or not
      *
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL)
     public void setAirplaneMode(boolean enable) {
         try {
             mService.setAirplaneMode(enable);
@@ -2605,9 +2618,12 @@
         public void onLost(Network network) {}
 
         /**
-         * Called if no network is found in the given timeout time.  If no timeout is given,
-         * this will not be called.
-         * @hide
+         * Called if no network is found in the timeout time specified in
+         * {@link #requestNetwork(NetworkRequest, int, NetworkCallback)} call. This callback is not
+         * called for the version of {@link #requestNetwork(NetworkRequest, NetworkCallback)}
+         * without timeout. When this callback is invoked the associated
+         * {@link NetworkRequest} will have already been removed and released, as if
+         * {@link #unregisterNetworkCallback(NetworkCallback)} had been called.
          */
         public void onUnavailable() {}
 
@@ -2670,6 +2686,7 @@
     public static final int CALLBACK_IP_CHANGED          = BASE + 7;
     /** @hide */
     public static final int CALLBACK_RELEASED            = BASE + 8;
+    // TODO: consider deleting CALLBACK_EXIT and shifting following enum codes down by 1.
     /** @hide */
     public static final int CALLBACK_EXIT                = BASE + 9;
     /** @hide obj = NetworkCapabilities, arg1 = seq number */
@@ -2679,25 +2696,42 @@
     /** @hide */
     public static final int CALLBACK_RESUMED             = BASE + 12;
 
+    /** @hide */
+    public static String getCallbackName(int whichCallback) {
+        switch (whichCallback) {
+            case CALLBACK_PRECHECK:     return "CALLBACK_PRECHECK";
+            case CALLBACK_AVAILABLE:    return "CALLBACK_AVAILABLE";
+            case CALLBACK_LOSING:       return "CALLBACK_LOSING";
+            case CALLBACK_LOST:         return "CALLBACK_LOST";
+            case CALLBACK_UNAVAIL:      return "CALLBACK_UNAVAIL";
+            case CALLBACK_CAP_CHANGED:  return "CALLBACK_CAP_CHANGED";
+            case CALLBACK_IP_CHANGED:   return "CALLBACK_IP_CHANGED";
+            case CALLBACK_RELEASED:     return "CALLBACK_RELEASED";
+            case CALLBACK_EXIT:         return "CALLBACK_EXIT";
+            case EXPIRE_LEGACY_REQUEST: return "EXPIRE_LEGACY_REQUEST";
+            case CALLBACK_SUSPENDED:    return "CALLBACK_SUSPENDED";
+            case CALLBACK_RESUMED:      return "CALLBACK_RESUMED";
+            default:
+                return Integer.toString(whichCallback);
+        }
+    }
+
     private class CallbackHandler extends Handler {
-        private final HashMap<NetworkRequest, NetworkCallback>mCallbackMap;
-        private final AtomicInteger mRefCount;
         private static final String TAG = "ConnectivityManager.CallbackHandler";
-        private final ConnectivityManager mCm;
         private static final boolean DBG = false;
 
-        CallbackHandler(Looper looper, HashMap<NetworkRequest, NetworkCallback>callbackMap,
-                AtomicInteger refCount, ConnectivityManager cm) {
+        CallbackHandler(Looper looper) {
             super(looper);
-            mCallbackMap = callbackMap;
-            mRefCount = refCount;
-            mCm = cm;
+        }
+
+        CallbackHandler(Handler handler) {
+            this(handler.getLooper());
         }
 
         @Override
         public void handleMessage(Message message) {
-            NetworkRequest request = (NetworkRequest) getObject(message, NetworkRequest.class);
-            Network network = (Network) getObject(message, Network.class);
+            NetworkRequest request = getObject(message, NetworkRequest.class);
+            Network network = getObject(message, Network.class);
             if (DBG) {
                 Log.d(TAG, whatToString(message.what) + " for network " + network);
             }
@@ -2740,9 +2774,7 @@
                 case CALLBACK_CAP_CHANGED: {
                     NetworkCallback callback = getCallback(request, "CAP_CHANGED");
                     if (callback != null) {
-                        NetworkCapabilities cap = (NetworkCapabilities)getObject(message,
-                                NetworkCapabilities.class);
-
+                        NetworkCapabilities cap = getObject(message, NetworkCapabilities.class);
                         callback.onCapabilitiesChanged(network, cap);
                     }
                     break;
@@ -2750,9 +2782,7 @@
                 case CALLBACK_IP_CHANGED: {
                     NetworkCallback callback = getCallback(request, "IP_CHANGED");
                     if (callback != null) {
-                        LinkProperties lp = (LinkProperties)getObject(message,
-                                LinkProperties.class);
-
+                        LinkProperties lp = getObject(message, LinkProperties.class);
                         callback.onLinkPropertiesChanged(network, lp);
                     }
                     break;
@@ -2772,24 +2802,16 @@
                     break;
                 }
                 case CALLBACK_RELEASED: {
-                    NetworkCallback callback = null;
-                    synchronized(mCallbackMap) {
-                        callback = mCallbackMap.remove(request);
+                    final NetworkCallback callback;
+                    synchronized(sCallbacks) {
+                        callback = sCallbacks.remove(request);
                     }
-                    if (callback != null) {
-                        synchronized(mRefCount) {
-                            if (mRefCount.decrementAndGet() == 0) {
-                                getLooper().quit();
-                            }
-                        }
-                    } else {
+                    if (callback == null) {
                         Log.e(TAG, "callback not found for RELEASED message");
                     }
                     break;
                 }
                 case CALLBACK_EXIT: {
-                    Log.d(TAG, "Listener quitting");
-                    getLooper().quit();
                     break;
                 }
                 case EXPIRE_LEGACY_REQUEST: {
@@ -2799,14 +2821,14 @@
             }
         }
 
-        private Object getObject(Message msg, Class c) {
-            return msg.getData().getParcelable(c.getSimpleName());
+        private <T> T getObject(Message msg, Class<T> c) {
+            return (T) msg.getData().getParcelable(c.getSimpleName());
         }
 
         private NetworkCallback getCallback(NetworkRequest req, String name) {
             NetworkCallback callback;
-            synchronized(mCallbackMap) {
-                callback = mCallbackMap.get(req);
+            synchronized(sCallbacks) {
+                callback = sCallbacks.get(req);
             }
             if (callback == null) {
                 Log.e(TAG, "callback not found for " + name + " message");
@@ -2815,63 +2837,51 @@
         }
     }
 
-    private void incCallbackHandlerRefCount() {
-        synchronized(sCallbackRefCount) {
-            if (sCallbackRefCount.incrementAndGet() == 1) {
-                // TODO: switch this to ConnectivityThread
-                HandlerThread callbackThread = new HandlerThread("ConnectivityManager");
-                callbackThread.start();
-                sCallbackHandler = new CallbackHandler(callbackThread.getLooper(),
-                        sNetworkCallback, sCallbackRefCount, this);
+    private CallbackHandler getDefaultHandler() {
+        synchronized (sCallbacks) {
+            if (sCallbackHandler == null) {
+                sCallbackHandler = new CallbackHandler(ConnectivityThread.getInstanceLooper());
             }
+            return sCallbackHandler;
         }
     }
 
-    private void decCallbackHandlerRefCount() {
-        synchronized(sCallbackRefCount) {
-            if (sCallbackRefCount.decrementAndGet() == 0) {
-                sCallbackHandler.obtainMessage(CALLBACK_EXIT).sendToTarget();
-                sCallbackHandler = null;
-            }
-        }
-    }
+    private static final HashMap<NetworkRequest, NetworkCallback> sCallbacks = new HashMap<>();
+    private static CallbackHandler sCallbackHandler;
 
-    static final HashMap<NetworkRequest, NetworkCallback> sNetworkCallback =
-            new HashMap<NetworkRequest, NetworkCallback>();
-    static final AtomicInteger sCallbackRefCount = new AtomicInteger(0);
-    static CallbackHandler sCallbackHandler = null;
+    private static final int LISTEN  = 1;
+    private static final int REQUEST = 2;
 
-    private final static int LISTEN  = 1;
-    private final static int REQUEST = 2;
-
-    private NetworkRequest sendRequestForNetwork(NetworkCapabilities need,
-            NetworkCallback networkCallback, int timeoutSec, int action,
-            int legacyType) {
-        if (networkCallback == null) {
+    private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback,
+            int timeoutMs, int action, int legacyType, CallbackHandler handler) {
+        if (callback == null) {
             throw new IllegalArgumentException("null NetworkCallback");
         }
         if (need == null && action != REQUEST) {
             throw new IllegalArgumentException("null NetworkCapabilities");
         }
+        // TODO: throw an exception if callback.networkRequest is not null.
+        // http://b/20701525
+        final NetworkRequest request;
         try {
-            incCallbackHandlerRefCount();
-            synchronized(sNetworkCallback) {
+            synchronized(sCallbacks) {
+                Messenger messenger = new Messenger(handler);
+                Binder binder = new Binder();
                 if (action == LISTEN) {
-                    networkCallback.networkRequest = mService.listenForNetwork(need,
-                            new Messenger(sCallbackHandler), new Binder());
+                    request = mService.listenForNetwork(need, messenger, binder);
                 } else {
-                    networkCallback.networkRequest = mService.requestNetwork(need,
-                            new Messenger(sCallbackHandler), timeoutSec, new Binder(), legacyType);
+                    request = mService.requestNetwork(
+                            need, messenger, timeoutMs, binder, legacyType);
                 }
-                if (networkCallback.networkRequest != null) {
-                    sNetworkCallback.put(networkCallback.networkRequest, networkCallback);
+                if (request != null) {
+                    sCallbacks.put(request, callback);
                 }
+                callback.networkRequest = request;
             }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        if (networkCallback.networkRequest == null) decCallbackHandlerRefCount();
-        return networkCallback.networkRequest;
+        return request;
     }
 
     /**
@@ -2886,16 +2896,19 @@
      * @hide
      */
     public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
-            int timeoutMs, int legacyType) {
-        sendRequestForNetwork(request.networkCapabilities, networkCallback, timeoutMs, REQUEST,
-                legacyType);
+            int timeoutMs, int legacyType, Handler handler) {
+        CallbackHandler cbHandler = new CallbackHandler(handler);
+        NetworkCapabilities nc = request.networkCapabilities;
+        sendRequestForNetwork(nc, networkCallback, timeoutMs, REQUEST, legacyType, cbHandler);
     }
 
     /**
      * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}.
      *
      * This {@link NetworkRequest} will live until released via
-     * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits.
+     * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits. A
+     * version of the method which takes a timeout is
+     * {@link #requestNetwork(NetworkRequest, int, NetworkCallback)}.
      * Status of the request can be followed by listening to the various
      * callbacks described in {@link NetworkCallback}.  The {@link Network}
      * can be used to direct traffic to the network.
@@ -2914,25 +2927,34 @@
      * {@link android.provider.Settings.System#canWrite}.</p>
      *
      * @param request {@link NetworkRequest} describing this request.
-     * @param networkCallback The {@link NetworkCallback} to be utilized for this
-     *                        request.  Note the callback must not be shared - they
-     *                        uniquely specify this request.
+     * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
+     *                        the callback must not be shared - it uniquely specifies this request.
+     *                        The callback is invoked on the default internal Handler.
      * @throws IllegalArgumentException if {@code request} specifies any mutable
      *         {@code NetworkCapabilities}.
      */
     public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback) {
-        requestNetwork(request, networkCallback, 0,
-                inferLegacyTypeForNetworkCapabilities(request.networkCapabilities));
+        requestNetwork(request, networkCallback, getDefaultHandler());
     }
 
     /**
-     * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited
-     * by a timeout.
+     * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}.
      *
-     * This function behaves identically to the non-timedout version, but if a suitable
-     * network is not found within the given time (in milliseconds) the
-     * {@link NetworkCallback#unavailable} callback is called.  The request must
-     * still be released normally by calling {@link unregisterNetworkCallback(NetworkCallback)}.
+     * This {@link NetworkRequest} will live until released via
+     * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits. A
+     * version of the method which takes a timeout is
+     * {@link #requestNetwork(NetworkRequest, int, NetworkCallback)}.
+     * Status of the request can be followed by listening to the various
+     * callbacks described in {@link NetworkCallback}.  The {@link Network}
+     * can be used to direct traffic to the network.
+     * <p>It is presently unsupported to request a network with mutable
+     * {@link NetworkCapabilities} such as
+     * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or
+     * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}
+     * as these {@code NetworkCapabilities} represent states that a particular
+     * network may never attain, and whether a network will attain these states
+     * is unknown prior to bringing up the network so the framework does not
+     * know how to go about satisfing a request with these capabilities.
      *
      * <p>This method requires the caller to hold either the
      * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
@@ -2940,28 +2962,93 @@
      * {@link android.provider.Settings.System#canWrite}.</p>
      *
      * @param request {@link NetworkRequest} describing this request.
-     * @param networkCallback The callbacks to be utilized for this request.  Note
-     *                        the callbacks must not be shared - they uniquely specify
-     *                        this request.
-     * @param timeoutMs The time in milliseconds to attempt looking for a suitable network
-     *                  before {@link NetworkCallback#unavailable} is called.
-     *
-     * TODO: Make timeouts work and then unhide this method.
-     *
-     * @hide
+     * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
+     *                        the callback must not be shared - it uniquely specifies this request.
+     * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+     * @throws IllegalArgumentException if {@code request} specifies any mutable
+     *         {@code NetworkCapabilities}.
      */
-    public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
-            int timeoutMs) {
-        requestNetwork(request, networkCallback, timeoutMs,
-                inferLegacyTypeForNetworkCapabilities(request.networkCapabilities));
+    public void requestNetwork(
+            NetworkRequest request, NetworkCallback networkCallback, Handler handler) {
+        int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
+        CallbackHandler cbHandler = new CallbackHandler(handler);
+        requestNetwork(request, networkCallback, 0, legacyType, cbHandler);
     }
 
     /**
-     * The maximum number of milliseconds the framework will look for a suitable network
-     * during a timeout-equiped call to {@link requestNetwork}.
-     * {@hide}
+     * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited
+     * by a timeout.
+     *
+     * This function behaves identically to the non-timed-out version
+     * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, but if a suitable network
+     * is not found within the given time (in milliseconds) the
+     * {@link NetworkCallback#onUnavailable()} callback is called. The request can still be
+     * released normally by calling {@link #unregisterNetworkCallback(NetworkCallback)} but does
+     * not have to be released if timed-out (it is automatically released). Unregistering a
+     * request that timed out is not an error.
+     *
+     * <p>Do not use this method to poll for the existence of specific networks (e.g. with a small
+     * timeout) - {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} is provided
+     * for that purpose. Calling this method will attempt to bring up the requested network.
+     *
+     * <p>This method requires the caller to hold either the
+     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+     * or the ability to modify system settings as determined by
+     * {@link android.provider.Settings.System#canWrite}.</p>
+     *
+     * @param request {@link NetworkRequest} describing this request.
+     * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
+     *                        the callback must not be shared - it uniquely specifies this request.
+     * @param timeoutMs The time in milliseconds to attempt looking for a suitable network
+     *                  before {@link NetworkCallback#onUnavailable()} is called. The timeout must
+     *                  be a positive value (i.e. >0).
      */
-    public final static int MAX_NETWORK_REQUEST_TIMEOUT_MS = 100 * 60 * 1000;
+    public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
+            int timeoutMs) {
+        if (timeoutMs <= 0) {
+            throw new IllegalArgumentException("Non-positive timeoutMs: " + timeoutMs);
+        }
+        int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
+        requestNetwork(request, networkCallback, timeoutMs, legacyType, getDefaultHandler());
+    }
+
+
+    /**
+     * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited
+     * by a timeout.
+     *
+     * This function behaves identically to the non-timedout version, but if a suitable
+     * network is not found within the given time (in milliseconds) the
+     * {@link NetworkCallback#onUnavailable} callback is called. The request can still be
+     * released normally by calling {@link #unregisterNetworkCallback(NetworkCallback)} but does
+     * not have to be released if timed-out (it is automatically released). Unregistering a
+     * request that timed out is not an error.
+     *
+     * <p>Do not use this method to poll for the existence of specific networks (e.g. with a small
+     * timeout) - {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} is provided
+     * for that purpose. Calling this method will attempt to bring up the requested network.
+     *
+     * <p>This method requires the caller to hold either the
+     * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+     * or the ability to modify system settings as determined by
+     * {@link android.provider.Settings.System#canWrite}.</p>
+     *
+     * @param request {@link NetworkRequest} describing this request.
+     * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
+     *                        the callback must not be shared - it uniquely specifies this request.
+     * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+     * @param timeoutMs The time in milliseconds to attempt looking for a suitable network
+     *                  before {@link NetworkCallback#onUnavailable} is called.
+     */
+    public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
+            Handler handler, int timeoutMs) {
+        if (timeoutMs <= 0) {
+            throw new IllegalArgumentException("Non-positive timeoutMs");
+        }
+        int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
+        CallbackHandler cbHandler = new CallbackHandler(handler);
+        requestNetwork(request, networkCallback, timeoutMs, legacyType, cbHandler);
+    }
 
     /**
      * The lookup key for a {@link Network} object included with the intent after
@@ -3068,15 +3155,33 @@
      * Registers to receive notifications about all networks which satisfy the given
      * {@link NetworkRequest}.  The callbacks will continue to be called until
      * either the application exits or link #unregisterNetworkCallback(NetworkCallback)} is called.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      *
      * @param request {@link NetworkRequest} describing this request.
      * @param networkCallback The {@link NetworkCallback} that the system will call as suitable
      *                        networks change state.
+     *                        The callback is invoked on the default internal Handler.
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public void registerNetworkCallback(NetworkRequest request, NetworkCallback networkCallback) {
-        sendRequestForNetwork(request.networkCapabilities, networkCallback, 0, LISTEN, TYPE_NONE);
+        registerNetworkCallback(request, networkCallback, getDefaultHandler());
+    }
+
+    /**
+     * Registers to receive notifications about all networks which satisfy the given
+     * {@link NetworkRequest}.  The callbacks will continue to be called until
+     * either the application exits or link #unregisterNetworkCallback(NetworkCallback)} is called.
+     *
+     * @param request {@link NetworkRequest} describing this request.
+     * @param networkCallback The {@link NetworkCallback} that the system will call as suitable
+     *                        networks change state.
+     * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+    public void registerNetworkCallback(
+            NetworkRequest request, NetworkCallback networkCallback, Handler handler) {
+        CallbackHandler cbHandler = new CallbackHandler(handler);
+        NetworkCapabilities nc = request.networkCapabilities;
+        sendRequestForNetwork(nc, networkCallback, 0, LISTEN, TYPE_NONE, cbHandler);
     }
 
     /**
@@ -3103,13 +3208,12 @@
      * <p>
      * The request may be released normally by calling
      * {@link #unregisterNetworkCallback(android.app.PendingIntent)}.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      * @param request {@link NetworkRequest} describing this request.
      * @param operation Action to perform when the network is available (corresponds
      *                  to the {@link NetworkCallback#onAvailable} call.  Typically
      *                  comes from {@link PendingIntent#getBroadcast}. Cannot be null.
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public void registerNetworkCallback(NetworkRequest request, PendingIntent operation) {
         checkPendingIntent(operation);
         try {
@@ -3123,13 +3227,27 @@
      * Registers to receive notifications about changes in the system default network. The callbacks
      * will continue to be called until either the application exits or
      * {@link #unregisterNetworkCallback(NetworkCallback)} is called.
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      *
      * @param networkCallback The {@link NetworkCallback} that the system will call as the
      *                        system default network changes.
+     *                        The callback is invoked on the default internal Handler.
      */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
     public void registerDefaultNetworkCallback(NetworkCallback networkCallback) {
+        registerDefaultNetworkCallback(networkCallback, getDefaultHandler());
+    }
+
+    /**
+     * Registers to receive notifications about changes in the system default network. The callbacks
+     * will continue to be called until either the application exits or
+     * {@link #unregisterNetworkCallback(NetworkCallback)} is called.
+     *
+     * @param networkCallback The {@link NetworkCallback} that the system will call as the
+     *                        system default network changes.
+     * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+    public void registerDefaultNetworkCallback(NetworkCallback networkCallback, Handler handler) {
         // This works because if the NetworkCapabilities are null,
         // ConnectivityService takes them from the default request.
         //
@@ -3137,7 +3255,8 @@
         // capabilities, this request is guaranteed, at all times, to be
         // satisfied by the same network, if any, that satisfies the default
         // request, i.e., the system default network.
-        sendRequestForNetwork(null, networkCallback, 0, REQUEST, TYPE_NONE);
+        CallbackHandler cbHandler = new CallbackHandler(handler);
+        sendRequestForNetwork(null, networkCallback, 0, REQUEST, TYPE_NONE, cbHandler);
     }
 
     /**
@@ -3204,15 +3323,13 @@
      * {@code always} is true, then the choice is remembered, so that the next time the user
      * connects to this network, the system will switch to it.
      *
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}
-     *
      * @param network The network to accept.
      * @param accept Whether to accept the network even if unvalidated.
      * @param always Whether to remember this choice in the future.
      *
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL)
     public void setAcceptUnvalidated(Network network, boolean accept, boolean always) {
         try {
             mService.setAcceptUnvalidated(network, accept, always);
@@ -3227,13 +3344,11 @@
      * {@code config_networkAvoidBadWifi} configuration variable is set to 0 and the {@code
      * NETWORK_AVOID_BAD_WIFI setting is unset}.
      *
-     * <p>This method requires the caller to hold the permission
-     * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}
-     *
      * @param network The network to accept.
      *
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL)
     public void setAvoidUnvalidated(Network network) {
         try {
             mService.setAvoidUnvalidated(network);
@@ -3243,6 +3358,89 @@
     }
 
     /**
+     * Requests that the system open the captive portal app on the specified network.
+     *
+     * @param network The network to log into.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL)
+    public void startCaptivePortalApp(Network network) {
+        try {
+            mService.startCaptivePortalApp(network);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * It is acceptable to briefly use multipath data to provide seamless connectivity for
+     * time-sensitive user-facing operations when the system default network is temporarily
+     * unresponsive. The amount of data should be limited (less than one megabyte for every call to
+     * this method), and the operation should be infrequent to ensure that data usage is limited.
+     *
+     * An example of such an operation might be a time-sensitive foreground activity, such as a
+     * voice command, that the user is performing while walking out of range of a Wi-Fi network.
+     */
+    public static final int MULTIPATH_PREFERENCE_HANDOVER = 1 << 0;
+
+    /**
+     * It is acceptable to use small amounts of multipath data on an ongoing basis to provide
+     * a backup channel for traffic that is primarily going over another network.
+     *
+     * An example might be maintaining backup connections to peers or servers for the purpose of
+     * fast fallback if the default network is temporarily unresponsive or disconnects. The traffic
+     * on backup paths should be negligible compared to the traffic on the main path.
+     */
+    public static final int MULTIPATH_PREFERENCE_RELIABILITY = 1 << 1;
+
+    /**
+     * It is acceptable to use metered data to improve network latency and performance.
+     */
+    public static final int MULTIPATH_PREFERENCE_PERFORMANCE = 1 << 2;
+
+    /**
+     * Return value to use for unmetered networks. On such networks we currently set all the flags
+     * to true.
+     * @hide
+     */
+    public static final int MULTIPATH_PREFERENCE_UNMETERED =
+            MULTIPATH_PREFERENCE_HANDOVER |
+            MULTIPATH_PREFERENCE_RELIABILITY |
+            MULTIPATH_PREFERENCE_PERFORMANCE;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = {
+            MULTIPATH_PREFERENCE_HANDOVER,
+            MULTIPATH_PREFERENCE_RELIABILITY,
+            MULTIPATH_PREFERENCE_PERFORMANCE,
+    })
+    public @interface MultipathPreference {
+    }
+
+    /**
+     * Provides a hint to the calling application on whether it is desirable to use the
+     * multinetwork APIs (e.g., {@link Network#openConnection}, {@link Network#bindSocket}, etc.)
+     * for multipath data transfer on this network when it is not the system default network.
+     * Applications desiring to use multipath network protocols should call this method before
+     * each such operation.
+     *
+     * @param network The network on which the application desires to use multipath data.
+     *                If {@code null}, this method will return the a preference that will generally
+     *                apply to metered networks.
+     * @return a bitwise OR of zero or more of the  {@code MULTIPATH_PREFERENCE_*} constants.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+    public @MultipathPreference int getMultipathPreference(Network network) {
+        try {
+            return mService.getMultipathPreference(network);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Resets all connectivity manager settings back to factory defaults.
      * @hide
      */
@@ -3299,6 +3497,7 @@
      *             {@link #bindProcessToNetwork} instead.  {@code bindProcessToNetwork}
      *             is a direct replacement.
      */
+    @Deprecated
     public static boolean setProcessDefaultNetwork(Network network) {
         int netId = (network == null) ? NETID_UNSET : network.netId;
         if (netId == NetworkUtils.getBoundNetworkForProcess()) {
@@ -3345,6 +3544,7 @@
      *             {@link IllegalStateException}.  Use {@link #getBoundNetworkForProcess} instead.
      *             {@code getBoundNetworkForProcess} is a direct replacement.
      */
+    @Deprecated
     public static Network getProcessDefaultNetwork() {
         int netId = NetworkUtils.getBoundNetworkForProcess();
         if (netId == NETID_UNSET) return null;
@@ -3389,6 +3589,7 @@
      * @hide
      * @deprecated This is strictly for legacy usage to support {@link #startUsingNetworkFeature}.
      */
+    @Deprecated
     public static boolean setProcessDefaultNetworkForHostResolution(Network network) {
         return NetworkUtils.bindProcessToNetworkForHostResolution(
                 network == null ? NETID_UNSET : network.netId);
diff --git a/core/java/android/net/ConnectivityThread.java b/core/java/android/net/ConnectivityThread.java
index 55c3402..0b218e7 100644
--- a/core/java/android/net/ConnectivityThread.java
+++ b/core/java/android/net/ConnectivityThread.java
@@ -27,25 +27,30 @@
  * @hide
  */
 public final class ConnectivityThread extends HandlerThread {
-    private static ConnectivityThread sInstance;
+
+    // A class implementing the lazy holder idiom: the unique static instance
+    // of ConnectivityThread is instantiated in a thread-safe way (guaranteed by
+    // the language specs) the first time that Singleton is referenced in get()
+    // or getInstanceLooper().
+    private static class Singleton {
+        private static final ConnectivityThread INSTANCE = createInstance();
+    }
 
     private ConnectivityThread() {
         super("ConnectivityThread");
     }
 
-    private static synchronized ConnectivityThread getInstance() {
-        if (sInstance == null) {
-            sInstance = new ConnectivityThread();
-            sInstance.start();
-        }
-        return sInstance;
+    private static ConnectivityThread createInstance() {
+        ConnectivityThread t = new ConnectivityThread();
+        t.start();
+        return t;
     }
 
     public static ConnectivityThread get() {
-        return getInstance();
+        return Singleton.INSTANCE;
     }
 
     public static Looper getInstanceLooper() {
-        return getInstance().getLooper();
+        return Singleton.INSTANCE.getLooper();
     }
 }
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 4aabda9..6b2da61 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -156,12 +156,13 @@
     void pendingListenForNetwork(in NetworkCapabilities networkCapabilities,
             in PendingIntent operation);
 
-    void requestLinkProperties(in NetworkRequest networkRequest);
-    void requestNetworkCapabilities(in NetworkRequest networkRequest);
     void releaseNetworkRequest(in NetworkRequest networkRequest);
 
     void setAcceptUnvalidated(in Network network, boolean accept, boolean always);
     void setAvoidUnvalidated(in Network network);
+    void startCaptivePortalApp(in Network network);
+
+    int getMultipathPreference(in Network Network);
 
     int getRestoreDefaultNetworkDelay(int networkType);
 
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index fe69320..0c0872a 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -34,9 +34,13 @@
 import java.net.UnknownHostException;
 import java.net.URL;
 import java.net.URLConnection;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
 import javax.net.SocketFactory;
 
 import com.android.okhttp.ConnectionPool;
+import com.android.okhttp.Dns;
 import com.android.okhttp.HttpHandler;
 import com.android.okhttp.HttpsHandler;
 import com.android.okhttp.OkHttpClient;
@@ -62,10 +66,10 @@
     // Objects used to perform per-network operations such as getSocketFactory
     // and openConnection, and a lock to protect access to them.
     private volatile NetworkBoundSocketFactory mNetworkBoundSocketFactory = null;
-    // mLock should be used to control write access to mConnectionPool and mNetwork.
+    // mLock should be used to control write access to mConnectionPool and mDns.
     // maybeInitHttpClient() must be called prior to reading either variable.
     private volatile ConnectionPool mConnectionPool = null;
-    private volatile com.android.okhttp.internal.Network mNetwork = null;
+    private volatile Dns mDns = null;
     private final Object mLock = new Object();
 
     // Default connection pool values. These are evaluated at startup, just
@@ -219,17 +223,17 @@
     // out) ConnectionPools.
     private void maybeInitHttpClient() {
         synchronized (mLock) {
-            if (mNetwork == null) {
-                mNetwork = new com.android.okhttp.internal.Network() {
+            if (mDns == null) {
+                mDns = new Dns() {
                     @Override
-                    public InetAddress[] resolveInetAddresses(String host) throws UnknownHostException {
-                        return Network.this.getAllByName(host);
+                    public List<InetAddress> lookup(String hostname) throws UnknownHostException {
+                        return Arrays.asList(Network.this.getAllByName(hostname));
                     }
                 };
             }
             if (mConnectionPool == null) {
                 mConnectionPool = new ConnectionPool(httpMaxConnections,
-                        httpKeepAliveDurationMs);
+                        httpKeepAliveDurationMs, TimeUnit.MILLISECONDS);
             }
         }
     }
@@ -288,9 +292,8 @@
         }
         OkHttpClient client = okUrlFactory.client();
         client.setSocketFactory(getSocketFactory()).setConnectionPool(mConnectionPool);
-
-        // Use internal APIs to change the Network.
-        Internal.instance.setNetwork(client, mNetwork);
+        // Let network traffic go via mDns
+        client.setDns(mDns);
 
         return okUrlFactory.open(url);
     }
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 56eba4f..bf7207c 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -18,8 +18,11 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.text.TextUtils;
-import java.lang.IllegalArgumentException;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.BitUtils;
+
+import java.util.Objects;
 
 /**
  * This class represents the capabilities of a network.  This is used both to specify
@@ -33,6 +36,8 @@
  * all cellular based connections are metered and all Wi-Fi based connections are not.
  */
 public final class NetworkCapabilities implements Parcelable {
+    private static final String TAG = "NetworkCapabilities";
+
     /**
      * @hide
      */
@@ -205,19 +210,6 @@
             (1 << NET_CAPABILITY_FOREGROUND);
 
     /**
-     * Network specifier for factories which want to match any network specifier
-     * (NS) in a request. Behavior:
-     * <li>Empty NS in request matches any network factory NS</li>
-     * <li>Empty NS in the network factory NS only matches a request with an
-     * empty NS</li>
-     * <li>"*" (this constant) NS in the network factory matches requests with
-     * any NS</li>
-     *
-     * @hide
-     */
-    public static final String MATCH_ALL_REQUESTS_NETWORK_SPECIFIER = "*";
-
-    /**
      * Network capabilities that are not allowed in NetworkRequests. This exists because the
      * NetworkFactory / NetworkAgent model does not deal well with the situation where a
      * capability's presence cannot be known in advance. If such a capability is requested, then we
@@ -239,7 +231,8 @@
      * Capabilities that suggest that a network is restricted.
      * {@see #maybeMarkCapabilitiesRestricted}.
      */
-    private static final long RESTRICTED_CAPABILITIES =
+    @VisibleForTesting
+    /* package */ static final long RESTRICTED_CAPABILITIES =
             (1 << NET_CAPABILITY_CBS) |
             (1 << NET_CAPABILITY_DUN) |
             (1 << NET_CAPABILITY_EIMS) |
@@ -250,6 +243,17 @@
             (1 << NET_CAPABILITY_XCAP);
 
     /**
+     * Capabilities that suggest that a network is unrestricted.
+     * {@see #maybeMarkCapabilitiesRestricted}.
+     */
+    @VisibleForTesting
+    /* package */ static final long UNRESTRICTED_CAPABILITIES =
+            (1 << NET_CAPABILITY_INTERNET) |
+            (1 << NET_CAPABILITY_MMS) |
+            (1 << NET_CAPABILITY_SUPL) |
+            (1 << NET_CAPABILITY_WIFI_P2P);
+
+    /**
      * 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.
@@ -289,7 +293,7 @@
      * @hide
      */
     public int[] getCapabilities() {
-        return enumerateBits(mNetworkCapabilities);
+        return BitUtils.unpackBits(mNetworkCapabilities);
     }
 
     /**
@@ -305,19 +309,6 @@
         return ((mNetworkCapabilities & (1 << capability)) != 0);
     }
 
-    private int[] enumerateBits(long val) {
-        int size = Long.bitCount(val);
-        int[] result = new int[size];
-        int index = 0;
-        int resource = 0;
-        while (val > 0) {
-            if ((val & 1) == 1) result[index++] = resource;
-            val = val >> 1;
-            resource++;
-        }
-        return result;
-    }
-
     private void combineNetCapabilities(NetworkCapabilities nc) {
         this.mNetworkCapabilities |= nc.mNetworkCapabilities;
     }
@@ -375,12 +366,16 @@
      * @hide
      */
     public void maybeMarkCapabilitiesRestricted() {
-        // If all the capabilities are typically provided by restricted networks, conclude that this
-        // network is restricted.
-        if ((mNetworkCapabilities & ~(DEFAULT_CAPABILITIES | RESTRICTED_CAPABILITIES)) == 0 &&
-                // Must have at least some restricted capabilities, otherwise a request for an
-                // internet-less network will get marked restricted.
-                (mNetworkCapabilities & RESTRICTED_CAPABILITIES) != 0) {
+        // Verify there aren't any unrestricted capabilities.  If there are we say
+        // the whole thing is unrestricted.
+        final boolean hasUnrestrictedCapabilities =
+                ((mNetworkCapabilities & UNRESTRICTED_CAPABILITIES) != 0);
+
+        // Must have at least some restricted capabilities.
+        final boolean hasRestrictedCapabilities =
+                ((mNetworkCapabilities & RESTRICTED_CAPABILITIES) != 0);
+
+        if (hasRestrictedCapabilities && !hasUnrestrictedCapabilities) {
             removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
         }
     }
@@ -418,8 +413,24 @@
      */
     public static final int TRANSPORT_VPN = 4;
 
-    private static final int MIN_TRANSPORT = TRANSPORT_CELLULAR;
-    private static final int MAX_TRANSPORT = TRANSPORT_VPN;
+    /**
+     * Indicates this network uses a Wi-Fi Aware transport.
+     */
+    public static final int TRANSPORT_WIFI_AWARE = 5;
+
+    /** @hide */
+    public static final int MIN_TRANSPORT = TRANSPORT_CELLULAR;
+    /** @hide */
+    public static final int MAX_TRANSPORT = TRANSPORT_WIFI_AWARE;
+
+    private static final String[] TRANSPORT_NAMES = {
+        "CELLULAR",
+        "WIFI",
+        "BLUETOOTH",
+        "ETHERNET",
+        "VPN",
+        "WIFI_AWARE"
+    };
 
     /**
      * Adds the given transport type to this {@code NetworkCapability} instance.
@@ -467,7 +478,7 @@
      * @hide
      */
     public int[] getTransportTypes() {
-        return enumerateBits(mTransportTypes);
+        return BitUtils.unpackBits(mTransportTypes);
     }
 
     /**
@@ -576,63 +587,56 @@
                 this.mLinkDownBandwidthKbps == nc.mLinkDownBandwidthKbps);
     }
 
-    private String mNetworkSpecifier;
+    private NetworkSpecifier mNetworkSpecifier = null;
+
     /**
      * Sets the optional bearer specific network specifier.
      * This has no meaning if a single transport is also not specified, so calling
      * this without a single transport set will generate an exception, as will
      * subsequently adding or removing transports after this is set.
      * </p>
-     * The interpretation of this {@code String} is bearer specific and bearers that use
-     * it should document their particulars.  For example, Bluetooth may use some sort of
-     * device id while WiFi could used SSID and/or BSSID.  Cellular may use carrier SPN (name)
-     * or Subscription ID.
      *
-     * @param networkSpecifier An {@code String} of opaque format used to specify the bearer
-     *                         specific network specifier where the bearer has a choice of
-     *                         networks.
+     * @param networkSpecifier A concrete, parcelable framework class that extends
+     *                         NetworkSpecifier.
      * @return This NetworkCapabilities instance, to facilitate chaining.
      * @hide
      */
-    public NetworkCapabilities setNetworkSpecifier(String networkSpecifier) {
-        if (TextUtils.isEmpty(networkSpecifier) == false && Long.bitCount(mTransportTypes) != 1) {
+    public NetworkCapabilities setNetworkSpecifier(NetworkSpecifier networkSpecifier) {
+        if (networkSpecifier != null && Long.bitCount(mTransportTypes) != 1) {
             throw new IllegalStateException("Must have a single transport specified to use " +
                     "setNetworkSpecifier");
         }
+
         mNetworkSpecifier = networkSpecifier;
+
         return this;
     }
 
     /**
      * Gets the optional bearer specific network specifier.
      *
-     * @return The optional {@code String} specifying the bearer specific network specifier.
-     *         See {@link #setNetworkSpecifier}.
+     * @return The optional {@link NetworkSpecifier} specifying the bearer specific network
+     *         specifier. See {@link #setNetworkSpecifier}.
      * @hide
      */
-    public String getNetworkSpecifier() {
+    public NetworkSpecifier getNetworkSpecifier() {
         return mNetworkSpecifier;
     }
 
     private void combineSpecifiers(NetworkCapabilities nc) {
-        String otherSpecifier = nc.getNetworkSpecifier();
-        if (TextUtils.isEmpty(otherSpecifier)) return;
-        if (TextUtils.isEmpty(mNetworkSpecifier) == false) {
+        if (mNetworkSpecifier != null && !mNetworkSpecifier.equals(nc.mNetworkSpecifier)) {
             throw new IllegalStateException("Can't combine two networkSpecifiers");
         }
-        setNetworkSpecifier(otherSpecifier);
+        setNetworkSpecifier(nc.mNetworkSpecifier);
     }
+
     private boolean satisfiedBySpecifier(NetworkCapabilities nc) {
-        return (TextUtils.isEmpty(mNetworkSpecifier) ||
-                mNetworkSpecifier.equals(nc.mNetworkSpecifier) ||
-                MATCH_ALL_REQUESTS_NETWORK_SPECIFIER.equals(nc.mNetworkSpecifier));
+        return mNetworkSpecifier == null || mNetworkSpecifier.satisfiedBy(nc.mNetworkSpecifier)
+                || nc.mNetworkSpecifier instanceof MatchAllNetworkSpecifier;
     }
+
     private boolean equalsSpecifier(NetworkCapabilities nc) {
-        if (TextUtils.isEmpty(mNetworkSpecifier)) {
-            return TextUtils.isEmpty(nc.mNetworkSpecifier);
-        } else {
-            return mNetworkSpecifier.equals(nc.mNetworkSpecifier);
-        }
+        return Objects.equals(mNetworkSpecifier, nc.mNetworkSpecifier);
     }
 
     /**
@@ -794,7 +798,7 @@
                 ((int)(mTransportTypes >> 32) * 7) +
                 (mLinkUpBandwidthKbps * 11) +
                 (mLinkDownBandwidthKbps * 13) +
-                (TextUtils.isEmpty(mNetworkSpecifier) ? 0 : mNetworkSpecifier.hashCode() * 17) +
+                Objects.hashCode(mNetworkSpecifier) * 17 +
                 (mSignalStrength * 19));
     }
 
@@ -808,7 +812,7 @@
         dest.writeLong(mTransportTypes);
         dest.writeInt(mLinkUpBandwidthKbps);
         dest.writeInt(mLinkDownBandwidthKbps);
-        dest.writeString(mNetworkSpecifier);
+        dest.writeParcelable((Parcelable) mNetworkSpecifier, flags);
         dest.writeInt(mSignalStrength);
     }
 
@@ -822,7 +826,7 @@
                 netCap.mTransportTypes = in.readLong();
                 netCap.mLinkUpBandwidthKbps = in.readInt();
                 netCap.mLinkDownBandwidthKbps = in.readInt();
-                netCap.mNetworkSpecifier = in.readString();
+                netCap.mNetworkSpecifier = in.readParcelable(null);
                 netCap.mSignalStrength = in.readInt();
                 return netCap;
             }
@@ -881,17 +885,23 @@
      * @hide
      */
     public static String transportNamesOf(int[] types) {
-        String transports = "";
-        for (int i = 0; i < types.length;) {
-            switch (types[i]) {
-                case TRANSPORT_CELLULAR:    transports += "CELLULAR"; break;
-                case TRANSPORT_WIFI:        transports += "WIFI"; break;
-                case TRANSPORT_BLUETOOTH:   transports += "BLUETOOTH"; break;
-                case TRANSPORT_ETHERNET:    transports += "ETHERNET"; break;
-                case TRANSPORT_VPN:         transports += "VPN"; break;
-            }
-            if (++i < types.length) transports += "|";
+        if (types == null || types.length == 0) {
+            return "";
         }
-        return transports;
+        StringBuilder transports = new StringBuilder();
+        for (int t : types) {
+            transports.append("|").append(transportNameOf(t));
+        }
+        return transports.substring(1);
+    }
+
+    /**
+     * @hide
+     */
+    public static String transportNameOf(int transport) {
+        if (transport < 0 || TRANSPORT_NAMES.length <= transport) {
+            return "UNKNOWN";
+        }
+        return TRANSPORT_NAMES[transport];
     }
 }
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index ae72470..95a8bb4 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -18,6 +18,7 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
 
 import java.util.Objects;
 
@@ -178,6 +179,20 @@
         }
 
         /**
+         * Set the {@code NetworkCapabilities} for this builder instance,
+         * overriding any capabilities that had been previously set.
+         *
+         * @param nc The superseding {@code NetworkCapabilities} instance.
+         * @return The builder to facilitate chaining.
+         * @hide
+         */
+        public Builder setCapabilities(NetworkCapabilities nc) {
+            mNetworkCapabilities.clearAll();
+            mNetworkCapabilities.combineCapabilities(nc);
+            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.
          *
@@ -245,10 +260,27 @@
          *                         networks.
          */
         public Builder setNetworkSpecifier(String networkSpecifier) {
-            if (NetworkCapabilities.MATCH_ALL_REQUESTS_NETWORK_SPECIFIER.equals(networkSpecifier)) {
-                throw new IllegalArgumentException("Invalid network specifier - must not be '"
-                        + NetworkCapabilities.MATCH_ALL_REQUESTS_NETWORK_SPECIFIER + "'");
-            }
+            /*
+             * A StringNetworkSpecifier does not accept null or empty ("") strings. When network
+             * specifiers were strings a null string and an empty string were considered equivalent.
+             * Hence no meaning is attached to a null or empty ("") string.
+             */
+            return setNetworkSpecifier(TextUtils.isEmpty(networkSpecifier) ? null
+                    : new StringNetworkSpecifier(networkSpecifier));
+        }
+
+        /**
+         * Sets the optional bearer specific network specifier.
+         * This has no meaning if a single transport is also not specified, so calling
+         * this without a single transport set will generate an exception, as will
+         * subsequently adding or removing transports after this is set.
+         * </p>
+         *
+         * @param networkSpecifier A concrete, parcelable framework class that extends
+         *                         NetworkSpecifier.
+         */
+        public Builder setNetworkSpecifier(NetworkSpecifier networkSpecifier) {
+            MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(networkSpecifier);
             mNetworkCapabilities.setNetworkSpecifier(networkSpecifier);
             return this;
         }
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index a677d73..fe9563d 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -92,6 +92,7 @@
      *
      * @deprecated This is strictly for legacy usage to support startUsingNetworkFeature().
      */
+    @Deprecated
     public native static boolean bindProcessToNetworkForHostResolution(int netId);
 
     /**
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index eb105d2..3e99521 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -25,11 +25,8 @@
 #include <arpa/inet.h>
 #include <net/if.h>
 #include <linux/filter.h>
-#include <linux/if.h>
 #include <linux/if_arp.h>
-#include <linux/if_ether.h>
-#include <linux/if_packet.h>
-#include <net/if_ether.h>
+#include <netinet/ether.h>
 #include <netinet/icmp6.h>
 #include <netinet/ip.h>
 #include <netinet/ip6.h>
diff --git a/core/tests/coretests/src/android/net/IpPrefixTest.java b/core/tests/coretests/src/android/net/IpPrefixTest.java
index fcc6389..4f2387d 100644
--- a/core/tests/coretests/src/android/net/IpPrefixTest.java
+++ b/core/tests/coretests/src/android/net/IpPrefixTest.java
@@ -18,14 +18,14 @@
 
 import android.net.IpPrefix;
 import android.os.Parcel;
-import static android.test.MoreAsserts.assertNotEqual;
 import android.test.suitebuilder.annotation.SmallTest;
-
-import static org.junit.Assert.assertArrayEquals;
 import java.net.InetAddress;
 import java.util.Random;
 import junit.framework.TestCase;
 
+import static android.test.MoreAsserts.assertNotEqual;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
 
 public class IpPrefixTest extends TestCase {
 
@@ -242,25 +242,42 @@
 
     @SmallTest
     public void testHashCode() {
-        IpPrefix p;
-        int oldCode = -1;
+        IpPrefix p = new IpPrefix(new byte[4], 0);
         Random random = new Random();
         for (int i = 0; i < 100; i++) {
+            final IpPrefix oldP = p;
             if (random.nextBoolean()) {
                 // IPv4.
                 byte[] b = new byte[4];
                 random.nextBytes(b);
                 p = new IpPrefix(b, random.nextInt(33));
-                assertNotEqual(oldCode, p.hashCode());
-                oldCode = p.hashCode();
             } else {
                 // IPv6.
                 byte[] b = new byte[16];
                 random.nextBytes(b);
                 p = new IpPrefix(b, random.nextInt(129));
-                assertNotEqual(oldCode, p.hashCode());
-                oldCode = p.hashCode();
             }
+            if (p.equals(oldP)) {
+              assertEquals(p.hashCode(), oldP.hashCode());
+            }
+            if (p.hashCode() != oldP.hashCode()) {
+              assertNotEqual(p, oldP);
+            }
+        }
+    }
+
+    @SmallTest
+    public void testHashCodeIsNotConstant() {
+        IpPrefix[] prefixes = {
+            new IpPrefix("2001:db8:f00::ace:d00d/127"),
+            new IpPrefix("192.0.2.0/23"),
+            new IpPrefix("::/0"),
+            new IpPrefix("0.0.0.0/0"),
+        };
+        for (int i = 0; i < prefixes.length; i++) {
+          for (int j = i + 1; j < prefixes.length; j++) {
+            assertNotEqual(prefixes[i].hashCode(), prefixes[j].hashCode());
+          }
         }
     }
 
diff --git a/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java b/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
deleted file mode 100644
index 327f3fd..0000000
--- a/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2011 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.internal.net;
-
-import static android.net.NetworkStats.ROAMING_NO;
-import static android.net.NetworkStats.SET_ALL;
-import static android.net.NetworkStats.SET_DEFAULT;
-import static android.net.NetworkStats.SET_FOREGROUND;
-import static android.net.NetworkStats.TAG_NONE;
-import static android.net.NetworkStats.UID_ALL;
-import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
-
-import android.content.res.Resources;
-import android.net.NetworkStats;
-import android.net.TrafficStats;
-import android.test.AndroidTestCase;
-
-import com.android.frameworks.coretests.R;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.FileWriter;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import libcore.io.IoUtils;
-import libcore.io.Streams;
-
-/**
- * Tests for {@link NetworkStatsFactory}.
- */
-public class NetworkStatsFactoryTest extends AndroidTestCase {
-    private File mTestProc;
-    private NetworkStatsFactory mFactory;
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-
-        mTestProc = new File(getContext().getFilesDir(), "proc");
-        if (mTestProc.exists()) {
-            IoUtils.deleteContents(mTestProc);
-        }
-
-        mFactory = new NetworkStatsFactory(mTestProc);
-    }
-
-    @Override
-    public void tearDown() throws Exception {
-        mFactory = null;
-
-        if (mTestProc.exists()) {
-            IoUtils.deleteContents(mTestProc);
-        }
-
-        super.tearDown();
-    }
-
-    public void testNetworkStatsDetail() throws Exception {
-        stageFile(R.raw.xt_qtaguid_typical, new File(mTestProc, "net/xt_qtaguid/stats"));
-
-        final NetworkStats stats = mFactory.readNetworkStatsDetail();
-        assertEquals(70, stats.size());
-        assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 18621L, 2898L);
-        assertStatsEntry(stats, "wlan0", 10011, SET_DEFAULT, 0x0, 35777L, 5718L);
-        assertStatsEntry(stats, "wlan0", 10021, SET_DEFAULT, 0x7fffff01, 562386L, 49228L);
-        assertStatsEntry(stats, "rmnet1", 10021, SET_DEFAULT, 0x30100000, 219110L, 227423L);
-        assertStatsEntry(stats, "rmnet2", 10001, SET_DEFAULT, 0x0, 1125899906842624L, 984L);
-    }
-
-    public void testKernelTags() throws Exception {
-        assertEquals(0, kernelToTag("0x0000000000000000"));
-        assertEquals(0x32, kernelToTag("0x0000003200000000"));
-        assertEquals(2147483647, kernelToTag("0x7fffffff00000000"));
-        assertEquals(0, kernelToTag("0x0000000000000000"));
-        assertEquals(2147483136, kernelToTag("0x7FFFFE0000000000"));
-
-        assertEquals(0, kernelToTag("0x0"));
-        assertEquals(0, kernelToTag("0xf00d"));
-        assertEquals(1, kernelToTag("0x100000000"));
-        assertEquals(14438007, kernelToTag("0xdc4e7700000000"));
-        assertEquals(TrafficStats.TAG_SYSTEM_DOWNLOAD, kernelToTag("0xffffff0100000000"));
-    }
-
-    public void testNetworkStatsWithSet() throws Exception {
-        stageFile(R.raw.xt_qtaguid_typical, new File(mTestProc, "net/xt_qtaguid/stats"));
-
-        final NetworkStats stats = mFactory.readNetworkStatsDetail();
-        assertEquals(70, stats.size());
-        assertStatsEntry(stats, "rmnet1", 10021, SET_DEFAULT, 0x30100000, 219110L, 578L, 227423L, 676L);
-        assertStatsEntry(stats, "rmnet1", 10021, SET_FOREGROUND, 0x30100000, 742L, 3L, 1265L, 3L);
-    }
-
-    public void testNetworkStatsSingle() throws Exception {
-        stageFile(R.raw.xt_qtaguid_iface_typical, new File(mTestProc, "net/xt_qtaguid/iface_stat_all"));
-
-        final NetworkStats stats = mFactory.readNetworkStatsSummaryDev();
-        assertEquals(6, stats.size());
-        assertStatsEntry(stats, "rmnet0", UID_ALL, SET_ALL, TAG_NONE, 2112L, 24L, 700L, 10L);
-        assertStatsEntry(stats, "test1", UID_ALL, SET_ALL, TAG_NONE, 6L, 8L, 10L, 12L);
-        assertStatsEntry(stats, "test2", UID_ALL, SET_ALL, TAG_NONE, 1L, 2L, 3L, 4L);
-    }
-
-    public void testNetworkStatsXt() throws Exception {
-        stageFile(R.raw.xt_qtaguid_iface_fmt_typical,
-                new File(mTestProc, "net/xt_qtaguid/iface_stat_fmt"));
-
-        final NetworkStats stats = mFactory.readNetworkStatsSummaryXt();
-        assertEquals(3, stats.size());
-        assertStatsEntry(stats, "rmnet0", UID_ALL, SET_ALL, TAG_NONE, 6824L, 16L, 5692L, 10L);
-        assertStatsEntry(stats, "rmnet1", UID_ALL, SET_ALL, TAG_NONE, 11153922L, 8051L, 190226L, 2468L);
-        assertStatsEntry(stats, "rmnet2", UID_ALL, SET_ALL, TAG_NONE, 4968L, 35L, 3081L, 39L);
-    }
-
-    /**
-     * Copy a {@link Resources#openRawResource(int)} into {@link File} for
-     * testing purposes.
-     */
-    private void stageFile(int rawId, File file) throws Exception {
-        new File(file.getParent()).mkdirs();
-        InputStream in = null;
-        OutputStream out = null;
-        try {
-            in = getContext().getResources().openRawResource(rawId);
-            out = new FileOutputStream(file);
-            Streams.copy(in, out);
-        } finally {
-            IoUtils.closeQuietly(in);
-            IoUtils.closeQuietly(out);
-        }
-    }
-
-    private void stageLong(long value, File file) throws Exception {
-        new File(file.getParent()).mkdirs();
-        FileWriter out = null;
-        try {
-            out = new FileWriter(file);
-            out.write(Long.toString(value));
-        } finally {
-            IoUtils.closeQuietly(out);
-        }
-    }
-
-    private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set,
-            int tag, long rxBytes, long txBytes) {
-        final int i = stats.findIndex(iface, uid, set, tag, ROAMING_NO);
-        final NetworkStats.Entry entry = stats.getValues(i, null);
-        assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
-        assertEquals("unexpected txBytes", txBytes, entry.txBytes);
-    }
-
-    private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set,
-            int tag, long rxBytes, long rxPackets, long txBytes, long txPackets) {
-        final int i = stats.findIndex(iface, uid, set, tag, ROAMING_NO);
-        final NetworkStats.Entry entry = stats.getValues(i, null);
-        assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
-        assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
-        assertEquals("unexpected txBytes", txBytes, entry.txBytes);
-        assertEquals("unexpected txPackets", txPackets, entry.txPackets);
-    }
-
-}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 2693272..4b12bc4 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -29,13 +29,6 @@
 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_VALIDATED;
-import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
-import static android.net.NetworkPolicyManager.RULE_ALLOW_METERED;
-import static android.net.NetworkPolicyManager.RULE_NONE;
-import static android.net.NetworkPolicyManager.RULE_REJECT_ALL;
-import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
-import static android.net.NetworkPolicyManager.RULE_TEMPORARY_ALLOW_METERED;
-import static android.net.NetworkPolicyManager.uidRulesToString;
 
 import android.annotation.Nullable;
 import android.app.BroadcastOptions;
@@ -62,6 +55,7 @@
 import android.net.Network;
 import android.net.NetworkAgent;
 import android.net.NetworkCapabilities;
+import android.net.MatchAllNetworkSpecifier;
 import android.net.NetworkConfig;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
@@ -78,7 +72,7 @@
 import android.net.metrics.DefaultNetworkEvent;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.NetworkEvent;
-import android.net.util.AvoidBadWifiTracker;
+import android.net.util.MultinetworkPolicyTracker;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -96,7 +90,6 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -104,7 +97,6 @@
 import android.security.KeyStore;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
-import android.util.ArraySet;
 import android.util.LocalLog;
 import android.util.LocalLog.ReadOnlyLocalLog;
 import android.util.Log;
@@ -125,13 +117,16 @@
 import com.android.internal.net.VpnInfo;
 import com.android.internal.net.VpnProfile;
 import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.MessageUtils;
 import com.android.internal.util.WakeupMessage;
 import com.android.internal.util.XmlUtils;
+import com.android.server.LocalServices;
 import com.android.server.am.BatteryStatsService;
 import com.android.server.connectivity.DataConnectionStats;
 import com.android.server.connectivity.KeepaliveTracker;
+import com.android.server.connectivity.MockableSystemProperties;
 import com.android.server.connectivity.Nat464Xlat;
 import com.android.server.connectivity.LingerMonitor;
 import com.android.server.connectivity.NetworkAgentInfo;
@@ -145,6 +140,7 @@
 import com.android.server.connectivity.Vpn;
 import com.android.server.net.BaseNetworkObserver;
 import com.android.server.net.LockdownVpnTracker;
+import com.android.server.net.NetworkPolicyManagerInternal;
 
 import com.google.android.collect.Lists;
 
@@ -179,6 +175,9 @@
         implements PendingIntent.OnFinished {
     private static final String TAG = ConnectivityService.class.getSimpleName();
 
+    public static final String DIAG_ARG = "--diag";
+    public static final String SHORT_ARG = "--short";
+
     private static final boolean DBG = true;
     private static final boolean VDBG = false;
 
@@ -207,6 +206,8 @@
     // See Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS
     private final int mReleasePendingIntentDelayMs;
 
+    private MockableSystemProperties mSystemProperties;
+
     private Tethering mTethering;
 
     private final PermissionMonitor mPermissionMonitor;
@@ -219,18 +220,6 @@
     private boolean mLockdownEnabled;
     private LockdownVpnTracker mLockdownTracker;
 
-    /** Lock around {@link #mUidRules} and {@link #mMeteredIfaces}. */
-    private Object mRulesLock = new Object();
-    /** Currently active network rules by UID. */
-    @GuardedBy("mRulesLock")
-    private SparseIntArray mUidRules = new SparseIntArray();
-    /** Set of ifaces that are costly. */
-    @GuardedBy("mRulesLock")
-    private ArraySet<String> mMeteredIfaces = new ArraySet<>();
-    /** Flag indicating if background data is restricted. */
-    @GuardedBy("mRulesLock")
-    private boolean mRestrictBackground;
-
     final private Context mContext;
     private int mNetworkPreference;
     // 0 is full bad, 100 is full good
@@ -244,6 +233,7 @@
     private INetworkManagementService mNetd;
     private INetworkStatsService mStatsService;
     private INetworkPolicyManager mPolicyManager;
+    private NetworkPolicyManagerInternal mPolicyManagerInternal;
 
     private String mCurrentTcpBufferSizes;
 
@@ -394,16 +384,6 @@
      */
     private static final int EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT = 31;
 
-    /**
-     * Indicates a caller has requested to have its callback invoked with
-     * the latest LinkProperties or NetworkCapabilities.
-     *
-     * arg1 = UID of caller
-     * obj  = NetworkRequest
-     */
-    private static final int EVENT_REQUEST_LINKPROPERTIES  = 32;
-    private static final int EVENT_REQUEST_NETCAPABILITIES = 33;
-
     /** Handler thread used for both of the handlers below. */
     @VisibleForTesting
     protected final HandlerThread mHandlerThread;
@@ -498,7 +478,7 @@
     private final IpConnectivityLog mMetricsLog;
 
     @VisibleForTesting
-    final AvoidBadWifiTracker mAvoidBadWifiTracker;
+    final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
 
     /**
      * Implements support for the legacy "one network per network type" model.
@@ -689,11 +669,6 @@
     }
     private LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker();
 
-    @VisibleForTesting
-    protected HandlerThread createHandlerThread() {
-        return new HandlerThread("ConnectivityServiceThread");
-    }
-
     public ConnectivityService(Context context, INetworkManagementService netManager,
             INetworkStatsService statsService, INetworkPolicyManager policyManager) {
         this(context, netManager, statsService, policyManager, new IpConnectivityLog());
@@ -705,6 +680,8 @@
             IpConnectivityLog logger) {
         if (DBG) log("ConnectivityService starting up");
 
+        mSystemProperties = getSystemProperties();
+
         mMetricsLog = logger;
         mDefaultRequest = createInternetRequestForTransport(-1, NetworkRequest.Type.REQUEST);
         NetworkRequestInfo defaultNRI = new NetworkRequestInfo(null, mDefaultRequest, new Binder());
@@ -714,36 +691,29 @@
         mDefaultMobileDataRequest = createInternetRequestForTransport(
                 NetworkCapabilities.TRANSPORT_CELLULAR, NetworkRequest.Type.BACKGROUND_REQUEST);
 
-        mHandlerThread = createHandlerThread();
+        mHandlerThread = new HandlerThread("ConnectivityServiceThread");
         mHandlerThread.start();
         mHandler = new InternalHandler(mHandlerThread.getLooper());
         mTrackerHandler = new NetworkStateTrackerHandler(mHandlerThread.getLooper());
 
-        // setup our unique device name
-        if (TextUtils.isEmpty(SystemProperties.get("net.hostname"))) {
-            String id = Settings.Secure.getString(context.getContentResolver(),
-                    Settings.Secure.ANDROID_ID);
-            if (id != null && id.length() > 0) {
-                String name = new String("android-").concat(id);
-                SystemProperties.set("net.hostname", name);
-            }
-        }
-
         mReleasePendingIntentDelayMs = Settings.Secure.getInt(context.getContentResolver(),
                 Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, 5_000);
 
-        mLingerDelayMs = SystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS);
+        mLingerDelayMs = mSystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS);
 
         mContext = checkNotNull(context, "missing Context");
         mNetd = checkNotNull(netManager, "missing INetworkManagementService");
         mStatsService = checkNotNull(statsService, "missing INetworkStatsService");
         mPolicyManager = checkNotNull(policyManager, "missing INetworkPolicyManager");
+        mPolicyManagerInternal = checkNotNull(
+                LocalServices.getService(NetworkPolicyManagerInternal.class),
+                "missing NetworkPolicyManagerInternal");
+
         mKeyStore = KeyStore.getInstance();
         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
 
         try {
-            mPolicyManager.setConnectivityListener(mPolicyListener);
-            mRestrictBackground = mPolicyManager.getRestrictBackground();
+            mPolicyManager.registerListener(mPolicyListener);
         } catch (RemoteException e) {
             // ouch, no rules updates means some processes may never get network
             loge("unable to register INetworkPolicyListener" + e);
@@ -759,7 +729,7 @@
         mNetConfigs = new NetworkConfig[ConnectivityManager.MAX_NETWORK_TYPE+1];
 
         // TODO: What is the "correct" way to do determine if this is a wifi only device?
-        boolean wifiOnly = SystemProperties.getBoolean("ro.radio.noril", false);
+        boolean wifiOnly = mSystemProperties.getBoolean("ro.radio.noril", false);
         log("wifiOnly=" + wifiOnly);
         String[] naStrings = context.getResources().getStringArray(
                 com.android.internal.R.array.networkAttributes);
@@ -812,10 +782,11 @@
             }
         }
 
-        mTestMode = SystemProperties.get("cm.test.mode").equals("true")
-                && SystemProperties.get("ro.build.type").equals("eng");
+        mTestMode = mSystemProperties.get("cm.test.mode").equals("true")
+                && mSystemProperties.get("ro.build.type").equals("eng");
 
-        mTethering = new Tethering(mContext, mNetd, statsService, mPolicyManager);
+        mTethering = new Tethering(mContext, mNetd, statsService, mPolicyManager,
+                                   IoThread.get().getLooper(), new MockableSystemProperties());
 
         mPermissionMonitor = new PermissionMonitor(mContext, mNetd);
 
@@ -828,6 +799,8 @@
         intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
         mContext.registerReceiverAsUser(
                 mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
+        mContext.registerReceiverAsUser(mUserPresentReceiver, UserHandle.SYSTEM,
+                new IntentFilter(Intent.ACTION_USER_PRESENT), null, null);
 
         try {
             mNetd.registerObserver(mTethering);
@@ -862,9 +835,9 @@
                 LingerMonitor.DEFAULT_NOTIFICATION_RATE_LIMIT_MILLIS);
         mLingerMonitor = new LingerMonitor(mContext, mNotifier, dailyLimit, rateLimit);
 
-        mAvoidBadWifiTracker = createAvoidBadWifiTracker(
+        mMultinetworkPolicyTracker = createMultinetworkPolicyTracker(
                 mContext, mHandler, () -> rematchForAvoidBadWifiUpdate());
-        mAvoidBadWifiTracker.start();
+        mMultinetworkPolicyTracker.start();
     }
 
     private NetworkRequest createInternetRequestForTransport(
@@ -891,7 +864,7 @@
 
     private void handleMobileDataAlwaysOn() {
         final boolean enable = (Settings.Global.getInt(
-                mContext.getContentResolver(), Settings.Global.MOBILE_DATA_ALWAYS_ON, 0) == 1);
+                mContext.getContentResolver(), Settings.Global.MOBILE_DATA_ALWAYS_ON, 1) == 1);
         final boolean isEnabled = (mNetworkRequests.get(mDefaultMobileDataRequest) != null);
         if (enable == isEnabled) {
             return;  // Nothing to do.
@@ -1011,51 +984,22 @@
     private boolean isNetworkWithLinkPropertiesBlocked(LinkProperties lp, int uid,
             boolean ignoreBlocked) {
         // Networks aren't blocked when ignoring blocked status
-        if (ignoreBlocked) return false;
+        if (ignoreBlocked) {
+            return false;
+        }
         // Networks are never blocked for system services
-        if (isSystem(uid)) return false;
-
-        final boolean networkMetered;
-        final int uidRules;
-
+        // TODO: consider moving this check to NetworkPolicyManagerInternal.isUidNetworkingBlocked.
+        if (isSystem(uid)) {
+            return false;
+        }
         synchronized (mVpns) {
             final Vpn vpn = mVpns.get(UserHandle.getUserId(uid));
             if (vpn != null && vpn.isBlockingUid(uid)) {
                 return true;
             }
         }
-
         final String iface = (lp == null ? "" : lp.getInterfaceName());
-        synchronized (mRulesLock) {
-            networkMetered = mMeteredIfaces.contains(iface);
-            uidRules = mUidRules.get(uid, RULE_NONE);
-        }
-
-        boolean allowed = true;
-        // Check Data Saver Mode first...
-        if (networkMetered) {
-            if ((uidRules & RULE_REJECT_METERED) != 0) {
-                if (LOGD_RULES) Log.d(TAG, "uid " + uid + " is blacklisted");
-                // Explicitly blacklisted.
-                allowed = false;
-            } else {
-                allowed = !mRestrictBackground
-                      || (uidRules & RULE_ALLOW_METERED) != 0
-                      || (uidRules & RULE_TEMPORARY_ALLOW_METERED) != 0;
-                if (LOGD_RULES) Log.d(TAG, "allowed status for uid " + uid + " when"
-                        + " mRestrictBackground=" + mRestrictBackground
-                        + ", whitelisted=" + ((uidRules & RULE_ALLOW_METERED) != 0)
-                        + ", tempWhitelist= + ((uidRules & RULE_TEMPORARY_ALLOW_METERED) != 0)"
-                        + ": " + allowed);
-            }
-        }
-        // ...then power restrictions.
-        if (allowed) {
-            allowed = (uidRules & RULE_REJECT_ALL) == 0;
-            if (LOGD_RULES) Log.d(TAG, "allowed status for uid " + uid + " when"
-                    + " rule is " + uidRulesToString(uidRules) + ": " + allowed);
-        }
-        return !allowed;
+        return mPolicyManagerInternal.isUidNetworkingBlocked(uid, iface);
     }
 
     private void maybeLogBlockedNetworkInfo(NetworkInfo ni, int uid) {
@@ -1328,13 +1272,16 @@
     @Override
     public LinkProperties getLinkProperties(Network network) {
         enforceAccessPermission();
-        NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
-        if (nai != null) {
-            synchronized (nai) {
-                return new LinkProperties(nai.linkProperties);
-            }
+        return getLinkProperties(getNetworkAgentInfoForNetwork(network));
+    }
+
+    private LinkProperties getLinkProperties(NetworkAgentInfo nai) {
+        if (nai == null) {
+            return null;
         }
-        return null;
+        synchronized (nai) {
+            return new LinkProperties(nai.linkProperties);
+        }
     }
 
     private NetworkCapabilities getNetworkCapabilitiesInternal(NetworkAgentInfo nai) {
@@ -1501,76 +1448,24 @@
         return true;
     }
 
-    private INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() {
+    private final INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() {
         @Override
         public void onUidRulesChanged(int uid, int uidRules) {
-            // caller is NPMS, since we only register with them
-            if (LOGD_RULES) {
-                log("onUidRulesChanged(uid=" + uid + ", uidRules=" + uidRules + ")");
-            }
-
-            synchronized (mRulesLock) {
-                // skip update when we've already applied rules
-                final int oldRules = mUidRules.get(uid, RULE_NONE);
-                if (oldRules == uidRules) return;
-
-                if (uidRules == RULE_NONE) {
-                    mUidRules.delete(uid);
-                } else {
-                    mUidRules.put(uid, uidRules);
-                }
-            }
-
             // TODO: notify UID when it has requested targeted updates
         }
-
         @Override
         public void onMeteredIfacesChanged(String[] meteredIfaces) {
-            // caller is NPMS, since we only register with them
-            if (LOGD_RULES) {
-                log("onMeteredIfacesChanged(ifaces=" + Arrays.toString(meteredIfaces) + ")");
-            }
-
-            synchronized (mRulesLock) {
-                mMeteredIfaces.clear();
-                for (String iface : meteredIfaces) {
-                    mMeteredIfaces.add(iface);
-                }
-            }
         }
-
         @Override
         public void onRestrictBackgroundChanged(boolean restrictBackground) {
-            // caller is NPMS, since we only register with them
-            if (LOGD_RULES) {
-                log("onRestrictBackgroundChanged(restrictBackground=" + restrictBackground + ")");
-            }
-
-            synchronized (mRulesLock) {
-                mRestrictBackground = restrictBackground;
-            }
-
+            // TODO: relocate this specific callback in Tethering.
             if (restrictBackground) {
                 log("onRestrictBackgroundChanged(true): disabling tethering");
                 mTethering.untetherAll();
             }
         }
-
         @Override
-        public void onRestrictBackgroundWhitelistChanged(int uid, boolean whitelisted) {
-            if (LOGD_RULES) {
-                // caller is NPMS, since we only register with them
-                log("onRestrictBackgroundWhitelistChanged(uid=" + uid + ", whitelisted="
-                        + whitelisted + ")");
-            }
-        }
-        @Override
-        public void onRestrictBackgroundBlacklistChanged(int uid, boolean blacklisted) {
-            if (LOGD_RULES) {
-                // caller is NPMS, since we only register with them
-                log("onRestrictBackgroundBlacklistChanged(uid=" + uid + ", blacklisted="
-                        + blacklisted + ")");
-            }
+        public void onUidPoliciesChanged(int uid, int uidPolicies) {
         }
     };
 
@@ -1812,11 +1707,14 @@
     private void updateMtu(LinkProperties newLp, LinkProperties oldLp) {
         final String iface = newLp.getInterfaceName();
         final int mtu = newLp.getMtu();
+        if (oldLp == null && mtu == 0) {
+            // Silently ignore unset MTU value.
+            return;
+        }
         if (oldLp != null && newLp.isIdenticalMtu(oldLp)) {
             if (VDBG) log("identical MTU - not setting");
             return;
         }
-
         if (LinkProperties.isValidMtu(mtu, newLp.hasGlobalIPv6Address()) == false) {
             if (mtu != 0) loge("Unexpected mtu value: " + mtu + ", " + iface);
             return;
@@ -1841,8 +1739,8 @@
 
     // Overridden for testing purposes to avoid writing to SystemProperties.
     @VisibleForTesting
-    protected int getDefaultTcpRwnd() {
-        return SystemProperties.getInt(DEFAULT_TCP_RWND_KEY, 0);
+    protected MockableSystemProperties getSystemProperties() {
+        return new MockableSystemProperties();
     }
 
     private void updateTcpBufferSizes(NetworkAgentInfo nai) {
@@ -1880,10 +1778,11 @@
         }
 
         Integer rwndValue = Settings.Global.getInt(mContext.getContentResolver(),
-            Settings.Global.TCP_DEFAULT_INIT_RWND, getDefaultTcpRwnd());
+            Settings.Global.TCP_DEFAULT_INIT_RWND,
+                    mSystemProperties.getInt("net.tcp.default_init_rwnd", 0));
         final String sysctlKey = "sys.sysctl.tcp_def_init_rwnd";
         if (rwndValue != 0) {
-            SystemProperties.set(sysctlKey, rwndValue.toString());
+            mSystemProperties.set(sysctlKey, rwndValue.toString());
         }
     }
 
@@ -1907,7 +1806,7 @@
 
     @Override
     public int getRestoreDefaultNetworkDelay(int networkType) {
-        String restoreDefaultNetworkDelayStr = SystemProperties.get(
+        String restoreDefaultNetworkDelayStr = mSystemProperties.get(
                 NETWORK_RESTORE_DELAY_PROP_NAME);
         if(restoreDefaultNetworkDelayStr != null &&
                 restoreDefaultNetworkDelayStr.length() != 0) {
@@ -1954,16 +1853,9 @@
     @Override
     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
-        if (mContext.checkCallingOrSelfPermission(
-                android.Manifest.permission.DUMP)
-                != PackageManager.PERMISSION_GRANTED) {
-            pw.println("Permission Denial: can't dump ConnectivityService " +
-                    "from from pid=" + Binder.getCallingPid() + ", uid=" +
-                    Binder.getCallingUid());
-            return;
-        }
+        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
-        if (argsContain(args, "--diag")) {
+        if (argsContain(args, DIAG_ARG)) {
             dumpNetworkDiagnostics(pw);
             return;
         }
@@ -2009,33 +1901,6 @@
         pw.decreaseIndent();
         pw.println();
 
-        pw.println("Metered Interfaces:");
-        pw.increaseIndent();
-        for (String value : mMeteredIfaces) {
-            pw.println(value);
-        }
-        pw.decreaseIndent();
-        pw.println();
-
-        pw.print("Restrict background: ");
-        pw.println(mRestrictBackground);
-        pw.println();
-
-        pw.println("Status for known UIDs:");
-        pw.increaseIndent();
-        final int size = mUidRules.size();
-        for (int i = 0; i < size; i++) {
-            final int uid = mUidRules.keyAt(i);
-            pw.print("UID=");
-            pw.print(uid);
-            final int uidRules = mUidRules.get(uid, RULE_NONE);
-            pw.print(" rules=");
-            pw.print(uidRulesToString(uidRules));
-            pw.println();
-        }
-        pw.println();
-        pw.decreaseIndent();
-
         pw.println("Network Requests:");
         pw.increaseIndent();
         for (NetworkRequestInfo nri : mNetworkRequests.values()) {
@@ -2076,7 +1941,7 @@
             pw.decreaseIndent();
         }
 
-        if (argsContain(args, "--short") == false) {
+        if (argsContain(args, SHORT_ARG) == false) {
             pw.println();
             synchronized (mValidationLogs) {
                 pw.println("mValidationLogs (most recent first):");
@@ -2582,42 +2447,28 @@
         return nri;
     }
 
-    private void handleRequestCallbackUpdate(NetworkRequest request, int callingUid,
-            String description, int callbackType) {
-        final NetworkRequestInfo nri = getNriForAppRequest(request, callingUid, description);
-        if (nri == null) return;
-
-        final NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
-        // The network that is satisfying this request may have changed since
-        // the application requested the update.
-        //
-        // - If the request is no longer satisfied, don't send any updates.
-        // - If the request is satisfied by a different network, it is the
-        //   caller's responsibility to check that the Network object in the
-        //   callback matches the network that was returned in the last
-        //   onAvailable() callback for this request.
-        if (nai == null) return;
-        callCallbackForRequest(nri, nai, callbackType, 0);
-    }
-
-    private void handleRequestLinkProperties(NetworkRequest request, int callingUid) {
-        handleRequestCallbackUpdate(request, callingUid,
-                "request LinkProperties", ConnectivityManager.CALLBACK_IP_CHANGED);
-    }
-
-    private void handleRequestNetworkCapabilities(NetworkRequest request, int callingUid) {
-        handleRequestCallbackUpdate(request, callingUid,
-                "request NetworkCapabilities", ConnectivityManager.CALLBACK_CAP_CHANGED);
+    private void handleTimedOutNetworkRequest(final NetworkRequestInfo nri) {
+        if (mNetworkRequests.get(nri.request) != null && mNetworkForRequestId.get(
+                nri.request.requestId) == null) {
+            handleRemoveNetworkRequest(nri, ConnectivityManager.CALLBACK_UNAVAIL);
+        }
     }
 
     private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) {
         final NetworkRequestInfo nri = getNriForAppRequest(
                 request, callingUid, "release NetworkRequest");
-        if (nri == null) return;
+        if (nri != null) {
+            handleRemoveNetworkRequest(nri, ConnectivityManager.CALLBACK_RELEASED);
+        }
+    }
 
-        if (VDBG || (DBG && nri.request.isRequest())) log("releasing " + request);
+    private void handleRemoveNetworkRequest(final NetworkRequestInfo nri, final int whichCallback) {
+        final String logCallbackType = ConnectivityManager.getCallbackName(whichCallback);
+        if (VDBG || (DBG && nri.request.isRequest())) {
+            log("releasing " + nri.request + " (" + logCallbackType + ")");
+        }
         nri.unlinkDeathRecipient();
-        mNetworkRequests.remove(request);
+        mNetworkRequests.remove(nri.request);
         synchronized (mUidToNetworkRequestCount) {
             int requests = mUidToNetworkRequestCount.get(nri.mUid, 0);
             if (requests < 1) {
@@ -2711,7 +2562,7 @@
                 }
             }
         }
-        callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_RELEASED, 0);
+        callCallbackForRequest(nri, null, whichCallback, 0);
     }
 
     @Override
@@ -2788,8 +2639,19 @@
                 PROMPT_UNVALIDATED_DELAY_MS);
     }
 
+    @Override
+    public void startCaptivePortalApp(Network network) {
+        enforceConnectivityInternalPermission();
+        mHandler.post(() -> {
+            NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+            if (nai == null) return;
+            if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) return;
+            nai.networkMonitor.sendMessage(NetworkMonitor.CMD_LAUNCH_CAPTIVE_PORTAL_APP);
+        });
+    }
+
     public boolean avoidBadWifi() {
-        return mAvoidBadWifiTracker.currentValue();
+        return mMultinetworkPolicyTracker.getAvoidBadWifi();
     }
 
     private void rematchForAvoidBadWifiUpdate() {
@@ -2802,9 +2664,9 @@
     }
 
     // TODO: Evaluate whether this is of interest to other consumers of
-    // AvoidBadWifiTracker and worth moving out of here.
+    // MultinetworkPolicyTracker and worth moving out of here.
     private void dumpAvoidBadWifiSettings(IndentingPrintWriter pw) {
-        final boolean configRestrict = mAvoidBadWifiTracker.configRestrictsAvoidBadWifi();
+        final boolean configRestrict = mMultinetworkPolicyTracker.configRestrictsAvoidBadWifi();
         if (!configRestrict) {
             pw.println("Bad Wi-Fi avoidance: unrestricted");
             return;
@@ -2814,7 +2676,7 @@
         pw.increaseIndent();
         pw.println("Config restrict:   " + configRestrict);
 
-        final String value = mAvoidBadWifiTracker.getSettingsValue();
+        final String value = mMultinetworkPolicyTracker.getAvoidBadWifiSetting();
         String description;
         // Can't use a switch statement because strings are legal case labels, but null is not.
         if ("0".equals(value)) {
@@ -2882,11 +2744,23 @@
         if (DBG) log("handleNetworkUnvalidated " + nai.name() + " cap=" + nc);
 
         if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
-            mAvoidBadWifiTracker.shouldNotifyWifiUnvalidated()) {
+            mMultinetworkPolicyTracker.shouldNotifyWifiUnvalidated()) {
             showValidationNotification(nai, NotificationType.LOST_INTERNET);
         }
     }
 
+    @Override
+    public int getMultipathPreference(Network network) {
+        enforceAccessPermission();
+
+        NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+        if (nai != null && !nai.networkInfo.isMetered()) {
+            return ConnectivityManager.MULTIPATH_PREFERENCE_UNMETERED;
+        }
+
+        return mMultinetworkPolicyTracker.getMeteredMultipathPreference();
+    }
+
     private class InternalHandler extends Handler {
         public InternalHandler(Looper looper) {
             super(looper);
@@ -2948,6 +2822,11 @@
                     handleRegisterNetworkRequestWithIntent(msg);
                     break;
                 }
+                case EVENT_TIMEOUT_NETWORK_REQUEST: {
+                    NetworkRequestInfo nri = (NetworkRequestInfo) msg.obj;
+                    handleTimedOutNetworkRequest(nri);
+                    break;
+                }
                 case EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT: {
                     handleReleaseNetworkRequestWithIntent((PendingIntent) msg.obj, msg.arg1);
                     break;
@@ -2972,12 +2851,6 @@
                     handleMobileDataAlwaysOn();
                     break;
                 }
-                case EVENT_REQUEST_LINKPROPERTIES:
-                    handleRequestLinkProperties((NetworkRequest) msg.obj, msg.arg1);
-                    break;
-                case EVENT_REQUEST_NETCAPABILITIES:
-                    handleRequestNetworkCapabilities((NetworkRequest) msg.obj, msg.arg1);
-                    break;
                 // Sent by KeepaliveTracker to process an app request on the state machine thread.
                 case NetworkAgent.CMD_START_PACKET_KEEPALIVE: {
                     mKeepaliveTracker.handleStartKeepalive(msg);
@@ -3111,20 +2984,26 @@
     @Override
     public boolean isTetheringSupported() {
         enforceTetherAccessPermission();
-        int defaultVal = (SystemProperties.get("ro.tether.denied").equals("true") ? 0 : 1);
+        int defaultVal = (mSystemProperties.get("ro.tether.denied").equals("true") ? 0 : 1);
         boolean tetherEnabledInSettings = (Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.TETHER_SUPPORTED, defaultVal) != 0)
                 && !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
-        return tetherEnabledInSettings && mUserManager.isAdminUser() &&
-                ((mTethering.getTetherableUsbRegexs().length != 0 ||
-                mTethering.getTetherableWifiRegexs().length != 0 ||
-                mTethering.getTetherableBluetoothRegexs().length != 0) &&
-                mTethering.getUpstreamIfaceTypes().length != 0);
+
+        // Elevate to system UID to avoid caller requiring MANAGE_USERS permission.
+        boolean adminUser = false;
+        final long token = Binder.clearCallingIdentity();
+        try {
+            adminUser = mUserManager.isAdminUser();
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+
+        return tetherEnabledInSettings && adminUser &&
+               mTethering.hasTetherableConfiguration();
     }
 
     @Override
-    public void startTethering(int type, ResultReceiver receiver,
-            boolean showProvisioningUi) {
+    public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi) {
         ConnectivityManager.enforceTetherChangePermission(mContext);
         if (!isTetheringSupported()) {
             receiver.send(ConnectivityManager.TETHER_ERROR_UNSUPPORTED, null);
@@ -3170,7 +3049,8 @@
         enforceAccessPermission();
         enforceInternetPermission();
 
-        NetworkAgentInfo nai;
+        // TODO: execute this logic on ConnectivityService handler.
+        final NetworkAgentInfo nai;
         if (network == null) {
             nai = getDefaultNetwork();
         } else {
@@ -3181,21 +3061,24 @@
             return;
         }
         // Revalidate if the app report does not match our current validated state.
-        if (hasConnectivity == nai.lastValidated) return;
+        if (hasConnectivity == nai.lastValidated) {
+            return;
+        }
         final int uid = Binder.getCallingUid();
         if (DBG) {
             log("reportNetworkConnectivity(" + nai.network.netId + ", " + hasConnectivity +
                     ") by " + uid);
         }
-        synchronized (nai) {
-            // Validating a network that has not yet connected could result in a call to
-            // rematchNetworkAndRequests() which is not meant to work on such networks.
-            if (!nai.everConnected) return;
-
-            if (isNetworkWithLinkPropertiesBlocked(nai.linkProperties, uid, false)) return;
-
-            nai.networkMonitor.sendMessage(NetworkMonitor.CMD_FORCE_REEVALUATION, uid);
+        // Validating a network that has not yet connected could result in a call to
+        // rematchNetworkAndRequests() which is not meant to work on such networks.
+        if (!nai.everConnected) {
+            return;
         }
+        LinkProperties lp = getLinkProperties(nai);
+        if (isNetworkWithLinkPropertiesBlocked(lp, uid, false)) {
+            return;
+        }
+        nai.networkMonitor.sendMessage(NetworkMonitor.CMD_FORCE_REEVALUATION, uid);
     }
 
     private ProxyInfo getDefaultProxy() {
@@ -3467,6 +3350,10 @@
         Slog.e(TAG, s);
     }
 
+    private static void loge(String s, Throwable t) {
+        Slog.e(TAG, s, t);
+    }
+
     private static <T> T checkNotNull(T value, String message) {
         if (value == null) {
             throw new NullPointerException(message);
@@ -3655,7 +3542,12 @@
         // Tear down existing lockdown if profile was removed
         mLockdownEnabled = LockdownVpnTracker.isEnabled();
         if (mLockdownEnabled) {
-            final String profileName = new String(mKeyStore.get(Credentials.LOCKDOWN_VPN));
+            byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN);
+            if (profileTag == null) {
+                Slog.e(TAG, "Lockdown VPN configured but cannot be read from keystore");
+                return false;
+            }
+            String profileName = new String(profileTag);
             final VpnProfile profile = VpnProfile.decode(
                     profileName, mKeyStore.get(Credentials.VPN + profileName));
             if (profile == null) {
@@ -3994,6 +3886,16 @@
         }
     };
 
+    private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // Try creating lockdown tracker, since user present usually means
+            // unlocked keystore.
+            updateLockdownVpn();
+            mContext.unregisterReceiver(this);
+        }
+    };
+
     private final HashMap<Messenger, NetworkFactoryInfo> mNetworkFactoryInfos =
             new HashMap<Messenger, NetworkFactoryInfo>();
     private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests =
@@ -4157,15 +4059,12 @@
         }
         ensureRequestableCapabilities(networkCapabilities);
 
-        if (timeoutMs < 0 || timeoutMs > ConnectivityManager.MAX_NETWORK_REQUEST_TIMEOUT_MS) {
+        if (timeoutMs < 0) {
             throw new IllegalArgumentException("Bad timeout specified");
         }
 
-        if (NetworkCapabilities.MATCH_ALL_REQUESTS_NETWORK_SPECIFIER
-                .equals(networkCapabilities.getNetworkSpecifier())) {
-            throw new IllegalArgumentException("Invalid network specifier - must not be '"
-                    + NetworkCapabilities.MATCH_ALL_REQUESTS_NETWORK_SPECIFIER + "'");
-        }
+        MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(
+                networkCapabilities.getNetworkSpecifier());
 
         NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
                 nextNetworkRequestId(), type);
@@ -4212,20 +4111,16 @@
     private void enforceMeteredApnPolicy(NetworkCapabilities networkCapabilities) {
         final int uid = Binder.getCallingUid();
         if (isSystem(uid)) {
+            // Exemption for system uid.
             return;
         }
-        // if UID is restricted, don't allow them to bring up metered APNs
-        if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED) == false) {
-            final int uidRules;
-            synchronized(mRulesLock) {
-                uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
-            }
-            if (mRestrictBackground && (uidRules & RULE_ALLOW_METERED) == 0
-                    && (uidRules & RULE_TEMPORARY_ALLOW_METERED) == 0) {
-                // we could silently fail or we can filter the available nets to only give
-                // them those they have access to.  Chose the more useful option.
-                networkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED);
-            }
+        if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED)) {
+            // Policy already enforced.
+            return;
+        }
+        if (mPolicyManagerInternal.isUidRestrictedOnMeteredNetworks(uid)) {
+            // If UID is restricted, don't allow them to bring up metered APNs.
+            networkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED);
         }
     }
 
@@ -4238,6 +4133,9 @@
         enforceMeteredApnPolicy(networkCapabilities);
         ensureRequestableCapabilities(networkCapabilities);
 
+        MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(
+                networkCapabilities.getNetworkSpecifier());
+
         NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
                 nextNetworkRequestId(), NetworkRequest.Type.REQUEST);
         NetworkRequestInfo nri = new NetworkRequestInfo(networkRequest, operation);
@@ -4299,6 +4197,9 @@
             nc.addCapability(NET_CAPABILITY_FOREGROUND);
         }
 
+        MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(
+                networkCapabilities.getNetworkSpecifier());
+
         NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
                 NetworkRequest.Type.LISTEN);
         NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder);
@@ -4316,6 +4217,9 @@
             enforceAccessPermission();
         }
 
+        MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(
+                networkCapabilities.getNetworkSpecifier());
+
         NetworkRequest networkRequest = new NetworkRequest(
                 new NetworkCapabilities(networkCapabilities), TYPE_NONE, nextNetworkRequestId(),
                 NetworkRequest.Type.LISTEN);
@@ -4326,22 +4230,6 @@
     }
 
     @Override
-    public void requestLinkProperties(NetworkRequest networkRequest) {
-        ensureNetworkRequestHasType(networkRequest);
-        if (networkRequest.type == NetworkRequest.Type.LISTEN) return;
-        mHandler.sendMessage(mHandler.obtainMessage(
-                EVENT_REQUEST_LINKPROPERTIES, getCallingUid(), 0, networkRequest));
-    }
-
-    @Override
-    public void requestNetworkCapabilities(NetworkRequest networkRequest) {
-        ensureNetworkRequestHasType(networkRequest);
-        if (networkRequest.type == NetworkRequest.Type.LISTEN) return;
-        mHandler.sendMessage(mHandler.obtainMessage(
-                EVENT_REQUEST_NETCAPABILITIES, getCallingUid(), 0, networkRequest));
-    }
-
-    @Override
     public void releaseNetworkRequest(NetworkRequest networkRequest) {
         ensureNetworkRequestHasType(networkRequest);
         mHandler.sendMessage(mHandler.obtainMessage(
@@ -4603,17 +4491,24 @@
         int last = 0;
         for (InetAddress dns : dnses) {
             ++last;
-            String key = "net.dns" + last;
-            String value = dns.getHostAddress();
-            SystemProperties.set(key, value);
+            setNetDnsProperty(last, dns.getHostAddress());
         }
         for (int i = last + 1; i <= mNumDnsEntries; ++i) {
-            String key = "net.dns" + i;
-            SystemProperties.set(key, "");
+            setNetDnsProperty(i, "");
         }
         mNumDnsEntries = last;
     }
 
+    private void setNetDnsProperty(int which, String value) {
+        final String key = "net.dns" + which;
+        // Log and forget errors setting unsupported properties.
+        try {
+            mSystemProperties.set(key, value);
+        } catch (Exception e) {
+            Log.e(TAG, "Error setting unsupported net.dns property: ", e);
+        }
+    }
+
     private String getNetworkPermission(NetworkCapabilities nc) {
         // TODO: make these permission strings AIDL constants instead.
         if (!nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) {
@@ -4853,7 +4748,7 @@
             if (!nr.isListen()) continue;
             if (nai.satisfies(nr) && !nai.isSatisfyingRequest(nr.requestId)) {
                 nai.addRequest(nr);
-                notifyNetworkCallback(nai, nri);
+                notifyNetworkAvailable(nai, nri);
             }
         }
     }
@@ -5035,7 +4930,7 @@
 
         // do this after the default net is switched, but
         // before LegacyTypeTracker sends legacy broadcasts
-        for (NetworkRequestInfo nri : addedRequests) notifyNetworkCallback(newNetwork, nri);
+        for (NetworkRequestInfo nri : addedRequests) notifyNetworkAvailable(newNetwork, nri);
 
         // Linger any networks that are no longer needed. This should be done after sending the
         // available callback for newNetwork.
@@ -5198,7 +5093,7 @@
     }
 
     private void updateNetworkInfo(NetworkAgentInfo networkAgent, NetworkInfo newInfo) {
-        NetworkInfo.State state = newInfo.getState();
+        final NetworkInfo.State state = newInfo.getState();
         NetworkInfo oldInfo = null;
         final int oldScore = networkAgent.getCurrentScore();
         synchronized (networkAgent) {
@@ -5325,14 +5220,27 @@
         sendUpdatedScoreToFactories(nai);
     }
 
-    // notify only this one new request of the current state
-    protected void notifyNetworkCallback(NetworkAgentInfo nai, NetworkRequestInfo nri) {
-        int notifyType = ConnectivityManager.CALLBACK_AVAILABLE;
-        if (nri.mPendingIntent == null) {
-            callCallbackForRequest(nri, nai, notifyType, 0);
-        } else {
-            sendPendingIntentForRequest(nri, nai, notifyType);
+    // Notify only this one new request of the current state. Transfer all the
+    // current state by calling NetworkCapabilities and LinkProperties callbacks
+    // so that callers can be guaranteed to have as close to atomicity in state
+    // transfer as can be supported by this current API.
+    protected void notifyNetworkAvailable(NetworkAgentInfo nai, NetworkRequestInfo nri) {
+        mHandler.removeMessages(EVENT_TIMEOUT_NETWORK_REQUEST, nri);
+        if (nri.mPendingIntent != null) {
+            sendPendingIntentForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE);
+            // Attempt no subsequent state pushes where intents are involved.
+            return;
         }
+
+        callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE, 0);
+        // Whether a network is currently suspended is also an important
+        // element of state to be transferred (it would not otherwise be
+        // delivered by any currently available mechanism).
+        if (nai.networkInfo.getState() == NetworkInfo.State.SUSPENDED) {
+            callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_SUSPENDED, 0);
+        }
+        callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_CAP_CHANGED, 0);
+        callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_IP_CHANGED, 0);
     }
 
     private void sendLegacyNetworkBroadcast(NetworkAgentInfo nai, DetailedState state, int type) {
@@ -5509,6 +5417,18 @@
                 }
             }
 
+            // Turn Always-on VPN off
+            if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) {
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    mKeyStore.delete(Credentials.LOCKDOWN_VPN);
+                    mLockdownEnabled = false;
+                    setLockdownTracker(null);
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+
             // Turn VPN off
             VpnConfig vpnConfig = getVpnConfig(userId);
             if (vpnConfig != null) {
@@ -5535,8 +5455,8 @@
     }
 
     @VisibleForTesting
-    AvoidBadWifiTracker createAvoidBadWifiTracker(Context c, Handler h, Runnable r) {
-        return new AvoidBadWifiTracker(c, h, r);
+    MultinetworkPolicyTracker createMultinetworkPolicyTracker(Context c, Handler h, Runnable r) {
+        return new MultinetworkPolicyTracker(c, h, r);
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
index 5f9efe7..85d1d1e 100644
--- a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
+++ b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
@@ -23,6 +23,7 @@
 import android.net.Network;
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
+import android.net.TrafficStats;
 import android.os.SystemClock;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -381,7 +382,12 @@
         protected void setupSocket(
                 int sockType, int protocol, long writeTimeout, long readTimeout, int dstPort)
                 throws ErrnoException, IOException {
-            mFileDescriptor = Os.socket(mAddressFamily, sockType, protocol);
+            final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_PROBE);
+            try {
+                mFileDescriptor = Os.socket(mAddressFamily, sockType, protocol);
+            } finally {
+                TrafficStats.setThreadStatsTag(oldTag);
+            }
             // Setting SNDTIMEO is purely for defensive purposes.
             Os.setsockoptTimeval(mFileDescriptor,
                     SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(writeTimeout));
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 4ff6657..703e50a 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -31,7 +31,8 @@
 import android.widget.Toast;
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.MetricsProto.MetricsEvent;
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.internal.notification.SystemNotificationChannels;
 
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -39,11 +40,12 @@
 
 public class NetworkNotificationManager {
 
+
     public static enum NotificationType {
-        LOST_INTERNET(MetricsEvent.NOTIFICATION_NETWORK_LOST_INTERNET),
-        NETWORK_SWITCH(MetricsEvent.NOTIFICATION_NETWORK_SWITCH),
-        NO_INTERNET(MetricsEvent.NOTIFICATION_NETWORK_NO_INTERNET),
-        SIGN_IN(MetricsEvent.NOTIFICATION_NETWORK_SIGN_IN);
+        LOST_INTERNET(SystemMessage.NOTE_NETWORK_LOST_INTERNET),
+        NETWORK_SWITCH(SystemMessage.NOTE_NETWORK_SWITCH),
+        NO_INTERNET(SystemMessage.NOTE_NETWORK_NO_INTERNET),
+        SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN);
 
         public final int eventId;
 
@@ -187,7 +189,9 @@
             return;
         }
 
-        Notification.Builder builder = new Notification.Builder(mContext)
+        final String channelId = highPriority ? SystemNotificationChannels.NETWORK_ALERTS :
+                SystemNotificationChannels.NETWORK_STATUS;
+        Notification.Builder builder = new Notification.Builder(mContext, channelId)
                 .setWhen(System.currentTimeMillis())
                 .setShowWhen(notifyType == NotificationType.NETWORK_SWITCH)
                 .setSmallIcon(icon)
@@ -198,10 +202,6 @@
                 .setContentTitle(title)
                 .setContentIntent(intent)
                 .setLocalOnly(true)
-                .setPriority(highPriority ?
-                        Notification.PRIORITY_HIGH :
-                        Notification.PRIORITY_DEFAULT)
-                .setDefaults(highPriority ? Notification.DEFAULT_ALL : 0)
                 .setOnlyAlertOnce(true);
 
         if (notifyType == NotificationType.NETWORK_SWITCH) {
@@ -210,6 +210,10 @@
             builder.setContentText(details);
         }
 
+        if (notifyType == NotificationType.SIGN_IN) {
+            builder.extend(new Notification.TvExtender().setChannelId(channelId));
+        }
+
         Notification notification = builder.build();
 
         mNotificationTypeMap.put(id, eventId);
diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
index 7cd1b7b..e084ff8 100644
--- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java
+++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
@@ -55,9 +55,9 @@
  */
 public class PermissionMonitor {
     private static final String TAG = "PermissionMonitor";
-    private static final boolean DBG = false;
-    private static final boolean SYSTEM = true;
-    private static final boolean NETWORK = false;
+    private static final boolean DBG = true;
+    private static final Boolean SYSTEM = Boolean.TRUE;
+    private static final Boolean NETWORK = Boolean.FALSE;
 
     private final Context mContext;
     private final PackageManager mPackageManager;
@@ -228,30 +228,40 @@
         update(users, mApps, false);
     }
 
+
+    private Boolean highestPermissionForUid(Boolean currentPermission, String name) {
+        if (currentPermission == SYSTEM) {
+            return currentPermission;
+        }
+        try {
+            final PackageInfo app = mPackageManager.getPackageInfo(name, GET_PERMISSIONS);
+            final boolean isNetwork = hasNetworkPermission(app);
+            final boolean hasRestrictedPermission = hasRestrictedNetworkPermission(app);
+            if (isNetwork || hasRestrictedPermission) {
+                currentPermission = hasRestrictedPermission;
+            }
+        } catch (NameNotFoundException e) {
+            // App not found.
+            loge("NameNotFoundException " + name);
+        }
+        return currentPermission;
+    }
+
     private synchronized void onAppAdded(String appName, int appUid) {
         if (TextUtils.isEmpty(appName) || appUid < 0) {
             loge("Invalid app in onAppAdded: " + appName + " | " + appUid);
             return;
         }
 
-        try {
-            PackageInfo app = mPackageManager.getPackageInfo(appName, GET_PERMISSIONS);
-            boolean isNetwork = hasNetworkPermission(app);
-            boolean hasRestrictedPermission = hasRestrictedNetworkPermission(app);
-            if (isNetwork || hasRestrictedPermission) {
-                Boolean permission = mApps.get(appUid);
-                // If multiple packages share a UID (cf: android:sharedUserId) and ask for different
-                // permissions, don't downgrade (i.e., if it's already SYSTEM, leave it as is).
-                if (permission == null || permission == NETWORK) {
-                    mApps.put(appUid, hasRestrictedPermission);
+        // If multiple packages share a UID (cf: android:sharedUserId) and ask for different
+        // permissions, don't downgrade (i.e., if it's already SYSTEM, leave it as is).
+        final Boolean permission = highestPermissionForUid(mApps.get(appUid), appName);
+        if (permission != mApps.get(appUid)) {
+            mApps.put(appUid, permission);
 
-                    Map<Integer, Boolean> apps = new HashMap<>();
-                    apps.put(appUid, hasRestrictedPermission);
-                    update(mUsers, apps, true);
-                }
-            }
-        } catch (NameNotFoundException e) {
-            loge("NameNotFoundException in onAppAdded: " + e);
+            Map<Integer, Boolean> apps = new HashMap<>();
+            apps.put(appUid, permission);
+            update(mUsers, apps, true);
         }
     }
 
@@ -260,11 +270,33 @@
             loge("Invalid app in onAppRemoved: " + appUid);
             return;
         }
-        mApps.remove(appUid);
-
         Map<Integer, Boolean> apps = new HashMap<>();
-        apps.put(appUid, NETWORK);  // doesn't matter which permission we pick here
-        update(mUsers, apps, false);
+
+        Boolean permission = null;
+        String[] packages = mPackageManager.getPackagesForUid(appUid);
+        if (packages != null && packages.length > 0) {
+            for (String name : packages) {
+                permission = highestPermissionForUid(permission, name);
+                if (permission == SYSTEM) {
+                    // An app with this UID still has the SYSTEM permission.
+                    // Therefore, this UID must already have the SYSTEM permission.
+                    // Nothing to do.
+                    return;
+                }
+            }
+        }
+        if (permission == mApps.get(appUid)) {
+            // The permissions of this UID have not changed. Nothing to do.
+            return;
+        } else if (permission != null) {
+            mApps.put(appUid, permission);
+            apps.put(appUid, permission);
+            update(mUsers, apps, true);
+        } else {
+            mApps.remove(appUid);
+            apps.put(appUid, NETWORK);  // doesn't matter which permission we pick here
+            update(mUsers, apps, false);
+        }
     }
 
     private static void log(String s) {
diff --git a/services/net/java/android/net/apf/ApfCapabilities.java b/services/net/java/android/net/apf/ApfCapabilities.java
index b0e0230..703b415 100644
--- a/services/net/java/android/net/apf/ApfCapabilities.java
+++ b/services/net/java/android/net/apf/ApfCapabilities.java
@@ -46,7 +46,7 @@
     }
 
     public String toString() {
-        return String.format("%s{version: %d, maxSize: %d format: %d}", getClass().getSimpleName(),
+        return String.format("%s{version: %d, maxSize: %d, format: %d}", getClass().getSimpleName(),
                 apfVersionSupported, maximumApfProgramSize, apfPacketFormat);
     }
 }
diff --git a/services/net/java/android/net/util/AvoidBadWifiTracker.java b/services/net/java/android/net/util/MultinetworkPolicyTracker.java
similarity index 62%
rename from services/net/java/android/net/util/AvoidBadWifiTracker.java
rename to services/net/java/android/net/util/MultinetworkPolicyTracker.java
index 2abaeb1..424e40d 100644
--- a/services/net/java/android/net/util/AvoidBadWifiTracker.java
+++ b/services/net/java/android/net/util/MultinetworkPolicyTracker.java
@@ -22,6 +22,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.database.ContentObserver;
+import android.net.ConnectivityManager;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.Message;
@@ -29,10 +30,14 @@
 import android.provider.Settings;
 import android.util.Slog;
 
+import java.util.Arrays;
+import java.util.List;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.R;
 
 import static android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI;
+import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
 
 /**
  * A class to encapsulate management of the "Smart Networking" capability of
@@ -42,8 +47,8 @@
  * This enables the device to switch to another form of connectivity, like
  * mobile, if it's available and working.
  *
- * The Runnable |cb|, if given, is called on the supplied Handler's thread
- * whether the computed "avoid bad wifi" value changes.
+ * The Runnable |avoidBadWifiCallback|, if given, is posted to the supplied
+ * Handler' whenever the computed "avoid bad wifi" value changes.
  *
  * Disabling this reverts the device to a level of networking sophistication
  * circa 2012-13 by disabling disparate code paths each of which contribute to
@@ -51,28 +56,36 @@
  *
  * @hide
  */
-public class AvoidBadWifiTracker {
-    private static String TAG = AvoidBadWifiTracker.class.getSimpleName();
+public class MultinetworkPolicyTracker {
+    private static String TAG = MultinetworkPolicyTracker.class.getSimpleName();
 
     private final Context mContext;
     private final Handler mHandler;
     private final Runnable mReevaluateRunnable;
-    private final Uri mUri;
+    private final List<Uri> mSettingsUris;
     private final ContentResolver mResolver;
     private final SettingObserver mSettingObserver;
     private final BroadcastReceiver mBroadcastReceiver;
 
     private volatile boolean mAvoidBadWifi = true;
+    private volatile int mMeteredMultipathPreference;
 
-    public AvoidBadWifiTracker(Context ctx, Handler handler) {
+    public MultinetworkPolicyTracker(Context ctx, Handler handler) {
         this(ctx, handler, null);
     }
 
-    public AvoidBadWifiTracker(Context ctx, Handler handler, Runnable cb) {
+    public MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback) {
         mContext = ctx;
         mHandler = handler;
-        mReevaluateRunnable = () -> { if (update() && cb != null) cb.run(); };
-        mUri = Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI);
+        mReevaluateRunnable = () -> {
+            if (updateAvoidBadWifi() && avoidBadWifiCallback != null) {
+                avoidBadWifiCallback.run();
+            }
+            updateMeteredMultipathPreference();
+        };
+        mSettingsUris = Arrays.asList(
+            Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI),
+            Settings.Global.getUriFor(NETWORK_METERED_MULTIPATH_PREFERENCE));
         mResolver = mContext.getContentResolver();
         mSettingObserver = new SettingObserver();
         mBroadcastReceiver = new BroadcastReceiver() {
@@ -82,11 +95,14 @@
             }
         };
 
-        update();
+        updateAvoidBadWifi();
+        updateMeteredMultipathPreference();
     }
 
     public void start() {
-        mResolver.registerContentObserver(mUri, false, mSettingObserver);
+        for (Uri uri : mSettingsUris) {
+            mResolver.registerContentObserver(uri, false, mSettingObserver);
+        }
 
         final IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
@@ -102,10 +118,14 @@
         mContext.unregisterReceiver(mBroadcastReceiver);
     }
 
-    public boolean currentValue() {
+    public boolean getAvoidBadWifi() {
         return mAvoidBadWifi;
     }
 
+    public int getMeteredMultipathPreference() {
+        return mMeteredMultipathPreference;
+    }
+
     /**
      * Whether the device or carrier configuration disables avoiding bad wifi by default.
      */
@@ -117,10 +137,10 @@
      * Whether we should display a notification when wifi becomes unvalidated.
      */
     public boolean shouldNotifyWifiUnvalidated() {
-        return configRestrictsAvoidBadWifi() && getSettingsValue() == null;
+        return configRestrictsAvoidBadWifi() && getAvoidBadWifiSetting() == null;
     }
 
-    public String getSettingsValue() {
+    public String getAvoidBadWifiSetting() {
         return Settings.Global.getString(mResolver, NETWORK_AVOID_BAD_WIFI);
     }
 
@@ -129,13 +149,30 @@
         mHandler.post(mReevaluateRunnable);
     }
 
-    public boolean update() {
-        final boolean settingAvoidBadWifi = "1".equals(getSettingsValue());
+    public boolean updateAvoidBadWifi() {
+        final boolean settingAvoidBadWifi = "1".equals(getAvoidBadWifiSetting());
         final boolean prev = mAvoidBadWifi;
         mAvoidBadWifi = settingAvoidBadWifi || !configRestrictsAvoidBadWifi();
         return mAvoidBadWifi != prev;
     }
 
+    /**
+     * The default (device and carrier-dependent) value for metered multipath preference.
+     */
+    public int configMeteredMultipathPreference() {
+        return mContext.getResources().getInteger(
+                R.integer.config_networkMeteredMultipathPreference);
+    }
+
+    public void updateMeteredMultipathPreference() {
+        String setting = Settings.Global.getString(mResolver, NETWORK_METERED_MULTIPATH_PREFERENCE);
+        try {
+            mMeteredMultipathPreference = Integer.parseInt(setting);
+        } catch (NumberFormatException e) {
+            mMeteredMultipathPreference = configMeteredMultipathPreference();
+        }
+    }
+
     private class SettingObserver extends ContentObserver {
         public SettingObserver() {
             super(null);
@@ -148,7 +185,9 @@
 
         @Override
         public void onChange(boolean selfChange, Uri uri) {
-            if (!mUri.equals(uri)) return;
+            if (!mSettingsUris.contains(uri)) {
+                Slog.wtf(TAG, "Unexpected settings observation: " + uri);
+            }
             reevaluate();
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
index 0d5daa5..f841bf9 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
@@ -24,6 +24,7 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
 import com.android.server.net.BaseNetworkObserver;
+import com.android.internal.util.test.BroadcastInterceptingContext;
 
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
deleted file mode 100644
index 011e505..0000000
--- a/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
+++ /dev/null
@@ -1,389 +0,0 @@
-/*
- * Copyright (C) 2016 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 com.android.server.connectivity.MetricsTestUtil.aBool;
-import static com.android.server.connectivity.MetricsTestUtil.aByteArray;
-import static com.android.server.connectivity.MetricsTestUtil.aLong;
-import static com.android.server.connectivity.MetricsTestUtil.aString;
-import static com.android.server.connectivity.MetricsTestUtil.aType;
-import static com.android.server.connectivity.MetricsTestUtil.anInt;
-import static com.android.server.connectivity.MetricsTestUtil.anIntArray;
-import static com.android.server.connectivity.MetricsTestUtil.b;
-import static com.android.server.connectivity.MetricsTestUtil.describeIpEvent;
-import static com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityLog;
-
-import android.net.ConnectivityMetricsEvent;
-import android.net.metrics.ApfProgramEvent;
-import android.net.metrics.ApfStats;
-import android.net.metrics.DefaultNetworkEvent;
-import android.net.metrics.DhcpClientEvent;
-import android.net.metrics.DhcpErrorEvent;
-import android.net.metrics.DnsEvent;
-import android.net.metrics.IpManagerEvent;
-import android.net.metrics.IpReachabilityEvent;
-import android.net.metrics.NetworkEvent;
-import android.net.metrics.RaEvent;
-import android.net.metrics.ValidationProbeEvent;
-import android.test.suitebuilder.annotation.SmallTest;
-import java.util.Arrays;
-import junit.framework.TestCase;
-
-public class IpConnectivityEventBuilderTest extends TestCase {
-
-    @SmallTest
-    public void testDefaultNetworkEventSerialization() {
-        ConnectivityMetricsEvent ev = describeIpEvent(
-                aType(DefaultNetworkEvent.class),
-                anInt(102),
-                anIntArray(1, 2, 3),
-                anInt(101),
-                aBool(true),
-                aBool(false));
-
-        String want = joinLines(
-                "dropped_events: 0",
-                "events <",
-                "  time_ms: 1",
-                "  transport: 0",
-                "  default_network_event <",
-                "    network_id <",
-                "      network_id: 102",
-                "    >",
-                "    previous_network_id <",
-                "      network_id: 101",
-                "    >",
-                "    previous_network_ip_support: 1",
-                "    transport_types: 1",
-                "    transport_types: 2",
-                "    transport_types: 3",
-                "  >",
-                ">",
-                "version: 2");
-
-        verifySerialization(want, ev);
-    }
-
-    @SmallTest
-    public void testDhcpClientEventSerialization() {
-        ConnectivityMetricsEvent ev = describeIpEvent(
-                aType(DhcpClientEvent.class),
-                aString("wlan0"),
-                aString("SomeState"),
-                anInt(192));
-
-        String want = joinLines(
-                "dropped_events: 0",
-                "events <",
-                "  time_ms: 1",
-                "  transport: 0",
-                "  dhcp_event <",
-                "    duration_ms: 192",
-                "    if_name: \"wlan0\"",
-                "    state_transition: \"SomeState\"",
-                "  >",
-                ">",
-                "version: 2");
-
-        verifySerialization(want, ev);
-    }
-
-    @SmallTest
-    public void testDhcpErrorEventSerialization() {
-        ConnectivityMetricsEvent ev = describeIpEvent(
-                aType(DhcpErrorEvent.class),
-                aString("wlan0"),
-                anInt(DhcpErrorEvent.L4_NOT_UDP));
-
-        String want = joinLines(
-                "dropped_events: 0",
-                "events <",
-                "  time_ms: 1",
-                "  transport: 0",
-                "  dhcp_event <",
-                "    duration_ms: 0",
-                "    if_name: \"wlan0\"",
-                "    error_code: 50397184",
-                "  >",
-                ">",
-                "version: 2");
-
-        verifySerialization(want, ev);
-    }
-
-    @SmallTest
-    public void testDnsEventSerialization() {
-        ConnectivityMetricsEvent ev = describeIpEvent(
-                aType(DnsEvent.class),
-                anInt(101),
-                aByteArray(b(1), b(1), b(2), b(1), b(1), b(1), b(2), b(2)),
-                aByteArray(b(0), b(0), b(22), b(3), b(1), b(0), b(200), b(178)),
-                anIntArray(3456, 267, 1230, 45, 2111, 450, 638, 1300));
-
-        String want = joinLines(
-                "dropped_events: 0",
-                "events <",
-                "  time_ms: 1",
-                "  transport: 0",
-                "  dns_lookup_batch <",
-                "    event_types: 1",
-                "    event_types: 1",
-                "    event_types: 2",
-                "    event_types: 1",
-                "    event_types: 1",
-                "    event_types: 1",
-                "    event_types: 2",
-                "    event_types: 2",
-                "    latencies_ms: 3456",
-                "    latencies_ms: 267",
-                "    latencies_ms: 1230",
-                "    latencies_ms: 45",
-                "    latencies_ms: 2111",
-                "    latencies_ms: 450",
-                "    latencies_ms: 638",
-                "    latencies_ms: 1300",
-                "    network_id <",
-                "      network_id: 101",
-                "    >",
-                "    return_codes: 0",
-                "    return_codes: 0",
-                "    return_codes: 22",
-                "    return_codes: 3",
-                "    return_codes: 1",
-                "    return_codes: 0",
-                "    return_codes: 200",
-                "    return_codes: 178",
-                "  >",
-                ">",
-                "version: 2");
-
-        verifySerialization(want, ev);
-    }
-
-    @SmallTest
-    public void testIpManagerEventSerialization() {
-        ConnectivityMetricsEvent ev = describeIpEvent(
-                aType(IpManagerEvent.class),
-                aString("wlan0"),
-                anInt(IpManagerEvent.PROVISIONING_OK),
-                aLong(5678));
-
-        String want = joinLines(
-                "dropped_events: 0",
-                "events <",
-                "  time_ms: 1",
-                "  transport: 0",
-                "  ip_provisioning_event <",
-                "    event_type: 1",
-                "    if_name: \"wlan0\"",
-                "    latency_ms: 5678",
-                "  >",
-                ">",
-                "version: 2");
-
-        verifySerialization(want, ev);
-    }
-
-    @SmallTest
-    public void testIpReachabilityEventSerialization() {
-        ConnectivityMetricsEvent ev = describeIpEvent(
-                aType(IpReachabilityEvent.class),
-                aString("wlan0"),
-                anInt(IpReachabilityEvent.NUD_FAILED));
-
-        String want = joinLines(
-                "dropped_events: 0",
-                "events <",
-                "  time_ms: 1",
-                "  transport: 0",
-                "  ip_reachability_event <",
-                "    event_type: 512",
-                "    if_name: \"wlan0\"",
-                "  >",
-                ">",
-                "version: 2");
-
-        verifySerialization(want, ev);
-    }
-
-    @SmallTest
-    public void testNetworkEventSerialization() {
-        ConnectivityMetricsEvent ev = describeIpEvent(
-                aType(NetworkEvent.class),
-                anInt(100),
-                anInt(5),
-                aLong(20410));
-
-        String want = joinLines(
-                "dropped_events: 0",
-                "events <",
-                "  time_ms: 1",
-                "  transport: 0",
-                "  network_event <",
-                "    event_type: 5",
-                "    latency_ms: 20410",
-                "    network_id <",
-                "      network_id: 100",
-                "    >",
-                "  >",
-                ">",
-                "version: 2");
-
-        verifySerialization(want, ev);
-    }
-
-    @SmallTest
-    public void testValidationProbeEventSerialization() {
-        ConnectivityMetricsEvent ev = describeIpEvent(
-                aType(ValidationProbeEvent.class),
-                anInt(120),
-                aLong(40730),
-                anInt(ValidationProbeEvent.PROBE_HTTP),
-                anInt(204));
-
-        String want = joinLines(
-                "dropped_events: 0",
-                "events <",
-                "  time_ms: 1",
-                "  transport: 0",
-                "  validation_probe_event <",
-                "    latency_ms: 40730",
-                "    network_id <",
-                "      network_id: 120",
-                "    >",
-                "    probe_result: 204",
-                "    probe_type: 1",
-                "  >",
-                ">",
-                "version: 2");
-
-        verifySerialization(want, ev);
-    }
-
-    @SmallTest
-    public void testApfProgramEventSerialization() {
-        ConnectivityMetricsEvent ev = describeIpEvent(
-                aType(ApfProgramEvent.class),
-                aLong(200),
-                anInt(7),
-                anInt(9),
-                anInt(2048),
-                anInt(3));
-
-        String want = joinLines(
-                "dropped_events: 0",
-                "events <",
-                "  time_ms: 1",
-                "  transport: 0",
-                "  apf_program_event <",
-                "    current_ras: 9",
-                "    drop_multicast: true",
-                "    filtered_ras: 7",
-                "    has_ipv4_addr: true",
-                "    lifetime: 200",
-                "    program_length: 2048",
-                "  >",
-                ">",
-                "version: 2");
-
-        verifySerialization(want, ev);
-    }
-
-    @SmallTest
-    public void testApfStatsSerialization() {
-        ConnectivityMetricsEvent ev = describeIpEvent(
-                aType(ApfStats.class),
-                aLong(45000),
-                anInt(10),
-                anInt(2),
-                anInt(2),
-                anInt(1),
-                anInt(2),
-                anInt(4),
-                anInt(2048));
-
-        String want = joinLines(
-                "dropped_events: 0",
-                "events <",
-                "  time_ms: 1",
-                "  transport: 0",
-                "  apf_statistics <",
-                "    dropped_ras: 2",
-                "    duration_ms: 45000",
-                "    matching_ras: 2",
-                "    max_program_size: 2048",
-                "    parse_errors: 2",
-                "    program_updates: 4",
-                "    received_ras: 10",
-                "    zero_lifetime_ras: 1",
-                "  >",
-                ">",
-                "version: 2");
-
-        verifySerialization(want, ev);
-    }
-
-    @SmallTest
-    public void testRaEventSerialization() {
-        ConnectivityMetricsEvent ev = describeIpEvent(
-                aType(RaEvent.class),
-                aLong(2000),
-                aLong(400),
-                aLong(300),
-                aLong(-1),
-                aLong(1000),
-                aLong(-1));
-
-        String want = joinLines(
-                "dropped_events: 0",
-                "events <",
-                "  time_ms: 1",
-                "  transport: 0",
-                "  ra_event <",
-                "    dnssl_lifetime: -1",
-                "    prefix_preferred_lifetime: 300",
-                "    prefix_valid_lifetime: 400",
-                "    rdnss_lifetime: 1000",
-                "    route_info_lifetime: -1",
-                "    router_lifetime: 2000",
-                "  >",
-                ">",
-                "version: 2");
-
-        verifySerialization(want, ev);
-    }
-
-    static void verifySerialization(String want, ConnectivityMetricsEvent... input) {
-        try {
-            byte[] got = IpConnectivityEventBuilder.serialize(0,
-                    IpConnectivityEventBuilder.toProto(Arrays.asList(input)));
-            IpConnectivityLog log = IpConnectivityLog.parseFrom(got);
-            assertEquals(want, log.toString());
-        } catch (Exception e) {
-            fail(e.toString());
-        }
-    }
-
-    static String joinLines(String ... elems) {
-        StringBuilder b = new StringBuilder();
-        for (String s : elems) {
-            b.append(s);
-            b.append("\n");
-        }
-        return b.toString();
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityMetricsTest.java b/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityMetricsTest.java
deleted file mode 100644
index 450653c..0000000
--- a/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ /dev/null
@@ -1,296 +0,0 @@
-/*
- * Copyright (C) 2016, 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 org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.net.ConnectivityMetricsEvent;
-import android.net.IIpConnectivityMetrics;
-import android.net.metrics.ApfProgramEvent;
-import android.net.metrics.ApfStats;
-import android.net.metrics.DefaultNetworkEvent;
-import android.net.metrics.DhcpClientEvent;
-import android.net.metrics.IpConnectivityLog;
-import android.net.metrics.IpManagerEvent;
-import android.net.metrics.IpReachabilityEvent;
-import android.net.metrics.RaEvent;
-import android.net.metrics.ValidationProbeEvent;
-import android.os.Parcelable;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Base64;
-import com.android.server.connectivity.metrics.IpConnectivityLogClass;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-import junit.framework.TestCase;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-public class IpConnectivityMetricsTest extends TestCase {
-    static final IpReachabilityEvent FAKE_EV =
-            new IpReachabilityEvent("wlan0", IpReachabilityEvent.NUD_FAILED);
-
-    @Mock Context mCtx;
-    @Mock IIpConnectivityMetrics mMockService;
-
-    IpConnectivityMetrics mService;
-
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mService = new IpConnectivityMetrics(mCtx, (ctx) -> 2000);
-    }
-
-    @SmallTest
-    public void testLoggingEvents() throws Exception {
-        IpConnectivityLog logger = new IpConnectivityLog(mMockService);
-
-        assertTrue(logger.log(1, FAKE_EV));
-        assertTrue(logger.log(2, FAKE_EV));
-        assertTrue(logger.log(3, FAKE_EV));
-
-        List<ConnectivityMetricsEvent> got = verifyEvents(3);
-        assertEventsEqual(expectedEvent(1), got.get(0));
-        assertEventsEqual(expectedEvent(2), got.get(1));
-        assertEventsEqual(expectedEvent(3), got.get(2));
-    }
-
-    @SmallTest
-    public void testLoggingEventsWithMultipleCallers() throws Exception {
-        IpConnectivityLog logger = new IpConnectivityLog(mMockService);
-
-        final int nCallers = 10;
-        final int nEvents = 10;
-        for (int n = 0; n < nCallers; n++) {
-            final int i = n;
-            new Thread() {
-                public void run() {
-                    for (int j = 0; j < nEvents; j++) {
-                        assertTrue(logger.log(i * 100 + j, FAKE_EV));
-                    }
-                }
-            }.start();
-        }
-
-        List<ConnectivityMetricsEvent> got = verifyEvents(nCallers * nEvents, 100);
-        Collections.sort(got, EVENT_COMPARATOR);
-        Iterator<ConnectivityMetricsEvent> iter = got.iterator();
-        for (int i = 0; i < nCallers; i++) {
-            for (int j = 0; j < nEvents; j++) {
-                int expectedTimestamp = i * 100 + j;
-                assertEventsEqual(expectedEvent(expectedTimestamp), iter.next());
-            }
-        }
-    }
-
-    @SmallTest
-    public void testBufferFlushing() {
-        String output1 = getdump("flush");
-        assertEquals("", output1);
-
-        new IpConnectivityLog(mService.impl).log(1, FAKE_EV);
-        String output2 = getdump("flush");
-        assertFalse("".equals(output2));
-
-        String output3 = getdump("flush");
-        assertEquals("", output3);
-    }
-
-    @SmallTest
-    public void testRateLimiting() {
-        final IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
-        final ApfProgramEvent ev = new ApfProgramEvent(0, 0, 0, 0, 0);
-        final long fakeTimestamp = 1;
-
-        int attempt = 100; // More than burst quota, but less than buffer size.
-        for (int i = 0; i < attempt; i++) {
-            logger.log(ev);
-        }
-
-        String output1 = getdump("flush");
-        assertFalse("".equals(output1));
-
-        for (int i = 0; i < attempt; i++) {
-            assertFalse("expected event to be dropped", logger.log(fakeTimestamp, ev));
-        }
-
-        String output2 = getdump("flush");
-        assertEquals("", output2);
-    }
-
-    @SmallTest
-    public void testEndToEndLogging() {
-        IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
-
-        Parcelable[] events = {
-            new IpReachabilityEvent("wlan0", IpReachabilityEvent.NUD_FAILED),
-            new DhcpClientEvent("wlan0", "SomeState", 192),
-            new DefaultNetworkEvent(102, new int[]{1,2,3}, 101, true, false),
-            new IpManagerEvent("wlan0", IpManagerEvent.PROVISIONING_OK, 5678),
-            new ValidationProbeEvent(120, 40730, ValidationProbeEvent.PROBE_HTTP, 204),
-            new ApfStats(45000, 10, 2, 2, 1, 2, 4, 2048),
-            new RaEvent(2000, 400, 300, -1, 1000, -1)
-        };
-
-        for (int i = 0; i < events.length; i++) {
-            logger.log(100 * (i + 1), events[i]);
-        }
-
-        String want = joinLines(
-                "dropped_events: 0",
-                "events <",
-                "  time_ms: 100",
-                "  transport: 0",
-                "  ip_reachability_event <",
-                "    event_type: 512",
-                "    if_name: \"wlan0\"",
-                "  >",
-                ">",
-                "events <",
-                "  time_ms: 200",
-                "  transport: 0",
-                "  dhcp_event <",
-                "    duration_ms: 192",
-                "    if_name: \"wlan0\"",
-                "    state_transition: \"SomeState\"",
-                "  >",
-                ">",
-                "events <",
-                "  time_ms: 300",
-                "  transport: 0",
-                "  default_network_event <",
-                "    network_id <",
-                "      network_id: 102",
-                "    >",
-                "    previous_network_id <",
-                "      network_id: 101",
-                "    >",
-                "    previous_network_ip_support: 1",
-                "    transport_types: 1",
-                "    transport_types: 2",
-                "    transport_types: 3",
-                "  >",
-                ">",
-                "events <",
-                "  time_ms: 400",
-                "  transport: 0",
-                "  ip_provisioning_event <",
-                "    event_type: 1",
-                "    if_name: \"wlan0\"",
-                "    latency_ms: 5678",
-                "  >",
-                ">",
-                "events <",
-                "  time_ms: 500",
-                "  transport: 0",
-                "  validation_probe_event <",
-                "    latency_ms: 40730",
-                "    network_id <",
-                "      network_id: 120",
-                "    >",
-                "    probe_result: 204",
-                "    probe_type: 1",
-                "  >",
-                ">",
-                "events <",
-                "  time_ms: 600",
-                "  transport: 0",
-                "  apf_statistics <",
-                "    dropped_ras: 2",
-                "    duration_ms: 45000",
-                "    matching_ras: 2",
-                "    max_program_size: 2048",
-                "    parse_errors: 2",
-                "    program_updates: 4",
-                "    received_ras: 10",
-                "    zero_lifetime_ras: 1",
-                "  >",
-                ">",
-                "events <",
-                "  time_ms: 700",
-                "  transport: 0",
-                "  ra_event <",
-                "    dnssl_lifetime: -1",
-                "    prefix_preferred_lifetime: 300",
-                "    prefix_valid_lifetime: 400",
-                "    rdnss_lifetime: 1000",
-                "    route_info_lifetime: -1",
-                "    router_lifetime: 2000",
-                "  >",
-                ">",
-                "version: 2");
-
-        verifySerialization(want, getdump("flush"));
-    }
-
-    String getdump(String ... command) {
-        StringWriter buffer = new StringWriter();
-        PrintWriter writer = new PrintWriter(buffer);
-        mService.impl.dump(null, writer, command);
-        return buffer.toString();
-    }
-
-    List<ConnectivityMetricsEvent> verifyEvents(int n, int timeoutMs) throws Exception {
-        ArgumentCaptor<ConnectivityMetricsEvent> captor =
-                ArgumentCaptor.forClass(ConnectivityMetricsEvent.class);
-        verify(mMockService, timeout(timeoutMs).times(n)).logEvent(captor.capture());
-        return captor.getAllValues();
-    }
-
-    List<ConnectivityMetricsEvent> verifyEvents(int n) throws Exception {
-        return verifyEvents(n, 10);
-    }
-
-    static void verifySerialization(String want, String output) {
-        try {
-            byte[] got = Base64.decode(output, Base64.DEFAULT);
-            IpConnectivityLogClass.IpConnectivityLog log =
-                    IpConnectivityLogClass.IpConnectivityLog.parseFrom(got);
-            assertEquals(want, log.toString());
-        } catch (Exception e) {
-            fail(e.toString());
-        }
-    }
-
-    static String joinLines(String ... elems) {
-        StringBuilder b = new StringBuilder();
-        for (String s : elems) {
-            b.append(s).append("\n");
-        }
-        return b.toString();
-    }
-
-    static ConnectivityMetricsEvent expectedEvent(int timestamp) {
-        return new ConnectivityMetricsEvent((long)timestamp, 0, 0, FAKE_EV);
-    }
-
-    /** Outer equality for ConnectivityMetricsEvent to avoid overriding equals() and hashCode(). */
-    static void assertEventsEqual(ConnectivityMetricsEvent expected, ConnectivityMetricsEvent got) {
-        assertEquals(expected.timestamp, got.timestamp);
-        assertEquals(expected.componentTag, got.componentTag);
-        assertEquals(expected.eventTag, got.eventTag);
-        assertEquals(expected.data, got.data);
-    }
-
-    static final Comparator<ConnectivityMetricsEvent> EVENT_COMPARATOR =
-        Comparator.comparingLong((ev) -> ev.timestamp);
-}
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/MetricsTestUtil.java b/services/tests/servicestests/src/com/android/server/connectivity/MetricsTestUtil.java
deleted file mode 100644
index e201012..0000000
--- a/services/tests/servicestests/src/com/android/server/connectivity/MetricsTestUtil.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2016 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 android.net.ConnectivityMetricsEvent;
-import android.net.ConnectivityMetricsLogger;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-abstract public class MetricsTestUtil {
-    private MetricsTestUtil() {
-    }
-
-    static ConnectivityMetricsEvent ipEv(Parcelable p) {
-        return ev(ConnectivityMetricsLogger.COMPONENT_TAG_CONNECTIVITY, p);
-    }
-
-    static ConnectivityMetricsEvent telephonyEv() {
-        return ev(ConnectivityMetricsLogger.COMPONENT_TAG_TELEPHONY, new Bundle());
-    }
-
-    static ConnectivityMetricsEvent ev(int tag, Parcelable p) {
-        return new ConnectivityMetricsEvent(1L, tag, 0, p);
-    }
-
-    // Utiliy interface for describing the content of a Parcel. This relies on
-    // the implementation defails of Parcelable and on the fact that the fully
-    // qualified Parcelable class names are written as string in the Parcels.
-    interface ParcelField {
-        void write(Parcel p);
-    }
-
-    static ConnectivityMetricsEvent describeIpEvent(ParcelField... fs) {
-        Parcel p = Parcel.obtain();
-        for (ParcelField f : fs) {
-            f.write(p);
-        }
-        p.setDataPosition(0);
-        return ipEv(p.readParcelable(ClassLoader.getSystemClassLoader()));
-    }
-
-    static ParcelField aType(Class<?> c) {
-        return new ParcelField() {
-            public void write(Parcel p) {
-                p.writeString(c.getName());
-            }
-        };
-    }
-
-    static ParcelField aBool(boolean b) {
-        return aByte((byte) (b ? 1 : 0));
-    }
-
-    static ParcelField aByte(byte b) {
-        return new ParcelField() {
-            public void write(Parcel p) {
-                p.writeByte(b);
-            }
-        };
-    }
-
-    static ParcelField anInt(int i) {
-        return new ParcelField() {
-            public void write(Parcel p) {
-                p.writeInt(i);
-            }
-        };
-    }
-
-    static ParcelField aLong(long l) {
-        return new ParcelField() {
-            public void write(Parcel p) {
-                p.writeLong(l);
-            }
-        };
-    }
-
-    static ParcelField aString(String s) {
-        return new ParcelField() {
-            public void write(Parcel p) {
-                p.writeString(s);
-            }
-        };
-    }
-
-    static ParcelField aByteArray(byte... ary) {
-        return new ParcelField() {
-            public void write(Parcel p) {
-                p.writeByteArray(ary);
-            }
-        };
-    }
-
-    static ParcelField anIntArray(int... ary) {
-        return new ParcelField() {
-            public void write(Parcel p) {
-                p.writeIntArray(ary);
-            }
-        };
-    }
-
-    static byte b(int i) {
-        return (byte) i;
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/NetdEventListenerServiceTest.java b/services/tests/servicestests/src/com/android/server/connectivity/NetdEventListenerServiceTest.java
deleted file mode 100644
index 97afa60..0000000
--- a/services/tests/servicestests/src/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2016, 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 android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
-import android.net.Network;
-import android.net.metrics.DnsEvent;
-import android.net.metrics.INetdEventListener;
-import android.net.metrics.IpConnectivityLog;
-import android.os.RemoteException;
-import android.system.OsConstants;
-import android.test.suitebuilder.annotation.SmallTest;
-import com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityEvent;
-import java.io.FileOutputStream;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.List;
-import java.util.OptionalInt;
-import java.util.stream.IntStream;
-import junit.framework.TestCase;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-public class NetdEventListenerServiceTest extends TestCase {
-
-    // TODO: read from NetdEventListenerService after this constant is read from system property
-    static final int BATCH_SIZE = 100;
-    static final int EVENT_TYPE = INetdEventListener.EVENT_GETADDRINFO;
-    // TODO: read from INetdEventListener
-    static final int RETURN_CODE = 1;
-
-    static final byte[] EVENT_TYPES  = new byte[BATCH_SIZE];
-    static final byte[] RETURN_CODES = new byte[BATCH_SIZE];
-    static final int[] LATENCIES     = new int[BATCH_SIZE];
-    static {
-        for (int i = 0; i < BATCH_SIZE; i++) {
-            EVENT_TYPES[i] = EVENT_TYPE;
-            RETURN_CODES[i] = RETURN_CODE;
-            LATENCIES[i] = i;
-        }
-    }
-
-    private static final String EXAMPLE_IPV4 = "192.0.2.1";
-    private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1";
-
-    NetdEventListenerService mNetdEventListenerService;
-
-    @Mock ConnectivityManager mCm;
-    @Mock IpConnectivityLog mLog;
-    ArgumentCaptor<NetworkCallback> mCallbackCaptor;
-    ArgumentCaptor<DnsEvent> mDnsEvCaptor;
-
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mCallbackCaptor = ArgumentCaptor.forClass(NetworkCallback.class);
-        mDnsEvCaptor = ArgumentCaptor.forClass(DnsEvent.class);
-        mNetdEventListenerService = new NetdEventListenerService(mCm, mLog);
-
-        verify(mCm, times(1)).registerNetworkCallback(any(), mCallbackCaptor.capture());
-    }
-
-    @SmallTest
-    public void testOneDnsBatch() throws Exception {
-        log(105, LATENCIES);
-        log(106, Arrays.copyOf(LATENCIES, BATCH_SIZE - 1)); // one lookup short of a batch event
-
-        verifyLoggedDnsEvents(new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES));
-
-        log(106, Arrays.copyOfRange(LATENCIES, BATCH_SIZE - 1, BATCH_SIZE));
-
-        mDnsEvCaptor = ArgumentCaptor.forClass(DnsEvent.class); // reset argument captor
-        verifyLoggedDnsEvents(
-            new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
-            new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES));
-    }
-
-    @SmallTest
-    public void testSeveralDmsBatches() throws Exception {
-        log(105, LATENCIES);
-        log(106, LATENCIES);
-        log(105, LATENCIES);
-        log(107, LATENCIES);
-
-        verifyLoggedDnsEvents(
-            new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
-            new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES),
-            new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
-            new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES));
-    }
-
-    @SmallTest
-    public void testDnsBatchAndNetworkLost() throws Exception {
-        byte[] eventTypes = Arrays.copyOf(EVENT_TYPES, 20);
-        byte[] returnCodes = Arrays.copyOf(RETURN_CODES, 20);
-        int[] latencies = Arrays.copyOf(LATENCIES, 20);
-
-        log(105, LATENCIES);
-        log(105, latencies);
-        mCallbackCaptor.getValue().onLost(new Network(105));
-        log(105, LATENCIES);
-
-        verifyLoggedDnsEvents(
-            new DnsEvent(105, eventTypes, returnCodes, latencies),
-            new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
-            new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES));
-    }
-
-    @SmallTest
-    public void testConcurrentDnsBatchesAndDumps() throws Exception {
-        final long stop = System.currentTimeMillis() + 100;
-        final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null"));
-        new Thread() {
-            public void run() {
-                while (System.currentTimeMillis() < stop) {
-                    mNetdEventListenerService.dump(pw);
-                }
-            }
-        }.start();
-
-        logDnsAsync(105, LATENCIES);
-        logDnsAsync(106, LATENCIES);
-        logDnsAsync(107, LATENCIES);
-
-        verifyLoggedDnsEvents(500,
-            new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
-            new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES),
-            new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES));
-    }
-
-    @SmallTest
-    public void testConcurrentDnsBatchesAndNetworkLoss() throws Exception {
-        logDnsAsync(105, LATENCIES);
-        Thread.sleep(10L);
-        // call onLost() asynchronously to logDnsAsync's onDnsEvent() calls.
-        mCallbackCaptor.getValue().onLost(new Network(105));
-
-        // do not verify unpredictable batch
-        verify(mLog, timeout(500).times(1)).log(any());
-    }
-
-    @SmallTest
-    public void testConnectLogging() throws Exception {
-        final int OK = 0;
-        Thread[] logActions = {
-            // ignored
-            connectEventAction(OsConstants.EALREADY, 0, EXAMPLE_IPV4),
-            connectEventAction(OsConstants.EALREADY, 0, EXAMPLE_IPV6),
-            connectEventAction(OsConstants.EINPROGRESS, 0, EXAMPLE_IPV4),
-            connectEventAction(OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6),
-            connectEventAction(OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6),
-            // valid latencies
-            connectEventAction(OK, 110, EXAMPLE_IPV4),
-            connectEventAction(OK, 23, EXAMPLE_IPV4),
-            connectEventAction(OK, 45, EXAMPLE_IPV4),
-            connectEventAction(OK, 56, EXAMPLE_IPV4),
-            connectEventAction(OK, 523, EXAMPLE_IPV6),
-            connectEventAction(OK, 214, EXAMPLE_IPV6),
-            connectEventAction(OK, 67, EXAMPLE_IPV6),
-            // errors
-            connectEventAction(OsConstants.EPERM, 0, EXAMPLE_IPV4),
-            connectEventAction(OsConstants.EPERM, 0, EXAMPLE_IPV4),
-            connectEventAction(OsConstants.EAGAIN, 0, EXAMPLE_IPV4),
-            connectEventAction(OsConstants.EACCES, 0, EXAMPLE_IPV4),
-            connectEventAction(OsConstants.EACCES, 0, EXAMPLE_IPV4),
-            connectEventAction(OsConstants.EACCES, 0, EXAMPLE_IPV6),
-            connectEventAction(OsConstants.EADDRINUSE, 0, EXAMPLE_IPV4),
-            connectEventAction(OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV4),
-            connectEventAction(OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6),
-            connectEventAction(OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6),
-            connectEventAction(OsConstants.ECONNREFUSED, 0, EXAMPLE_IPV4),
-        };
-
-        for (Thread t : logActions) {
-            t.start();
-        }
-        for (Thread t : logActions) {
-            t.join();
-        }
-
-        List<IpConnectivityEvent> events = new ArrayList<>();
-        mNetdEventListenerService.flushStatistics(events);
-
-        IpConnectivityEvent got = events.get(0);
-        String want = joinLines(
-                "time_ms: 0",
-                "transport: 0",
-                "connect_statistics <",
-                "  connect_count: 12",
-                "  errnos_counters <",
-                "    key: 1",
-                "    value: 2",
-                "  >",
-                "  errnos_counters <",
-                "    key: 11",
-                "    value: 1",
-                "  >",
-                "  errnos_counters <",
-                "    key: 13",
-                "    value: 3",
-                "  >",
-                "  errnos_counters <",
-                "    key: 98",
-                "    value: 1",
-                "  >",
-                "  errnos_counters <",
-                "    key: 110",
-                "    value: 3",
-                "  >",
-                "  errnos_counters <",
-                "    key: 111",
-                "    value: 1",
-                "  >",
-                "  ipv6_addr_count: 6",
-                "  latencies_ms: 23",
-                "  latencies_ms: 45",
-                "  latencies_ms: 56",
-                "  latencies_ms: 67",
-                "  latencies_ms: 110",
-                "  latencies_ms: 214",
-                "  latencies_ms: 523");
-        verifyConnectEvent(want, got);
-    }
-
-    Thread connectEventAction(int error, int latencyMs, String ipAddr) {
-        return new Thread(() -> {
-            try {
-                mNetdEventListenerService.onConnectEvent(100, error, latencyMs, ipAddr, 80, 1);
-            } catch (Exception e) {
-                fail(e.toString());
-            }
-        });
-    }
-
-    void log(int netId, int[] latencies) {
-        try {
-            for (int l : latencies) {
-                mNetdEventListenerService.onDnsEvent(netId, EVENT_TYPE, RETURN_CODE, l, null, null,
-                        0, 0);
-            }
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
-        }
-    }
-
-    void logDnsAsync(int netId, int[] latencies) {
-        new Thread() {
-            public void run() {
-                log(netId, latencies);
-            }
-        }.start();
-    }
-
-    void verifyLoggedDnsEvents(DnsEvent... expected) {
-        verifyLoggedDnsEvents(0, expected);
-    }
-
-    void verifyLoggedDnsEvents(int wait, DnsEvent... expectedEvents) {
-        verify(mLog, timeout(wait).times(expectedEvents.length)).log(mDnsEvCaptor.capture());
-        for (DnsEvent got : mDnsEvCaptor.getAllValues()) {
-            OptionalInt index = IntStream.range(0, expectedEvents.length)
-                    .filter(i -> dnsEventsEqual(expectedEvents[i], got))
-                    .findFirst();
-            // Don't match same expected event more than once.
-            index.ifPresent(i -> expectedEvents[i] = null);
-            assertTrue(index.isPresent());
-        }
-    }
-
-    /** equality function for DnsEvent to avoid overriding equals() and hashCode(). */
-    static boolean dnsEventsEqual(DnsEvent expected, DnsEvent got) {
-        return (expected == got) || ((expected != null) && (got != null)
-                && (expected.netId == got.netId)
-                && Arrays.equals(expected.eventTypes, got.eventTypes)
-                && Arrays.equals(expected.returnCodes, got.returnCodes)
-                && Arrays.equals(expected.latenciesMs, got.latenciesMs));
-    }
-
-    static String joinLines(String ... elems) {
-        StringBuilder b = new StringBuilder();
-        for (String s : elems) {
-            b.append(s).append("\n");
-        }
-        return b.toString();
-    }
-
-    static void verifyConnectEvent(String expected, IpConnectivityEvent got) {
-        try {
-            Arrays.sort(got.connectStatistics.latenciesMs);
-            Arrays.sort(got.connectStatistics.errnosCounters,
-                    Comparator.comparingInt((p) -> p.key));
-            assertEquals(expected, got.toString());
-        } catch (Exception e) {
-            fail(e.toString());
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java
index 21560ac..5eee7b9 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.Mockito.when;
 
 import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.METERED_NO;
 import static android.net.NetworkStats.ROAMING_NO;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
@@ -336,7 +337,7 @@
         // Baseline
         NetworkStats xtSnapshot = null;
         NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
@@ -344,7 +345,7 @@
 
         // Delta
         uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
@@ -374,7 +375,7 @@
         // Baseline
         NetworkStats xtSnapshot = null;
         NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
@@ -382,7 +383,7 @@
 
         // Delta
         uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
@@ -412,7 +413,7 @@
         // Baseline
         NetworkStats xtSnapshot = null;
         NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
@@ -420,7 +421,7 @@
 
         // Delta
         uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
@@ -450,16 +451,17 @@
         // Baseline
         NetworkStats xtSnapshot = null;
         NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, ROAMING_NO,
-                        BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
+                .addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO,
+                        ROAMING_NO, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
                 VPN_INFO, TEST_START);
 
         // Delta
         uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, ROAMING_NO,
-                        BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
+                .addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO,
+                        ROAMING_NO, BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES,
+                        2L, 0L);
         mStatsObservers.updateStats(
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
                 VPN_INFO, TEST_START);
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java
index 94c6711..728eb73 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java
@@ -22,6 +22,9 @@
 import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.ConnectivityManager.TYPE_WIMAX;
 import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.METERED_ALL;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.METERED_YES;
 import static android.net.NetworkStats.ROAMING_ALL;
 import static android.net.NetworkStats.ROAMING_NO;
 import static android.net.NetworkStats.ROAMING_YES;
@@ -40,21 +43,21 @@
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 import static android.text.format.DateUtils.WEEK_IN_MILLIS;
+
 import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
-import static org.easymock.EasyMock.anyInt;
-import static org.easymock.EasyMock.anyLong;
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.capture;
-import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.eq;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.expectLastCall;
-import static org.easymock.EasyMock.isA;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
 
 import android.app.AlarmManager;
-import android.app.IAlarmListener;
-import android.app.IAlarmManager;
-import android.app.PendingIntent;
 import android.app.usage.NetworkStatsManager;
 import android.content.Context;
 import android.content.Intent;
@@ -63,6 +66,7 @@
 import android.net.INetworkManagementEventObserver;
 import android.net.INetworkStatsSession;
 import android.net.LinkProperties;
+import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkState;
@@ -80,23 +84,28 @@
 import android.os.MessageQueue.IdleHandler;
 import android.os.Message;
 import android.os.PowerManager;
-import android.os.WorkSource;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 import android.telephony.TelephonyManager;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.Suppress;
 import android.util.TrustedTime;
 
 import com.android.internal.net.VpnInfo;
-import com.android.server.BroadcastInterceptingContext;
+import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.server.net.NetworkStatsService;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
 
 import libcore.io.IoUtils;
 
-import org.easymock.Capture;
-import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -106,11 +115,11 @@
 /**
  * Tests for {@link NetworkStatsService}.
  *
- * TODO: This test is really brittle, largely due to overly-strict use of Easymock.
- * Rewrite w/ Mockito.
+ * TODO: This test used to be really brittle because it used Easymock - it uses Mockito now, but
+ * still uses the Easymock structure, which could be simplified.
  */
-@LargeTest
-public class NetworkStatsServiceTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class NetworkStatsServiceTest {
     private static final String TAG = "NetworkStatsServiceTest";
 
     private static final String TEST_IFACE = "test0";
@@ -137,10 +146,12 @@
     private BroadcastInterceptingContext mServiceContext;
     private File mStatsDir;
 
-    private INetworkManagementService mNetManager;
-    private TrustedTime mTime;
-    private NetworkStatsSettings mSettings;
-    private IConnectivityManager mConnManager;
+    private @Mock INetworkManagementService mNetManager;
+    private @Mock TrustedTime mTime;
+    private @Mock NetworkStatsSettings mSettings;
+    private @Mock IConnectivityManager mConnManager;
+    private @Mock IBinder mBinder;
+    private @Mock AlarmManager mAlarmManager;
     private IdleableHandlerThread mHandlerThread;
     private Handler mHandler;
 
@@ -148,32 +159,24 @@
     private INetworkStatsSession mSession;
     private INetworkManagementEventObserver mNetworkObserver;
 
-    @Override
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
+        MockitoAnnotations.initMocks(this);
+        final Context context = InstrumentationRegistry.getContext();
 
-        mServiceContext = new BroadcastInterceptingContext(getContext());
-        mStatsDir = getContext().getFilesDir();
+        mServiceContext = new BroadcastInterceptingContext(context);
+        mStatsDir = context.getFilesDir();
         if (mStatsDir.exists()) {
             IoUtils.deleteContents(mStatsDir);
         }
 
-        mNetManager = createMock(INetworkManagementService.class);
-
-        // TODO: Mock AlarmManager when migrating this test to Mockito.
-        AlarmManager alarmManager = (AlarmManager) mServiceContext
-                .getSystemService(Context.ALARM_SERVICE);
-        mTime = createMock(TrustedTime.class);
-        mSettings = createMock(NetworkStatsSettings.class);
-        mConnManager = createMock(IConnectivityManager.class);
-
         PowerManager powerManager = (PowerManager) mServiceContext.getSystemService(
                 Context.POWER_SERVICE);
         PowerManager.WakeLock wakeLock =
                 powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
 
         mService = new NetworkStatsService(
-                mServiceContext, mNetManager, alarmManager, wakeLock, mTime,
+                mServiceContext, mNetManager, mAlarmManager, wakeLock, mTime,
                 TelephonyManager.getDefault(), mSettings, new NetworkStatsObservers(),
                 mStatsDir, getBaseDir(mStatsDir));
         mHandlerThread = new IdleableHandlerThread("HandlerThread");
@@ -190,22 +193,20 @@
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectSystemReady();
 
-        // catch INetworkManagementEventObserver during systemReady()
-        final Capture<INetworkManagementEventObserver> networkObserver = new Capture<
-                INetworkManagementEventObserver>();
-        mNetManager.registerObserver(capture(networkObserver));
-        expectLastCall().atLeastOnce();
-
-        replay();
         mService.systemReady();
         mSession = mService.openSession();
-        verifyAndReset();
+        assertNotNull("openSession() failed", mSession);
 
+
+        // catch INetworkManagementEventObserver during systemReady()
+        ArgumentCaptor<INetworkManagementEventObserver> networkObserver =
+              ArgumentCaptor.forClass(INetworkManagementEventObserver.class);
+        verify(mNetManager).registerObserver(networkObserver.capture());
         mNetworkObserver = networkObserver.getValue();
 
     }
 
-    @Override
+    @After
     public void tearDown() throws Exception {
         IoUtils.deleteContents(mStatsDir);
 
@@ -219,10 +220,9 @@
 
         mSession.close();
         mService = null;
-
-        super.tearDown();
     }
 
+    @Test
     public void testNetworkStatsWifi() throws Exception {
         // pretend that wifi network comes online; service should ask about full
         // network state, and poll any existing interfaces before updating.
@@ -231,15 +231,13 @@
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
 
         // verify service has empty history for wifi
         assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
-        verifyAndReset();
+
 
         // modify some number on wifi, and trigger poll event
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -248,14 +246,11 @@
         expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 1024L, 1L, 2048L, 2L));
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
-
-        replay();
         forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertNetworkTotal(sTemplateWifi, 1024L, 1L, 2048L, 2L, 0);
-        verifyAndReset();
+
 
         // and bump forward again, with counters going higher. this is
         // important, since polling should correctly subtract last snapshot.
@@ -265,17 +260,14 @@
         expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 4096L, 4L, 8192L, 8L));
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
-
-        replay();
         forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertNetworkTotal(sTemplateWifi, 4096L, 4L, 8192L, 8L, 0);
-        verifyAndReset();
 
     }
 
+    @Test
     public void testStatsRebootPersist() throws Exception {
         assertStatsFilesExist(false);
 
@@ -286,15 +278,13 @@
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
 
         // verify service has empty history for wifi
         assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
-        verifyAndReset();
+
 
         // modify some number on wifi, and trigger poll event
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -308,33 +298,28 @@
                 .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
                 .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
                 .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 0L));
-        expectNetworkStatsPoll();
-
         mService.setUidForeground(UID_RED, false);
         mService.incrementOperationCount(UID_RED, 0xFAAD, 4);
         mService.setUidForeground(UID_RED, true);
         mService.incrementOperationCount(UID_RED, 0xFAAD, 6);
 
-        replay();
         forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertNetworkTotal(sTemplateWifi, 1024L, 8L, 2048L, 16L, 0);
         assertUidTotal(sTemplateWifi, UID_RED, 1024L, 8L, 512L, 4L, 10);
-        assertUidTotal(sTemplateWifi, UID_RED, SET_DEFAULT, ROAMING_NO, 512L, 4L, 256L, 2L, 4);
-        assertUidTotal(sTemplateWifi, UID_RED, SET_FOREGROUND, ROAMING_NO, 512L, 4L, 256L, 2L,
-                6);
+        assertUidTotal(sTemplateWifi, UID_RED, SET_DEFAULT, METERED_NO, ROAMING_NO, 512L, 4L, 256L,
+                2L, 4);
+        assertUidTotal(sTemplateWifi, UID_RED, SET_FOREGROUND, METERED_NO, ROAMING_NO, 512L, 4L,
+                256L, 2L, 6);
         assertUidTotal(sTemplateWifi, UID_BLUE, 128L, 1L, 128L, 1L, 0);
-        verifyAndReset();
+
 
         // graceful shutdown system, which should trigger persist of stats, and
         // clear any values in memory.
         expectCurrentTime();
         expectDefaultSettings();
-        replay();
         mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SHUTDOWN));
-        verifyAndReset();
-
         assertStatsFilesExist(true);
 
         // boot through serviceReady() again
@@ -343,30 +328,22 @@
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectSystemReady();
 
-        // catch INetworkManagementEventObserver during systemReady()
-        final Capture<INetworkManagementEventObserver> networkObserver = new Capture<
-                INetworkManagementEventObserver>();
-        mNetManager.registerObserver(capture(networkObserver));
-        expectLastCall().atLeastOnce();
-
-        replay();
         mService.systemReady();
 
-        mNetworkObserver = networkObserver.getValue();
-
         // after systemReady(), we should have historical stats loaded again
         assertNetworkTotal(sTemplateWifi, 1024L, 8L, 2048L, 16L, 0);
         assertUidTotal(sTemplateWifi, UID_RED, 1024L, 8L, 512L, 4L, 10);
-        assertUidTotal(sTemplateWifi, UID_RED, SET_DEFAULT, ROAMING_NO, 512L, 4L, 256L, 2L, 4);
-        assertUidTotal(sTemplateWifi, UID_RED, SET_FOREGROUND, ROAMING_NO, 512L, 4L, 256L, 2L,
-                6);
+        assertUidTotal(sTemplateWifi, UID_RED, SET_DEFAULT, METERED_NO, ROAMING_NO, 512L, 4L, 256L,
+                2L, 4);
+        assertUidTotal(sTemplateWifi, UID_RED, SET_FOREGROUND, METERED_NO, ROAMING_NO, 512L, 4L,
+                256L, 2L, 6);
         assertUidTotal(sTemplateWifi, UID_BLUE, 128L, 1L, 128L, 1L, 0);
-        verifyAndReset();
 
     }
 
     // TODO: simulate reboot to test bucket resize
-    @Suppress
+    @Test
+    @Ignore
     public void testStatsBucketResize() throws Exception {
         NetworkStatsHistory history = null;
 
@@ -379,12 +356,10 @@
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
-        verifyAndReset();
+
 
         // modify some number on wifi, and trigger poll event
         incrementCurrentTime(2 * HOUR_IN_MILLIS);
@@ -393,9 +368,6 @@
         expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 512L, 4L, 512L, 4L));
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
-
-        replay();
         forcePollAndWaitForIdle();
 
         // verify service recorded history
@@ -403,7 +375,7 @@
         assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, 512L, 4L, 512L, 4L, 0);
         assertEquals(HOUR_IN_MILLIS, history.getBucketDuration());
         assertEquals(2, history.size());
-        verifyAndReset();
+
 
         // now change bucket duration setting and trigger another poll with
         // exact same values, which should resize existing buckets.
@@ -411,9 +383,6 @@
         expectSettings(0L, 30 * MINUTE_IN_MILLIS, WEEK_IN_MILLIS);
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
-
-        replay();
         forcePollAndWaitForIdle();
 
         // verify identical stats, but spread across 4 buckets now
@@ -421,10 +390,10 @@
         assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, 512L, 4L, 512L, 4L, 0);
         assertEquals(30 * MINUTE_IN_MILLIS, history.getBucketDuration());
         assertEquals(4, history.size());
-        verifyAndReset();
 
     }
 
+    @Test
     public void testUidStatsAcrossNetworks() throws Exception {
         // pretend first mobile network comes online
         expectCurrentTime();
@@ -432,12 +401,10 @@
         expectNetworkState(buildMobile3gState(IMSI_1));
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
-        verifyAndReset();
+
 
         // create some traffic on first network
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -449,11 +416,8 @@
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
                 .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
-        expectNetworkStatsPoll();
-
         mService.incrementOperationCount(UID_RED, 0xF00D, 10);
 
-        replay();
         forcePollAndWaitForIdle();
 
         // verify service recorded history
@@ -461,7 +425,7 @@
         assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
         assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 512L, 4L, 10);
         assertUidTotal(sTemplateImsi1, UID_BLUE, 512L, 4L, 0L, 0L, 0);
-        verifyAndReset();
+
 
         // now switch networks; this also tests that we're okay with interfaces
         // disappearing, to verify we don't count backwards.
@@ -475,13 +439,11 @@
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
                 .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
         forcePollAndWaitForIdle();
-        verifyAndReset();
+
 
         // create traffic on second network
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -494,11 +456,8 @@
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
                 .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 640L, 5L, 1024L, 8L, 0L)
                 .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xFAAD, 128L, 1L, 1024L, 8L, 0L));
-        expectNetworkStatsPoll();
-
         mService.incrementOperationCount(UID_BLUE, 0xFAAD, 10);
 
-        replay();
         forcePollAndWaitForIdle();
 
         // verify original history still intact
@@ -511,10 +470,10 @@
         assertNetworkTotal(sTemplateImsi2, 128L, 1L, 1024L, 8L, 0);
         assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
         assertUidTotal(sTemplateImsi2, UID_BLUE, 128L, 1L, 1024L, 8L, 10);
-        verifyAndReset();
 
     }
 
+    @Test
     public void testUidRemovedIsMoved() throws Exception {
         // pretend that network comes online
         expectCurrentTime();
@@ -522,12 +481,10 @@
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
-        verifyAndReset();
+
 
         // create some traffic
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -540,11 +497,8 @@
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
                 .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L)
                 .addValues(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L));
-        expectNetworkStatsPoll();
-
         mService.incrementOperationCount(UID_RED, 0xFAAD, 10);
 
-        replay();
         forcePollAndWaitForIdle();
 
         // verify service recorded history
@@ -552,7 +506,7 @@
         assertUidTotal(sTemplateWifi, UID_RED, 16L, 1L, 16L, 1L, 10);
         assertUidTotal(sTemplateWifi, UID_BLUE, 4096L, 258L, 512L, 32L, 0);
         assertUidTotal(sTemplateWifi, UID_GREEN, 16L, 1L, 16L, 1L, 0);
-        verifyAndReset();
+
 
         // now pretend two UIDs are uninstalled, which should migrate stats to
         // special "removed" bucket.
@@ -565,9 +519,6 @@
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
                 .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L)
                 .addValues(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L));
-        expectNetworkStatsPoll();
-
-        replay();
         final Intent intent = new Intent(ACTION_UID_REMOVED);
         intent.putExtra(EXTRA_UID, UID_BLUE);
         mServiceContext.sendBroadcast(intent);
@@ -581,10 +532,10 @@
         assertUidTotal(sTemplateWifi, UID_BLUE, 0L, 0L, 0L, 0L, 0);
         assertUidTotal(sTemplateWifi, UID_GREEN, 16L, 1L, 16L, 1L, 0);
         assertUidTotal(sTemplateWifi, UID_REMOVED, 4112L, 259L, 528L, 33L, 10);
-        verifyAndReset();
 
     }
 
+    @Test
     public void testUid3g4gCombinedByTemplate() throws Exception {
         // pretend that network comes online
         expectCurrentTime();
@@ -592,12 +543,10 @@
         expectNetworkState(buildMobile3gState(IMSI_1));
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
-        verifyAndReset();
+
 
         // create some traffic
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -607,16 +556,13 @@
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
-        expectNetworkStatsPoll();
-
         mService.incrementOperationCount(UID_RED, 0xF00D, 5);
 
-        replay();
         forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertUidTotal(sTemplateImsi1, UID_RED, 1024L, 8L, 1024L, 8L, 5);
-        verifyAndReset();
+
 
         // now switch over to 4g network
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -627,13 +573,11 @@
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
         forcePollAndWaitForIdle();
-        verifyAndReset();
+
 
         // create traffic on second network
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -645,19 +589,15 @@
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
                 .addValues(TEST_IFACE2, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
                 .addValues(TEST_IFACE2, UID_RED, SET_DEFAULT, 0xFAAD, 512L, 4L, 256L, 2L, 0L));
-        expectNetworkStatsPoll();
-
         mService.incrementOperationCount(UID_RED, 0xFAAD, 5);
 
-        replay();
         forcePollAndWaitForIdle();
 
         // verify that ALL_MOBILE template combines both
         assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 1280L, 10L, 10);
-
-        verifyAndReset();
     }
 
+    @Test
     public void testSummaryForAllUid() throws Exception {
         // pretend that network comes online
         expectCurrentTime();
@@ -665,12 +605,10 @@
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
-        verifyAndReset();
+
 
         // create some traffic for two apps
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -681,17 +619,14 @@
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
                 .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1024L, 8L, 512L, 4L, 0L));
-        expectNetworkStatsPoll();
-
         mService.incrementOperationCount(UID_RED, 0xF00D, 1);
 
-        replay();
         forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertUidTotal(sTemplateWifi, UID_RED, 50L, 5L, 50L, 5L, 1);
         assertUidTotal(sTemplateWifi, UID_BLUE, 1024L, 8L, 512L, 4L, 0);
-        verifyAndReset();
+
 
         // now create more traffic in next hour, but only for one app
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -702,33 +637,29 @@
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
                 .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 2048L, 16L, 1024L, 8L, 0L));
-        expectNetworkStatsPoll();
-
-        replay();
         forcePollAndWaitForIdle();
 
         // first verify entire history present
         NetworkStats stats = mSession.getSummaryForAllUid(
                 sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true);
         assertEquals(3, stats.size());
-        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_NO, 50L, 5L,
-                50L, 5L, 1);
-        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, ROAMING_NO, 10L, 1L, 10L,
-                1L, 1);
-        assertValues(stats, IFACE_ALL, UID_BLUE, SET_DEFAULT, TAG_NONE, ROAMING_NO, 2048L, 16L,
-                1024L, 8L, 0);
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 50L,
+                5L, 50L, 5L, 1);
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, 10L,
+                1L, 10L, 1L, 1);
+        assertValues(stats, IFACE_ALL, UID_BLUE, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                2048L, 16L, 1024L, 8L, 0);
 
         // now verify that recent history only contains one uid
         final long currentTime = currentTimeMillis();
         stats = mSession.getSummaryForAllUid(
                 sTemplateWifi, currentTime - HOUR_IN_MILLIS, currentTime, true);
         assertEquals(1, stats.size());
-        assertValues(stats, IFACE_ALL, UID_BLUE, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1024L, 8L,
-                512L, 4L, 0);
-
-        verifyAndReset();
+        assertValues(stats, IFACE_ALL, UID_BLUE, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                1024L, 8L, 512L, 4L, 0);
     }
 
+    @Test
     public void testForegroundBackground() throws Exception {
         // pretend that network comes online
         expectCurrentTime();
@@ -736,12 +667,10 @@
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
-        verifyAndReset();
+
 
         // create some initial traffic
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -751,16 +680,13 @@
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L)
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L));
-        expectNetworkStatsPoll();
-
         mService.incrementOperationCount(UID_RED, 0xF00D, 1);
 
-        replay();
         forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertUidTotal(sTemplateWifi, UID_RED, 128L, 2L, 128L, 2L, 1);
-        verifyAndReset();
+
 
         // now switch to foreground
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -772,12 +698,9 @@
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L)
                 .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 32L, 2L, 32L, 2L, 0L)
                 .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 1L, 1L, 1L, 1L, 0L));
-        expectNetworkStatsPoll();
-
         mService.setUidForeground(UID_RED, true);
         mService.incrementOperationCount(UID_RED, 0xFAAD, 1);
 
-        replay();
         forcePollAndWaitForIdle();
 
         // test that we combined correctly
@@ -787,18 +710,59 @@
         final NetworkStats stats = mSession.getSummaryForAllUid(
                 sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true);
         assertEquals(4, stats.size());
-        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_NO, 128L, 2L,
-                128L, 2L, 1);
-        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, ROAMING_NO, 64L, 1L, 64L,
-                1L, 1);
-        assertValues(stats, IFACE_ALL, UID_RED, SET_FOREGROUND, TAG_NONE, ROAMING_NO, 32L, 2L,
-                32L, 2L, 1);
-        assertValues(stats, IFACE_ALL, UID_RED, SET_FOREGROUND, 0xFAAD, ROAMING_NO, 1L, 1L, 1L,
-                1L, 1);
-
-        verifyAndReset();
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 128L,
+                2L, 128L, 2L, 1);
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, 64L,
+                1L, 64L, 1L, 1);
+        assertValues(stats, IFACE_ALL, UID_RED, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                32L, 2L, 32L, 2L, 1);
+        assertValues(stats, IFACE_ALL, UID_RED, SET_FOREGROUND, 0xFAAD, METERED_NO, ROAMING_NO, 1L,
+                1L, 1L, 1L, 1);
     }
 
+    @Test
+    public void testMetered() throws Exception {
+        // pretend that network comes online
+        expectCurrentTime();
+        expectDefaultSettings();
+        expectNetworkState(buildWifiState(true /* isMetered */));
+        expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(buildEmptyStats());
+        expectBandwidthControlCheck();
+
+        mService.forceUpdateIfaces();
+
+
+        // create some initial traffic
+        incrementCurrentTime(HOUR_IN_MILLIS);
+        expectCurrentTime();
+        expectDefaultSettings();
+        expectNetworkStatsSummary(buildEmptyStats());
+        // Note that all traffic from NetworkManagementService is tagged as METERED_NO and
+        // ROAMING_NO, because metered and roaming isn't tracked at that layer. We layer it
+        // on top by inspecting the iface properties.
+        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 128L,
+                        2L, 128L, 2L, 0L)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, 64L,
+                        1L, 64L, 1L, 0L));
+        mService.incrementOperationCount(UID_RED, 0xF00D, 1);
+
+        forcePollAndWaitForIdle();
+
+        // verify service recorded history
+        assertUidTotal(sTemplateWifi, UID_RED, 128L, 2L, 128L, 2L, 1);
+        // verify entire history present
+        final NetworkStats stats = mSession.getSummaryForAllUid(
+                sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true);
+        assertEquals(2, stats.size());
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+                128L, 2L, 128L, 2L, 1);
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO, 64L,
+                1L, 64L, 1L, 1);
+    }
+
+    @Test
     public void testRoaming() throws Exception {
         // pretend that network comes online
         expectCurrentTime();
@@ -806,29 +770,24 @@
         expectNetworkState(buildMobile3gState(IMSI_1, true /* isRoaming */));
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
-        verifyAndReset();
+
 
         // Create some traffic
         incrementCurrentTime(HOUR_IN_MILLIS);
         expectCurrentTime();
         expectDefaultSettings();
         expectNetworkStatsSummary(buildEmptyStats());
-        // Note that all traffic from NetworkManagementService is tagged as ROAMING_NO, because
-        // roaming isn't tracked at that layer. We layer it on top by inspecting the iface
-        // properties.
+        // Note that all traffic from NetworkManagementService is tagged as METERED_NO and
+        // ROAMING_NO, because metered and roaming isn't tracked at that layer. We layer it
+        // on top by inspecting the iface properties.
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_NO, 128L, 2L,
-                        128L, 2L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, ROAMING_NO, 64L, 1L, 64L,
-                        1L, 0L));
-        expectNetworkStatsPoll();
-
-        replay();
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_NO,
+                        128L, 2L, 128L, 2L, 0L)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_ALL, ROAMING_NO, 64L,
+                        1L, 64L, 1L, 0L));
         forcePollAndWaitForIdle();
 
         // verify service recorded history
@@ -838,14 +797,13 @@
         final NetworkStats stats = mSession.getSummaryForAllUid(
                 sTemplateImsi1, Long.MIN_VALUE, Long.MAX_VALUE, true);
         assertEquals(2, stats.size());
-        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_YES, 128L, 2L,
-                128L, 2L, 0);
-        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, ROAMING_YES, 64L, 1L, 64L,
-                1L, 0);
-
-        verifyAndReset();
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_YES,
+                128L, 2L, 128L, 2L, 0);
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_ALL, ROAMING_YES, 64L,
+                1L, 64L, 1L, 0);
     }
 
+    @Test
     public void testTethering() throws Exception {
         // pretend first mobile network comes online
         expectCurrentTime();
@@ -853,12 +811,10 @@
         expectNetworkState(buildMobile3gState(IMSI_1));
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
-        verifyAndReset();
+
 
         // create some tethering traffic
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -871,22 +827,20 @@
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L);
         final String[] tetherIfacePairs = new String[] { TEST_IFACE, "wlan0" };
         final NetworkStats tetherStats = new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L, 0L);
+                .addValues(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L,
+                        0L);
 
         expectNetworkStatsUidDetail(uidStats, tetherIfacePairs, tetherStats);
-        expectNetworkStatsPoll();
-
-        replay();
         forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertNetworkTotal(sTemplateImsi1, 2048L, 16L, 512L, 4L, 0);
         assertUidTotal(sTemplateImsi1, UID_RED, 128L, 2L, 128L, 2L, 0);
         assertUidTotal(sTemplateImsi1, UID_TETHERING, 1920L, 14L, 384L, 2L, 0);
-        verifyAndReset();
 
     }
 
+    @Test
     public void testRegisterUsageCallback() throws Exception {
         // pretend that wifi network comes online; service should ask about full
         // network state, and poll any existing interfaces before updating.
@@ -895,16 +849,12 @@
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
         expectBandwidthControlCheck();
 
-        replay();
         mService.forceUpdateIfaces();
 
         // verify service has empty history for wifi
         assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
-        verifyAndReset();
-
         String callingPackage = "the.calling.package";
         long thresholdInBytes = 1L;  // very small; should be overriden by framework
         DataUsageRequest inputRequest = new DataUsageRequest(
@@ -915,23 +865,18 @@
         LatchedHandler latchedHandler = new LatchedHandler(Looper.getMainLooper(), cv);
         Messenger messenger = new Messenger(latchedHandler);
 
-        // Allow binder to connect
-        IBinder mockBinder = createMock(IBinder.class);
-        mockBinder.linkToDeath((IBinder.DeathRecipient) anyObject(), anyInt());
-        EasyMock.replay(mockBinder);
-
         // Force poll
         expectCurrentTime();
         expectDefaultSettings();
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
-        replay();
+
+
 
         // Register and verify request and that binder was called
         DataUsageRequest request =
                 mService.registerUsageCallback(callingPackage, inputRequest,
-                        messenger, mockBinder);
+                        messenger, mBinder);
         assertTrue(request.requestId > 0);
         assertTrue(Objects.equals(sTemplateWifi, request.template));
         long minThresholdInBytes = 2 * 1024 * 1024; // 2 MB
@@ -941,11 +886,11 @@
         mHandler.sendMessage(mHandler.obtainMessage(-1));
         mHandlerThread.waitForIdle(WAIT_TIMEOUT);
 
-        verifyAndReset();
+
 
         // Make sure that the caller binder gets connected
-        EasyMock.verify(mockBinder);
-        EasyMock.reset(mockBinder);
+        verify(mBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+
 
         // modify some number on wifi, and trigger poll event
         // not enough traffic to call data usage callback
@@ -955,13 +900,9 @@
         expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 1024L, 1L, 2048L, 2L));
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
-
-        replay();
         forcePollAndWaitForIdle();
 
         // verify service recorded history
-        verifyAndReset();
         assertNetworkTotal(sTemplateWifi, 1024L, 1L, 2048L, 2L, 0);
 
         // make sure callback has not being called
@@ -975,14 +916,11 @@
         expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 4096000L, 4L, 8192000L, 8L));
         expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
-
-        replay();
         forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertNetworkTotal(sTemplateWifi, 4096000L, 4L, 8192000L, 8L, 0);
-        verifyAndReset();
+
 
         // Wait for the caller to ack receipt of CALLBACK_LIMIT_REACHED
         assertTrue(cv.block(WAIT_TIMEOUT));
@@ -990,9 +928,7 @@
         cv.close();
 
         // Allow binder to disconnect
-        expect(mockBinder.unlinkToDeath((IBinder.DeathRecipient) anyObject(), anyInt()))
-                .andReturn(true);
-        EasyMock.replay(mockBinder);
+        when(mBinder.unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt())).thenReturn(true);
 
         // Unregister request
         mService.unregisterUsageRequest(request);
@@ -1002,9 +938,10 @@
         assertEquals(NetworkStatsManager.CALLBACK_RELEASED, latchedHandler.mLastMessageType);
 
         // Make sure that the caller binder gets disconnected
-        EasyMock.verify(mockBinder);
+        verify(mBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
     }
 
+    @Test
     public void testUnregisterUsageCallback_unknown_noop() throws Exception {
         String callingPackage = "the.calling.package";
         long thresholdInBytes = 10 * 1024 * 1024;  // 10 MB
@@ -1034,18 +971,18 @@
 
         // verify summary API
         final NetworkStats stats = mSession.getSummaryForNetwork(template, start, end);
-        assertValues(stats, IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, ROAMING_NO, rxBytes,
-                rxPackets, txBytes, txPackets, operations);
+        assertValues(stats, IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_NO,
+                rxBytes, rxPackets, txBytes, txPackets, operations);
     }
 
     private void assertUidTotal(NetworkTemplate template, int uid, long rxBytes, long rxPackets,
             long txBytes, long txPackets, int operations) throws Exception {
-        assertUidTotal(template, uid, SET_ALL, ROAMING_ALL, rxBytes, rxPackets, txBytes, txPackets,
-                operations);
+        assertUidTotal(template, uid, SET_ALL, METERED_ALL, ROAMING_ALL, rxBytes, rxPackets,
+                txBytes, txPackets, operations);
     }
 
-    private void assertUidTotal(NetworkTemplate template, int uid, int set, int roaming,
-            long rxBytes, long rxPackets, long txBytes, long txPackets, int operations)
+    private void assertUidTotal(NetworkTemplate template, int uid, int set, int metered,
+            int roaming, long rxBytes, long rxPackets, long txBytes, long txPackets, int operations)
             throws Exception {
         // verify history API
         final NetworkStatsHistory history = mSession.getHistoryForUid(
@@ -1056,38 +993,35 @@
         // verify summary API
         final NetworkStats stats = mSession.getSummaryForAllUid(
                 template, Long.MIN_VALUE, Long.MAX_VALUE, false);
-        assertValues(stats, IFACE_ALL, uid, set, TAG_NONE, roaming, rxBytes, rxPackets, txBytes,
-                txPackets, operations);
+        assertValues(stats, IFACE_ALL, uid, set, TAG_NONE, metered, roaming, rxBytes, rxPackets,
+                txBytes, txPackets, operations);
     }
 
     private void expectSystemReady() throws Exception {
-        mNetManager.setGlobalAlert(anyLong());
-        expectLastCall().atLeastOnce();
-
         expectNetworkStatsSummary(buildEmptyStats());
         expectBandwidthControlCheck();
     }
 
     private void expectNetworkState(NetworkState... state) throws Exception {
-        expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
+        when(mConnManager.getAllNetworkState()).thenReturn(state);
 
         final LinkProperties linkProp = state.length > 0 ? state[0].linkProperties : null;
-        expect(mConnManager.getActiveLinkProperties()).andReturn(linkProp).atLeastOnce();
+        when(mConnManager.getActiveLinkProperties()).thenReturn(linkProp);
     }
 
     private void expectNetworkStatsSummary(NetworkStats summary) throws Exception {
-        expect(mConnManager.getAllVpnInfo()).andReturn(new VpnInfo[0]).atLeastOnce();
+        when(mConnManager.getAllVpnInfo()).thenReturn(new VpnInfo[0]);
 
         expectNetworkStatsSummaryDev(summary);
         expectNetworkStatsSummaryXt(summary);
     }
 
     private void expectNetworkStatsSummaryDev(NetworkStats summary) throws Exception {
-        expect(mNetManager.getNetworkStatsSummaryDev()).andReturn(summary).atLeastOnce();
+        when(mNetManager.getNetworkStatsSummaryDev()).thenReturn(summary);
     }
 
     private void expectNetworkStatsSummaryXt(NetworkStats summary) throws Exception {
-        expect(mNetManager.getNetworkStatsSummaryXt()).andReturn(summary).atLeastOnce();
+        when(mNetManager.getNetworkStatsSummaryXt()).thenReturn(summary);
     }
 
     private void expectNetworkStatsUidDetail(NetworkStats detail) throws Exception {
@@ -1097,11 +1031,10 @@
     private void expectNetworkStatsUidDetail(
             NetworkStats detail, String[] tetherIfacePairs, NetworkStats tetherStats)
             throws Exception {
-        expect(mNetManager.getNetworkStatsUidDetail(eq(UID_ALL))).andReturn(detail).atLeastOnce();
+        when(mNetManager.getNetworkStatsUidDetail(UID_ALL)).thenReturn(detail);
 
         // also include tethering details, since they are folded into UID
-        expect(mNetManager.getNetworkStatsTethering())
-                .andReturn(tetherStats).atLeastOnce();
+        when(mNetManager.getNetworkStatsTethering()).thenReturn(tetherStats);
     }
 
     private void expectDefaultSettings() throws Exception {
@@ -1110,38 +1043,33 @@
 
     private void expectSettings(long persistBytes, long bucketDuration, long deleteAge)
             throws Exception {
-        expect(mSettings.getPollInterval()).andReturn(HOUR_IN_MILLIS).anyTimes();
-        expect(mSettings.getTimeCacheMaxAge()).andReturn(DAY_IN_MILLIS).anyTimes();
-        expect(mSettings.getSampleEnabled()).andReturn(true).anyTimes();
+        when(mSettings.getPollInterval()).thenReturn(HOUR_IN_MILLIS);
+        when(mSettings.getTimeCacheMaxAge()).thenReturn(DAY_IN_MILLIS);
+        when(mSettings.getSampleEnabled()).thenReturn(true);
 
         final Config config = new Config(bucketDuration, deleteAge, deleteAge);
-        expect(mSettings.getDevConfig()).andReturn(config).anyTimes();
-        expect(mSettings.getXtConfig()).andReturn(config).anyTimes();
-        expect(mSettings.getUidConfig()).andReturn(config).anyTimes();
-        expect(mSettings.getUidTagConfig()).andReturn(config).anyTimes();
+        when(mSettings.getDevConfig()).thenReturn(config);
+        when(mSettings.getXtConfig()).thenReturn(config);
+        when(mSettings.getUidConfig()).thenReturn(config);
+        when(mSettings.getUidTagConfig()).thenReturn(config);
 
-        expect(mSettings.getGlobalAlertBytes(anyLong())).andReturn(MB_IN_BYTES).anyTimes();
-        expect(mSettings.getDevPersistBytes(anyLong())).andReturn(MB_IN_BYTES).anyTimes();
-        expect(mSettings.getXtPersistBytes(anyLong())).andReturn(MB_IN_BYTES).anyTimes();
-        expect(mSettings.getUidPersistBytes(anyLong())).andReturn(MB_IN_BYTES).anyTimes();
-        expect(mSettings.getUidTagPersistBytes(anyLong())).andReturn(MB_IN_BYTES).anyTimes();
+        when(mSettings.getGlobalAlertBytes(anyLong())).thenReturn(MB_IN_BYTES);
+        when(mSettings.getDevPersistBytes(anyLong())).thenReturn(MB_IN_BYTES);
+        when(mSettings.getXtPersistBytes(anyLong())).thenReturn(MB_IN_BYTES);
+        when(mSettings.getUidPersistBytes(anyLong())).thenReturn(MB_IN_BYTES);
+        when(mSettings.getUidTagPersistBytes(anyLong())).thenReturn(MB_IN_BYTES);
     }
 
     private void expectCurrentTime() throws Exception {
-        expect(mTime.forceRefresh()).andReturn(false).anyTimes();
-        expect(mTime.hasCache()).andReturn(true).anyTimes();
-        expect(mTime.currentTimeMillis()).andReturn(currentTimeMillis()).anyTimes();
-        expect(mTime.getCacheAge()).andReturn(0L).anyTimes();
-        expect(mTime.getCacheCertainty()).andReturn(0L).anyTimes();
-    }
-
-    private void expectNetworkStatsPoll() throws Exception {
-        mNetManager.setGlobalAlert(anyLong());
-        expectLastCall().anyTimes();
+        when(mTime.forceRefresh()).thenReturn(false);
+        when(mTime.hasCache()).thenReturn(true);
+        when(mTime.currentTimeMillis()).thenReturn(currentTimeMillis());
+        when(mTime.getCacheAge()).thenReturn(0L);
+        when(mTime.getCacheCertainty()).thenReturn(0L);
     }
 
     private void expectBandwidthControlCheck() throws Exception {
-        expect(mNetManager.isBandwidthControlEnabled()).andReturn(true).atLeastOnce();
+        when(mNetManager.isBandwidthControlEnabled()).thenReturn(true);
     }
 
     private void assertStatsFilesExist(boolean exist) {
@@ -1154,8 +1082,8 @@
     }
 
     private static void assertValues(NetworkStats stats, String iface, int uid, int set,
-            int tag, int roaming, long rxBytes, long rxPackets, long txBytes, long txPackets,
-            int operations) {
+            int tag, int metered, int roaming, long rxBytes, long rxPackets, long txBytes,
+            long txPackets, int operations) {
         final NetworkStats.Entry entry = new NetworkStats.Entry();
         List<Integer> sets = new ArrayList<>();
         if (set == SET_DEFAULT || set == SET_ALL) {
@@ -1173,11 +1101,21 @@
             roamings.add(ROAMING_YES);
         }
 
+        List<Integer> meterings = new ArrayList<>();
+        if (metered == METERED_NO || metered == METERED_ALL) {
+            meterings.add(METERED_NO);
+        }
+        if (metered == METERED_YES || metered == METERED_ALL) {
+            meterings.add(METERED_YES);
+        }
+
         for (int s : sets) {
             for (int r : roamings) {
-                final int i = stats.findIndex(iface, uid, s, tag, r);
-                if (i != -1) {
-                    entry.add(stats.getValues(i, null));
+                for (int m : meterings) {
+                    final int i = stats.findIndex(iface, uid, s, tag, m, r);
+                    if (i != -1) {
+                        entry.add(stats.getValues(i, null));
+                    }
                 }
             }
         }
@@ -1200,11 +1138,19 @@
     }
 
     private static NetworkState buildWifiState() {
+        return buildWifiState(false);
+    }
+
+    private static NetworkState buildWifiState(boolean isMetered) {
         final NetworkInfo info = new NetworkInfo(TYPE_WIFI, 0, null, null);
         info.setDetailedState(DetailedState.CONNECTED, null, null);
         final LinkProperties prop = new LinkProperties();
         prop.setInterfaceName(TEST_IFACE);
-        return new NetworkState(info, prop, null, null, null, TEST_SSID);
+        final NetworkCapabilities capabilities = new NetworkCapabilities();
+        if (!isMetered) {
+            capabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+        }
+        return new NetworkState(info, prop, capabilities, null, null, TEST_SSID);
     }
 
     private static NetworkState buildMobile3gState(String subscriberId) {
@@ -1218,7 +1164,8 @@
         info.setRoaming(isRoaming);
         final LinkProperties prop = new LinkProperties();
         prop.setInterfaceName(TEST_IFACE);
-        return new NetworkState(info, prop, null, null, subscriberId, null);
+        final NetworkCapabilities capabilities = new NetworkCapabilities();
+        return new NetworkState(info, prop, capabilities, null, subscriberId, null);
     }
 
     private static NetworkState buildMobile4gState(String iface) {
@@ -1226,7 +1173,8 @@
         info.setDetailedState(DetailedState.CONNECTED, null, null);
         final LinkProperties prop = new LinkProperties();
         prop.setInterfaceName(iface);
-        return new NetworkState(info, prop, null, null, null, null);
+        final NetworkCapabilities capabilities = new NetworkCapabilities();
+        return new NetworkState(info, prop, capabilities, null, null, null);
     }
 
     private NetworkStats buildEmptyStats() {
@@ -1249,15 +1197,6 @@
         mElapsedRealtime += duration;
     }
 
-    private void replay() {
-        EasyMock.replay(mNetManager, mTime, mSettings, mConnManager);
-    }
-
-    private void verifyAndReset() {
-        EasyMock.verify(mNetManager, mTime, mSettings, mConnManager);
-        EasyMock.reset(mNetManager, mTime, mSettings, mConnManager);
-    }
-
     private void forcePollAndWaitForIdle() {
         mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
         // Send dummy message to make sure that any previous message has been handled
diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java
new file mode 100644
index 0000000..b984bbf
--- /dev/null
+++ b/tests/net/java/android/net/ConnectivityManagerTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ConnectivityManagerTest {
+    static NetworkCapabilities verifyNetworkCapabilities(
+            int legacyType, int transportType, int... capabilities) {
+        final NetworkCapabilities nc = ConnectivityManager.networkCapabilitiesForType(legacyType);
+        assertNotNull(nc);
+        assertTrue(nc.hasTransport(transportType));
+        for (int capability : capabilities) {
+            assertTrue(nc.hasCapability(capability));
+        }
+
+        return nc;
+    }
+
+    static void verifyUnrestrictedNetworkCapabilities(int legacyType, int transportType) {
+        verifyNetworkCapabilities(
+                legacyType,
+                transportType,
+                NET_CAPABILITY_INTERNET,
+                NET_CAPABILITY_NOT_RESTRICTED,
+                NET_CAPABILITY_NOT_VPN,
+                NET_CAPABILITY_TRUSTED);
+    }
+
+    static void verifyRestrictedMobileNetworkCapabilities(int legacyType, int capability) {
+        final NetworkCapabilities nc = verifyNetworkCapabilities(
+                legacyType,
+                TRANSPORT_CELLULAR,
+                capability,
+                NET_CAPABILITY_NOT_VPN,
+                NET_CAPABILITY_TRUSTED);
+
+        assertFalse(nc.hasCapability(NET_CAPABILITY_INTERNET));
+        assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+    }
+
+    @Test
+    public void testNetworkCapabilitiesForTypeMobile() {
+        verifyUnrestrictedNetworkCapabilities(
+                ConnectivityManager.TYPE_MOBILE, TRANSPORT_CELLULAR);
+    }
+
+    @Test
+    public void testNetworkCapabilitiesForTypeMobileCbs() {
+        verifyRestrictedMobileNetworkCapabilities(
+                ConnectivityManager.TYPE_MOBILE_CBS, NET_CAPABILITY_CBS);
+    }
+
+    @Test
+    public void testNetworkCapabilitiesForTypeMobileDun() {
+        verifyRestrictedMobileNetworkCapabilities(
+                ConnectivityManager.TYPE_MOBILE_DUN, NET_CAPABILITY_DUN);
+    }
+
+    @Test
+    public void testNetworkCapabilitiesForTypeMobileFota() {
+        verifyRestrictedMobileNetworkCapabilities(
+                ConnectivityManager.TYPE_MOBILE_FOTA, NET_CAPABILITY_FOTA);
+    }
+
+    @Test
+    public void testNetworkCapabilitiesForTypeMobileHipri() {
+        verifyUnrestrictedNetworkCapabilities(
+                ConnectivityManager.TYPE_MOBILE_HIPRI, TRANSPORT_CELLULAR);
+    }
+
+    @Test
+    public void testNetworkCapabilitiesForTypeMobileIms() {
+        verifyRestrictedMobileNetworkCapabilities(
+                ConnectivityManager.TYPE_MOBILE_IMS, NET_CAPABILITY_IMS);
+    }
+
+    @Test
+    public void testNetworkCapabilitiesForTypeMobileMms() {
+        final NetworkCapabilities nc = verifyNetworkCapabilities(
+                ConnectivityManager.TYPE_MOBILE_MMS,
+                TRANSPORT_CELLULAR,
+                NET_CAPABILITY_MMS,
+                NET_CAPABILITY_NOT_VPN,
+                NET_CAPABILITY_TRUSTED);
+
+        assertFalse(nc.hasCapability(NET_CAPABILITY_INTERNET));
+    }
+
+    @Test
+    public void testNetworkCapabilitiesForTypeMobileSupl() {
+        final NetworkCapabilities nc = verifyNetworkCapabilities(
+                ConnectivityManager.TYPE_MOBILE_SUPL,
+                TRANSPORT_CELLULAR,
+                NET_CAPABILITY_SUPL,
+                NET_CAPABILITY_NOT_VPN,
+                NET_CAPABILITY_TRUSTED);
+
+        assertFalse(nc.hasCapability(NET_CAPABILITY_INTERNET));
+    }
+
+    @Test
+    public void testNetworkCapabilitiesForTypeWifi() {
+        verifyUnrestrictedNetworkCapabilities(
+                ConnectivityManager.TYPE_WIFI, TRANSPORT_WIFI);
+    }
+
+    @Test
+    public void testNetworkCapabilitiesForTypeWifiP2p() {
+        final NetworkCapabilities nc = verifyNetworkCapabilities(
+                ConnectivityManager.TYPE_WIFI_P2P,
+                TRANSPORT_WIFI,
+                NET_CAPABILITY_NOT_RESTRICTED, NET_CAPABILITY_NOT_VPN,
+                NET_CAPABILITY_TRUSTED, NET_CAPABILITY_WIFI_P2P);
+
+        assertFalse(nc.hasCapability(NET_CAPABILITY_INTERNET));
+    }
+
+    @Test
+    public void testNetworkCapabilitiesForTypeBluetooth() {
+        verifyUnrestrictedNetworkCapabilities(
+                ConnectivityManager.TYPE_BLUETOOTH, TRANSPORT_BLUETOOTH);
+    }
+
+    @Test
+    public void testNetworkCapabilitiesForTypeEthernet() {
+        verifyUnrestrictedNetworkCapabilities(
+                ConnectivityManager.TYPE_ETHERNET, TRANSPORT_ETHERNET);
+    }
+}
diff --git a/tests/net/java/android/net/NetworkCapabilitiesTest.java b/tests/net/java/android/net/NetworkCapabilitiesTest.java
new file mode 100644
index 0000000..e3b06c8
--- /dev/null
+++ b/tests/net/java/android/net/NetworkCapabilitiesTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES;
+import static android.net.NetworkCapabilities.UNRESTRICTED_CAPABILITIES;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+
+import android.net.NetworkCapabilities;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkCapabilitiesTest {
+    @Test
+    public void testMaybeMarkCapabilitiesRestricted() {
+        // verify EIMS is restricted
+        assertEquals((1 << NET_CAPABILITY_EIMS) & RESTRICTED_CAPABILITIES,
+                (1 << NET_CAPABILITY_EIMS));
+
+        // verify CBS is also restricted
+        assertEquals((1 << NET_CAPABILITY_CBS) & RESTRICTED_CAPABILITIES,
+                (1 << NET_CAPABILITY_CBS));
+
+        // verify default is not restricted
+        assertEquals((1 << NET_CAPABILITY_INTERNET) & RESTRICTED_CAPABILITIES, 0);
+
+        // just to see
+        assertEquals(RESTRICTED_CAPABILITIES & UNRESTRICTED_CAPABILITIES, 0);
+
+        // check that internet does not get restricted
+        NetworkCapabilities netCap = new NetworkCapabilities();
+        netCap.addCapability(NET_CAPABILITY_INTERNET);
+        netCap.maybeMarkCapabilitiesRestricted();
+        assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+
+        // metered-ness shouldn't matter
+        netCap = new NetworkCapabilities();
+        netCap.addCapability(NET_CAPABILITY_INTERNET);
+        netCap.addCapability(NET_CAPABILITY_NOT_METERED);
+        netCap.maybeMarkCapabilitiesRestricted();
+        assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+        netCap = new NetworkCapabilities();
+        netCap.addCapability(NET_CAPABILITY_INTERNET);
+        netCap.removeCapability(NET_CAPABILITY_NOT_METERED);
+        netCap.maybeMarkCapabilitiesRestricted();
+        assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+
+        // add EIMS - bundled with unrestricted means it's unrestricted
+        netCap = new NetworkCapabilities();
+        netCap.addCapability(NET_CAPABILITY_INTERNET);
+        netCap.addCapability(NET_CAPABILITY_EIMS);
+        netCap.addCapability(NET_CAPABILITY_NOT_METERED);
+        netCap.maybeMarkCapabilitiesRestricted();
+        assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+        netCap = new NetworkCapabilities();
+        netCap.addCapability(NET_CAPABILITY_INTERNET);
+        netCap.addCapability(NET_CAPABILITY_EIMS);
+        netCap.removeCapability(NET_CAPABILITY_NOT_METERED);
+        netCap.maybeMarkCapabilitiesRestricted();
+        assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+
+        // just a restricted cap should be restricted regardless of meteredness
+        netCap = new NetworkCapabilities();
+        netCap.addCapability(NET_CAPABILITY_EIMS);
+        netCap.addCapability(NET_CAPABILITY_NOT_METERED);
+        netCap.maybeMarkCapabilitiesRestricted();
+        assertFalse(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+        netCap = new NetworkCapabilities();
+        netCap.addCapability(NET_CAPABILITY_EIMS);
+        netCap.removeCapability(NET_CAPABILITY_NOT_METERED);
+        netCap.maybeMarkCapabilitiesRestricted();
+        assertFalse(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+
+        // try 2 restricted caps
+        netCap = new NetworkCapabilities();
+        netCap.addCapability(NET_CAPABILITY_CBS);
+        netCap.addCapability(NET_CAPABILITY_EIMS);
+        netCap.addCapability(NET_CAPABILITY_NOT_METERED);
+        netCap.maybeMarkCapabilitiesRestricted();
+        assertFalse(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+        netCap = new NetworkCapabilities();
+        netCap.addCapability(NET_CAPABILITY_CBS);
+        netCap.addCapability(NET_CAPABILITY_EIMS);
+        netCap.removeCapability(NET_CAPABILITY_NOT_METERED);
+        netCap.maybeMarkCapabilitiesRestricted();
+        assertFalse(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+    }
+
+}
diff --git a/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java b/tests/net/java/android/net/NetworkStatsHistoryTest.java
similarity index 99%
rename from core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
rename to tests/net/java/android/net/NetworkStatsHistoryTest.java
index 9a08f41..e7b91b5 100644
--- a/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
+++ b/tests/net/java/android/net/NetworkStatsHistoryTest.java
@@ -38,7 +38,7 @@
 import android.test.suitebuilder.annotation.Suppress;
 import android.util.Log;
 
-import com.android.frameworks.coretests.R;
+import com.android.frameworks.tests.net.R;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
diff --git a/core/tests/coretests/src/android/net/NetworkStatsTest.java b/tests/net/java/android/net/NetworkStatsTest.java
similarity index 65%
rename from core/tests/coretests/src/android/net/NetworkStatsTest.java
rename to tests/net/java/android/net/NetworkStatsTest.java
index d48a67a..eb85eb4 100644
--- a/core/tests/coretests/src/android/net/NetworkStatsTest.java
+++ b/tests/net/java/android/net/NetworkStatsTest.java
@@ -16,6 +16,9 @@
 
 package android.net;
 
+import static android.net.NetworkStats.METERED_ALL;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.METERED_YES;
 import static android.net.NetworkStats.ROAMING_ALL;
 import static android.net.NetworkStats.ROAMING_NO;
 import static android.net.NetworkStats.ROAMING_YES;
@@ -45,103 +48,124 @@
     private static final long TEST_START = 1194220800000L;
 
     public void testFindIndex() throws Exception {
-        final NetworkStats stats = new NetworkStats(TEST_START, 4)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1024L, 8L, 0L,
-                        0L, 10)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, ROAMING_NO, 0L, 0L, 1024L,
-                        8L, 11)
-                .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1024L, 8L,
-                        1024L, 8L, 12)
-                .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, ROAMING_YES, 1024L, 8L,
-                        1024L, 8L, 12);
+        final NetworkStats stats = new NetworkStats(TEST_START, 5)
+                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 1024L,
+                        8L, 0L, 0L, 10)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 0L, 0L,
+                        1024L, 8L, 11)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, 0L, 0L,
+                        1024L, 8L, 11)
+                .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 1024L,
+                        8L, 1024L, 8L, 12)
+                .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, 1024L,
+                        8L, 1024L, 8L, 12);
 
-        assertEquals(3, stats.findIndex(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, ROAMING_YES));
-        assertEquals(2, stats.findIndex(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, ROAMING_NO));
-        assertEquals(1, stats.findIndex(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, ROAMING_NO));
-        assertEquals(0, stats.findIndex(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO));
-        assertEquals(-1, stats.findIndex(TEST_IFACE, 6, SET_DEFAULT, TAG_NONE, ROAMING_NO));
+        assertEquals(4, stats.findIndex(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_YES,
+                ROAMING_YES));
+        assertEquals(3, stats.findIndex(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO,
+                ROAMING_NO));
+        assertEquals(2, stats.findIndex(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES,
+                ROAMING_NO));
+        assertEquals(1, stats.findIndex(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO,
+                ROAMING_NO));
+        assertEquals(0, stats.findIndex(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO,
+                ROAMING_NO));
+        assertEquals(-1, stats.findIndex(TEST_IFACE, 6, SET_DEFAULT, TAG_NONE, METERED_NO,
+                ROAMING_NO));
     }
 
     public void testFindIndexHinted() {
         final NetworkStats stats = new NetworkStats(TEST_START, 3)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1024L, 8L, 0L,
-                        0L, 10)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, ROAMING_NO, 0L, 0L, 1024L,
-                        8L, 11)
-                .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1024L, 8L,
-                        1024L, 8L, 12)
-                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, ROAMING_NO, 1024L, 8L,
-                        0L, 0L, 10)
-                .addValues(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, ROAMING_NO, 0L, 0L, 1024L,
-                        8L, 11)
-                .addValues(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1024L, 8L,
-                        1024L, 8L, 12)
-                .addValues(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, ROAMING_YES, 1024L, 8L,
-                        1024L, 8L, 12);
+                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 1024L,
+                        8L, 0L, 0L, 10)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 0L, 0L,
+                        1024L, 8L, 11)
+                .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 1024L,
+                        8L, 1024L, 8L, 12)
+                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                        1024L, 8L, 0L, 0L, 10)
+                .addValues(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, 0L, 0L,
+                        1024L, 8L, 11)
+                .addValues(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO, 0L, 0L,
+                        1024L, 8L, 11)
+                .addValues(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 1024L,
+                        8L, 1024L, 8L, 12)
+                .addValues(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, 1024L,
+                        8L, 1024L, 8L, 12);
 
         // verify that we correctly find across regardless of hinting
         for (int hint = 0; hint < stats.size(); hint++) {
             assertEquals(0, stats.findIndexHinted(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE,
-                    ROAMING_NO, hint));
+                    METERED_NO, ROAMING_NO, hint));
             assertEquals(1, stats.findIndexHinted(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE,
-                    ROAMING_NO, hint));
+                    METERED_NO, ROAMING_NO, hint));
             assertEquals(2, stats.findIndexHinted(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE,
-                    ROAMING_NO, hint));
+                    METERED_NO, ROAMING_NO, hint));
             assertEquals(3, stats.findIndexHinted(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE,
-                    ROAMING_NO, hint));
+                    METERED_NO, ROAMING_NO, hint));
             assertEquals(4, stats.findIndexHinted(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D,
-                    ROAMING_NO, hint));
-            assertEquals(5, stats.findIndexHinted(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE,
-                    ROAMING_NO, hint));
+                    METERED_NO, ROAMING_NO, hint));
+            assertEquals(5, stats.findIndexHinted(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D,
+                    METERED_YES, ROAMING_NO, hint));
             assertEquals(6, stats.findIndexHinted(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE,
-                    ROAMING_YES, hint));
+                    METERED_NO, ROAMING_NO, hint));
+            assertEquals(7, stats.findIndexHinted(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE,
+                    METERED_YES, ROAMING_YES, hint));
             assertEquals(-1, stats.findIndexHinted(TEST_IFACE, 6, SET_DEFAULT, TAG_NONE,
-                    ROAMING_NO, hint));
+                    METERED_NO, ROAMING_NO, hint));
         }
     }
 
     public void testAddEntryGrow() throws Exception {
-        final NetworkStats stats = new NetworkStats(TEST_START, 3);
+        final NetworkStats stats = new NetworkStats(TEST_START, 4);
 
         assertEquals(0, stats.size());
-        assertEquals(3, stats.internalSize());
+        assertEquals(4, stats.internalSize());
 
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1L, 1L, 2L,
-                2L, 3);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_NO, 2L, 2L, 2L,
-                2L, 4);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_YES, 3L, 3L, 2L,
-                2L, 5);
-
-        assertEquals(3, stats.size());
-        assertEquals(3, stats.internalSize());
-
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_NO, 4L, 40L, 4L,
-                40L, 7);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_NO, 5L, 50L, 4L,
-                40L, 8);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_NO, 6L, 60L, 5L,
-                50L, 10);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_YES, 7L, 70L, 5L,
-                50L, 11);
-
-        assertEquals(7, stats.size());
-        assertTrue(stats.internalSize() >= 7);
-
-        assertValues(stats, 0, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1L, 1L,
+        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 1L, 1L,
                 2L, 2L, 3);
-        assertValues(stats, 1, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_NO, 2L, 2L,
+        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 2L, 2L,
                 2L, 2L, 4);
-        assertValues(stats, 2, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_YES, 3L, 3L,
-                2L, 2L, 5);
-        assertValues(stats, 3, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_NO, 4L,
+        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, 3L,
+                3L, 2L, 2L, 5);
+        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, 3L,
+                3L, 2L, 2L, 5);
+
+        assertEquals(4, stats.size());
+        assertEquals(4, stats.internalSize());
+
+        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 4L,
                 40L, 4L, 40L, 7);
-        assertValues(stats, 4, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_NO, 5L,
+        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 5L,
                 50L, 4L, 40L, 8);
-        assertValues(stats, 5, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_NO, 6L,
+        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 6L,
                 60L, 5L, 50L, 10);
-        assertValues(stats, 6, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_YES, 7L,
+        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, 7L,
                 70L, 5L, 50L, 11);
+        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, 7L,
+                70L, 5L, 50L, 11);
+
+        assertEquals(9, stats.size());
+        assertTrue(stats.internalSize() >= 9);
+
+        assertValues(stats, 0, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                1L, 1L, 2L, 2L, 3);
+        assertValues(stats, 1, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                2L, 2L, 2L, 2L, 4);
+        assertValues(stats, 2, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+                3L, 3L, 2L, 2L, 5);
+        assertValues(stats, 3, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES,
+                ROAMING_YES, 3L, 3L, 2L, 2L, 5);
+        assertValues(stats, 4, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                4L, 40L, 4L, 40L, 7);
+        assertValues(stats, 5, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                5L, 50L, 4L, 40L, 8);
+        assertValues(stats, 6, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                6L, 60L, 5L, 50L, 10);
+        assertValues(stats, 7, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+                7L, 70L, 5L, 50L, 11);
+        assertValues(stats, 8, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES,
+                ROAMING_YES, 7L, 70L, 5L, 50L, 11);
     }
 
     public void testCombineExisting() throws Exception {
@@ -152,20 +176,18 @@
         stats.combineValues(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, -128L, -1L,
                 -128L, -1L, -1);
 
-        assertValues(stats, 0, TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, ROAMING_NO, 384L, 3L,
-                128L, 1L, 9);
-        assertValues(stats, 1, TEST_IFACE, 1001, SET_DEFAULT, 0xff, ROAMING_NO, 128L, 1L, 128L,
-                1L, 2);
+        assertValues(stats, 0, TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                384L, 3L, 128L, 1L, 9);
+        assertValues(stats, 1, TEST_IFACE, 1001, SET_DEFAULT, 0xff, METERED_NO, ROAMING_NO, 128L,
+                1L, 128L, 1L, 2);
 
         // now try combining that should create row
-        stats.combineValues(TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, 128L, 1L,
-                128L, 1L, 3);
-        assertValues(stats, 2, TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, ROAMING_NO, 128L, 1L,
-                128L, 1L, 3);
-        stats.combineValues(TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, 128L, 1L,
-                128L, 1L, 3);
-        assertValues(stats, 2, TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, ROAMING_NO, 256L, 2L,
-                256L, 2L, 6);
+        stats.combineValues(TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 3);
+        assertValues(stats, 2, TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                128L, 1L, 128L, 1L, 3);
+        stats.combineValues(TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 3);
+        assertValues(stats, 2, TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                256L, 2L, 256L, 2L, 6);
     }
 
     public void testSubtractIdenticalData() throws Exception {
@@ -180,10 +202,10 @@
         final NetworkStats result = after.subtract(before);
 
         // identical data should result in zero delta
-        assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 0L, 0L, 0L,
-                0L, 0);
-        assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, ROAMING_NO, 0L, 0L, 0L,
-                0L, 0);
+        assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 0L,
+                0L, 0L, 0L, 0);
+        assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 0L,
+                0L, 0L, 0L, 0);
     }
 
     public void testSubtractIdenticalRows() throws Exception {
@@ -198,10 +220,10 @@
         final NetworkStats result = after.subtract(before);
 
         // expect delta between measurements
-        assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1L, 1L, 2L,
-                1L, 4);
-        assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, ROAMING_NO, 3L, 1L, 4L,
-                1L, 8);
+        assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 1L,
+                1L, 2L, 1L, 4);
+        assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 3L,
+                1L, 4L, 1L, 8);
     }
 
     public void testSubtractNewRows() throws Exception {
@@ -217,12 +239,12 @@
         final NetworkStats result = after.subtract(before);
 
         // its okay to have new rows
-        assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 0L, 0L, 0L,
-                0L, 0);
-        assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, ROAMING_NO, 0L, 0L, 0L,
-                0L, 0);
-        assertValues(result, 2, TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1024L, 8L,
-                1024L, 8L, 20);
+        assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 0L,
+                0L, 0L, 0L, 0);
+        assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 0L,
+                0L, 0L, 0L, 0);
+        assertValues(result, 2, TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                1024L, 8L, 1024L, 8L, 20);
     }
 
     public void testSubtractMissingRows() throws Exception {
@@ -237,8 +259,8 @@
 
         // should silently drop omitted rows
         assertEquals(1, result.size());
-        assertValues(result, 0, TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1L,
-                2L, 3L, 4L, 0);
+        assertValues(result, 0, TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO,
+                ROAMING_NO, 1L, 2L, 3L, 4L, 0);
         assertEquals(4L, result.getTotalBytes());
     }
 
@@ -263,13 +285,22 @@
                 .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L);
         assertEquals(64L, uidTag.getTotalBytes());
 
+        final NetworkStats uidMetered = new NetworkStats(TEST_START, 3)
+                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 32L, 0L,
+                        0L, 0L, 0L)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, 32L, 0L,
+                        0L, 0L, 0L)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, 32L, 0L,
+                        0L, 0L, 0L);
+        assertEquals(96L, uidMetered.getTotalBytes());
+
         final NetworkStats uidRoaming = new NetworkStats(TEST_START, 3)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 32L, 0L, 0L, 0L,
-                        0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, ROAMING_NO, 32L, 0L, 0L, 0L,
-                        0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, ROAMING_YES, 32L, 0L, 0L, 0L,
-                        0L);
+                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 32L, 0L,
+                        0L, 0L, 0L)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, 32L, 0L,
+                        0L, 0L, 0L)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, 32L, 0L,
+                        0L, 0L, 0L);
         assertEquals(96L, uidRoaming.getTotalBytes());
     }
 
@@ -283,95 +314,95 @@
 
     public void testGroupedByIfaceAll() throws Exception {
         final NetworkStats uidStats = new NetworkStats(TEST_START, 3)
-                .addValues(IFACE_ALL, 100, SET_ALL, TAG_NONE, ROAMING_NO, 128L, 8L, 0L, 2L,
-                        20L)
-                .addValues(IFACE_ALL, 101, SET_FOREGROUND, TAG_NONE, ROAMING_NO, 128L, 8L, 0L,
+                .addValues(IFACE_ALL, 100, SET_ALL, TAG_NONE, METERED_NO, ROAMING_NO, 128L, 8L, 0L,
                         2L, 20L)
-                .addValues(IFACE_ALL, 101, SET_ALL, TAG_NONE, ROAMING_YES, 128L, 8L, 0L, 2L,
-                        20L);
+                .addValues(IFACE_ALL, 101, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_NO, 128L,
+                        8L, 0L, 2L, 20L)
+                .addValues(IFACE_ALL, 101, SET_ALL, TAG_NONE, METERED_NO, ROAMING_YES, 128L, 8L, 0L,
+                        2L, 20L);
         final NetworkStats grouped = uidStats.groupedByIface();
 
         assertEquals(3, uidStats.size());
         assertEquals(1, grouped.size());
 
-        assertValues(grouped, 0, IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE, ROAMING_ALL, 384L, 24L, 0L,
-                6L, 0L);
+        assertValues(grouped, 0, IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                384L, 24L, 0L, 6L, 0L);
     }
 
     public void testGroupedByIface() throws Exception {
         final NetworkStats uidStats = new NetworkStats(TEST_START, 7)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 128L, 8L, 0L,
-                        2L, 20L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 512L, 32L, 0L,
-                        0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, ROAMING_NO, 64L, 4L, 0L, 0L,
-                        0L)
-                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, ROAMING_NO, 512L, 32L,
+                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 128L, 8L,
+                        0L, 2L, 20L)
+                .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 512L,
+                        32L, 0L, 0L, 0L)
+                .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, 64L, 4L,
                         0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, ROAMING_NO, 128L, 8L, 0L,
-                        0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, ROAMING_NO, 128L, 8L, 0L, 0L,
-                        0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, ROAMING_YES, 128L, 8L, 0L,
-                        0L, 0L);
+                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, 512L,
+                        32L, 0L, 0L, 0L)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 128L, 8L,
+                        0L, 0L, 0L)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO, 128L, 8L,
+                        0L, 0L, 0L)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, 128L,
+                        8L, 0L, 0L, 0L);
 
         final NetworkStats grouped = uidStats.groupedByIface();
 
         assertEquals(7, uidStats.size());
 
         assertEquals(2, grouped.size());
-        assertValues(grouped, 0, TEST_IFACE, UID_ALL, SET_ALL, TAG_NONE, ROAMING_ALL, 384L, 24L, 0L,
-                2L, 0L);
-        assertValues(grouped, 1, TEST_IFACE2, UID_ALL, SET_ALL, TAG_NONE, ROAMING_ALL, 1024L, 64L,
-                0L, 0L, 0L);
+        assertValues(grouped, 0, TEST_IFACE, UID_ALL, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                384L, 24L, 0L, 2L, 0L);
+        assertValues(grouped, 1, TEST_IFACE2, UID_ALL, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                1024L, 64L, 0L, 0L, 0L);
     }
 
     public void testAddAllValues() {
         final NetworkStats first = new NetworkStats(TEST_START, 5)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 32L, 0L, 0L, 0L,
-                        0L)
-                .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, ROAMING_NO, 32L, 0L, 0L,
-                        0L, 0L)
-                .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, ROAMING_YES, 32L, 0L, 0L,
-                        0L, 0L);
+                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, 32L, 0L,
+                        0L, 0L, 0L)
+                .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, 32L,
+                        0L, 0L, 0L, 0L)
+                .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES, 32L,
+                        0L, 0L, 0L, 0L);
 
         final NetworkStats second = new NetworkStats(TEST_START, 2)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 32L, 0L, 0L, 0L,
-                        0L)
-                .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, ROAMING_NO, 32L, 0L,
+                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, 32L, 0L,
                         0L, 0L, 0L)
-                .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, ROAMING_YES, 32L, 0L, 0L,
-                        0L, 0L);
+                .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 32L,
+                        0L, 0L, 0L, 0L)
+                .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES, 32L,
+                        0L, 0L, 0L, 0L);
 
         first.combineAllValues(second);
 
         assertEquals(4, first.size());
-        assertValues(first, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 64L, 0L, 0L,
-                0L, 0L);
-        assertValues(first, 1, TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, ROAMING_NO, 32L, 0L,
-                0L, 0L, 0L);
-        assertValues(first, 2, TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, ROAMING_YES, 64L, 0L,
-                0L, 0L, 0L);
-        assertValues(first, 3, TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, ROAMING_NO, 32L,
+        assertValues(first, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, 64L,
                 0L, 0L, 0L, 0L);
+        assertValues(first, 1, TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                32L, 0L, 0L, 0L, 0L);
+        assertValues(first, 2, TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES,
+                64L, 0L, 0L, 0L, 0L);
+        assertValues(first, 3, TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                32L, 0L, 0L, 0L, 0L);
     }
 
     public void testGetTotal() {
         final NetworkStats stats = new NetworkStats(TEST_START, 7)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 128L, 8L, 0L,
-                        2L, 20L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 512L, 32L, 0L,
-                        0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, ROAMING_NO, 64L, 4L, 0L, 0L,
-                        0L)
-                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, ROAMING_NO, 512L, 32L,
+                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 128L, 8L,
+                        0L, 2L, 20L)
+                .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 512L,
+                        32L, 0L, 0L, 0L)
+                .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, 64L, 4L,
                         0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, ROAMING_NO, 128L, 8L, 0L,
-                        0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, ROAMING_NO, 128L, 8L, 0L, 0L,
-                        0L)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, ROAMING_YES, 128L, 8L, 0L,
-                        0L, 0L);
+                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, 512L,
+                        32L, 0L, 0L, 0L)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, 128L,
+                        8L, 0L, 0L, 0L)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, 128L, 8L,
+                        0L, 0L, 0L)
+                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, 128L,
+                        8L, 0L, 0L, 0L);
 
         assertValues(stats.getTotal(null), 1408L, 88L, 0L, 2L, 20L);
         assertValues(stats.getTotal(null, 100), 1280L, 80L, 0L, 2L, 20L);
@@ -396,10 +427,10 @@
         final NetworkStats after = before.withoutUids(new int[] { 100 });
         assertEquals(6, before.size());
         assertEquals(2, after.size());
-        assertValues(after, 0, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, ROAMING_NO, 128L, 8L,
-                0L, 0L, 0L);
-        assertValues(after, 1, TEST_IFACE, 101, SET_DEFAULT, 0xF00D, ROAMING_NO, 128L, 8L, 0L,
-                0L, 0L);
+        assertValues(after, 0, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                128L, 8L, 0L, 0L, 0L);
+        assertValues(after, 1, TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, 128L,
+                8L, 0L, 0L, 0L);
     }
 
     public void testClone() throws Exception {
@@ -434,77 +465,90 @@
         final String underlyingIface = "wlan0";
         final int testTag1 = 8888;
         NetworkStats delta = new NetworkStats(TEST_START, 17)
-            .addValues(tunIface, 10100, SET_DEFAULT, TAG_NONE, 39605L, 46L, 12259L, 55L, 0L)
-            .addValues(tunIface, 10100, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L)
-            .addValues(tunIface, 10120, SET_DEFAULT, TAG_NONE, 72667L, 197L, 43909L, 241L, 0L)
-            .addValues(tunIface, 10120, SET_FOREGROUND, TAG_NONE, 9297L, 17L, 4128L, 21L, 0L)
+            .addValues(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 39605L, 46L,
+                    12259L, 55L, 0L)
+            .addValues(tunIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, 0L, 0L,
+                    0L, 0L, 0L)
+            .addValues(tunIface, 10120, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 72667L, 197L,
+                    43909L, 241L, 0L)
+            .addValues(tunIface, 10120, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, 9297L,
+                    17L, 4128L, 21L, 0L)
             // VPN package also uses some traffic through unprotected network.
-            .addValues(tunIface, tunUid, SET_DEFAULT, TAG_NONE, 4983L, 10L, 1801L, 12L, 0L)
-            .addValues(tunIface, tunUid, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L)
+            .addValues(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 4983L, 10L,
+                    1801L, 12L, 0L)
+            .addValues(tunIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, 0L, 0L,
+                    0L, 0L, 0L)
             // Tag entries
-            .addValues(tunIface, 10120, SET_DEFAULT, testTag1, 21691L, 41L, 13820L, 51L, 0L)
-            .addValues(tunIface, 10120, SET_FOREGROUND, testTag1, 1281L, 2L, 665L, 2L, 0L)
+            .addValues(tunIface, 10120, SET_DEFAULT, testTag1, METERED_NO, ROAMING_NO, 21691L, 41L,
+                    13820L, 51L, 0L)
+            .addValues(tunIface, 10120, SET_FOREGROUND, testTag1, METERED_NO, ROAMING_NO, 1281L, 2L,
+                    665L, 2L, 0L)
             // Irrelevant entries
-            .addValues(TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, 1685L, 5L, 2070L, 6L, 0L)
+            .addValues(TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 1685L, 5L,
+                    2070L, 6L, 0L)
             // Underlying Iface entries
-            .addValues(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, 5178L, 8L, 2139L, 11L, 0L)
-            .addValues(underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L)
-            .addValues(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, 149873L, 287L,
-                    59217L /* smaller than sum(tun0) */, 299L /* smaller than sum(tun0) */, 0L)
-            .addValues(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
+            .addValues(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 5178L,
+                    8L, 2139L, 11L, 0L)
+            .addValues(underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, 0L,
+                    0L, 0L, 0L, 0L)
+            .addValues(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                    149873L, 287L, 59217L /* smaller than sum(tun0) */,
+                    299L /* smaller than sum(tun0) */, 0L)
+            .addValues(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                    0L, 0L, 0L, 0L, 0L);
 
         assertTrue(delta.migrateTun(tunUid, tunIface, underlyingIface));
         assertEquals(20, delta.size());
 
         // tunIface and TEST_IFACE entries are not changed.
-        assertValues(delta, 0, tunIface, 10100, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+        assertValues(delta, 0, tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                 39605L, 46L, 12259L, 55L, 0L);
-        assertValues(delta, 1, tunIface, 10100, SET_FOREGROUND, TAG_NONE, ROAMING_NO, 0L, 0L,
-                0L, 0L, 0L);
-        assertValues(delta, 2, tunIface, 10120, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+        assertValues(delta, 1, tunIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                0L, 0L, 0L, 0L, 0L);
+        assertValues(delta, 2, tunIface, 10120, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                 72667L, 197L, 43909L, 241L, 0L);
-        assertValues(delta, 3, tunIface, 10120, SET_FOREGROUND, TAG_NONE, ROAMING_NO,
+        assertValues(delta, 3, tunIface, 10120, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
                 9297L, 17L, 4128L, 21L, 0L);
-        assertValues(delta, 4, tunIface, tunUid, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+        assertValues(delta, 4, tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                 4983L, 10L, 1801L, 12L, 0L);
-        assertValues(delta, 5, tunIface, tunUid, SET_FOREGROUND, TAG_NONE, ROAMING_NO, 0L, 0L,
-                0L, 0L, 0L);
-        assertValues(delta, 6, tunIface, 10120, SET_DEFAULT, testTag1, ROAMING_NO,
+        assertValues(delta, 5, tunIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                0L, 0L, 0L, 0L, 0L);
+        assertValues(delta, 6, tunIface, 10120, SET_DEFAULT, testTag1, METERED_NO, ROAMING_NO,
                 21691L, 41L, 13820L, 51L, 0L);
-        assertValues(delta, 7, tunIface, 10120, SET_FOREGROUND, testTag1, ROAMING_NO, 1281L,
-                2L, 665L, 2L, 0L);
-        assertValues(delta, 8, TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1685L, 5L,
-                2070L, 6L, 0L);
+        assertValues(delta, 7, tunIface, 10120, SET_FOREGROUND, testTag1, METERED_NO, ROAMING_NO,
+                1281L, 2L, 665L, 2L, 0L);
+        assertValues(delta, 8, TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                1685L, 5L, 2070L, 6L, 0L);
 
         // Existing underlying Iface entries are updated
-        assertValues(delta, 9, underlyingIface, 10100, SET_DEFAULT, TAG_NONE, ROAMING_NO,
-                44783L, 54L, 14178L, 62L, 0L);
-        assertValues(delta, 10, underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, ROAMING_NO,
-                0L, 0L, 0L, 0L, 0L);
+        assertValues(delta, 9, underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO,
+                ROAMING_NO, 44783L, 54L, 14178L, 62L, 0L);
+        assertValues(delta, 10, underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO,
+                ROAMING_NO, 0L, 0L, 0L, 0L, 0L);
 
         // VPN underlying Iface entries are updated
-        assertValues(delta, 11, underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, ROAMING_NO,
-                28304L, 27L, 1L, 2L, 0L);
-        assertValues(delta, 12, underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, ROAMING_NO,
-                0L, 0L, 0L, 0L, 0L);
+        assertValues(delta, 11, underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO,
+                ROAMING_NO, 28304L, 27L, 1L, 2L, 0L);
+        assertValues(delta, 12, underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO,
+                ROAMING_NO, 0L, 0L, 0L, 0L, 0L);
 
         // New entries are added for new application's underlying Iface traffic
-        assertContains(delta, underlyingIface, 10120, SET_DEFAULT, TAG_NONE, ROAMING_NO,
-                72667L, 197L, 43123L, 227L, 0L);
-        assertContains(delta, underlyingIface, 10120, SET_FOREGROUND, TAG_NONE, ROAMING_NO,
-                9297L, 17L, 4054, 19L, 0L);
-        assertContains(delta, underlyingIface, 10120, SET_DEFAULT, testTag1, ROAMING_NO,
-                21691L, 41L, 13572L, 48L, 0L);
-        assertContains(delta, underlyingIface, 10120, SET_FOREGROUND, testTag1, ROAMING_NO,
-                1281L, 2L, 653L, 1L, 0L);
+        assertContains(delta, underlyingIface, 10120, SET_DEFAULT, TAG_NONE, METERED_NO,
+                ROAMING_NO, 72667L, 197L, 43123L, 227L, 0L);
+        assertContains(delta, underlyingIface, 10120, SET_FOREGROUND, TAG_NONE, METERED_NO,
+                ROAMING_NO, 9297L, 17L, 4054, 19L, 0L);
+        assertContains(delta, underlyingIface, 10120, SET_DEFAULT, testTag1, METERED_NO,
+                ROAMING_NO, 21691L, 41L, 13572L, 48L, 0L);
+        assertContains(delta, underlyingIface, 10120, SET_FOREGROUND, testTag1, METERED_NO,
+                ROAMING_NO, 1281L, 2L, 653L, 1L, 0L);
 
         // New entries are added for debug purpose
-        assertContains(delta, underlyingIface, 10100, SET_DBG_VPN_IN, TAG_NONE, ROAMING_NO,
-                39605L, 46L, 12039, 51, 0);
-        assertContains(delta, underlyingIface, 10120, SET_DBG_VPN_IN, TAG_NONE, ROAMING_NO,
-                81964, 214, 47177, 246, 0);
-        assertContains(delta, underlyingIface, tunUid, SET_DBG_VPN_OUT, TAG_NONE, ROAMING_ALL,
-                121569, 260, 59216, 297, 0);
+        assertContains(delta, underlyingIface, 10100, SET_DBG_VPN_IN, TAG_NONE, METERED_NO,
+                ROAMING_NO, 39605L, 46L, 12039, 51, 0);
+        assertContains(delta, underlyingIface, 10120, SET_DBG_VPN_IN, TAG_NONE, METERED_NO,
+                ROAMING_NO, 81964, 214, 47177, 246, 0);
+        assertContains(delta, underlyingIface, tunUid, SET_DBG_VPN_OUT, TAG_NONE, METERED_ALL,
+                ROAMING_ALL, 121569, 260, 59216, 297, 0);
 
     }
 
@@ -518,72 +562,78 @@
         final String underlyingIface = "wlan0";
         NetworkStats delta = new NetworkStats(TEST_START, 9)
             // 2 different apps sent/receive data via tun0.
-            .addValues(tunIface, 10100, SET_DEFAULT, TAG_NONE, 50000L, 25L, 100000L, 50L, 0L)
-            .addValues(tunIface, 20100, SET_DEFAULT, TAG_NONE, 500L, 2L, 200L, 5L, 0L)
+            .addValues(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 50000L, 25L,
+                    100000L, 50L, 0L)
+            .addValues(tunIface, 20100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 500L, 2L,
+                    200L, 5L, 0L)
             // VPN package resends data through the tunnel (with exaggerated overhead)
-            .addValues(tunIface, tunUid, SET_DEFAULT, TAG_NONE, 240000, 100L, 120000L, 60L, 0L)
+            .addValues(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 240000,
+                    100L, 120000L, 60L, 0L)
             // 1 app already has some traffic on the underlying interface, the other doesn't yet
-            .addValues(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, 1000L, 10L, 2000L, 20L, 0L)
+            .addValues(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 1000L,
+                    10L, 2000L, 20L, 0L)
             // Traffic through the underlying interface via the vpn app.
             // This test should redistribute this data correctly.
-            .addValues(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE,
+            .addValues(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                     75500L, 37L, 130000L, 70L, 0L);
 
         assertTrue(delta.migrateTun(tunUid, tunIface, underlyingIface));
         assertEquals(9, delta.size());
 
         // tunIface entries should not be changed.
-        assertValues(delta, 0, tunIface, 10100, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+        assertValues(delta, 0, tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                 50000L, 25L, 100000L, 50L, 0L);
-        assertValues(delta, 1, tunIface, 20100, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+        assertValues(delta, 1, tunIface, 20100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                 500L, 2L, 200L, 5L, 0L);
-        assertValues(delta, 2, tunIface, tunUid, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+        assertValues(delta, 2, tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                 240000L, 100L, 120000L, 60L, 0L);
 
         // Existing underlying Iface entries are updated
-        assertValues(delta, 3, underlyingIface, 10100, SET_DEFAULT, TAG_NONE, ROAMING_NO,
-                51000L, 35L, 102000L, 70L, 0L);
+        assertValues(delta, 3, underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO,
+                ROAMING_NO, 51000L, 35L, 102000L, 70L, 0L);
 
         // VPN underlying Iface entries are updated
-        assertValues(delta, 4, underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, ROAMING_NO,
-                25000L, 10L, 29800L, 15L, 0L);
+        assertValues(delta, 4, underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO,
+                ROAMING_NO, 25000L, 10L, 29800L, 15L, 0L);
 
         // New entries are added for new application's underlying Iface traffic
-        assertContains(delta, underlyingIface, 20100, SET_DEFAULT, TAG_NONE, ROAMING_NO,
-                500L, 2L, 200L, 5L, 0L);
+        assertContains(delta, underlyingIface, 20100, SET_DEFAULT, TAG_NONE, METERED_NO,
+                ROAMING_NO, 500L, 2L, 200L, 5L, 0L);
 
         // New entries are added for debug purpose
-        assertContains(delta, underlyingIface, 10100, SET_DBG_VPN_IN, TAG_NONE, ROAMING_NO,
-                50000L, 25L, 100000L, 50L, 0L);
-        assertContains(delta, underlyingIface, 20100, SET_DBG_VPN_IN, TAG_NONE, ROAMING_NO,
-                500, 2L, 200L, 5L, 0L);
-        assertContains(delta, underlyingIface, tunUid, SET_DBG_VPN_OUT, TAG_NONE, ROAMING_ALL,
-                50500L, 27L, 100200L, 55, 0);
+        assertContains(delta, underlyingIface, 10100, SET_DBG_VPN_IN, TAG_NONE, METERED_NO,
+                ROAMING_NO, 50000L, 25L, 100000L, 50L, 0L);
+        assertContains(delta, underlyingIface, 20100, SET_DBG_VPN_IN, TAG_NONE, METERED_NO,
+                ROAMING_NO, 500, 2L, 200L, 5L, 0L);
+        assertContains(delta, underlyingIface, tunUid, SET_DBG_VPN_OUT, TAG_NONE, METERED_ALL,
+                ROAMING_ALL, 50500L, 27L, 100200L, 55, 0);
     }
 
     private static void assertContains(NetworkStats stats,  String iface, int uid, int set,
-            int tag, int roaming, long rxBytes, long rxPackets, long txBytes, long txPackets,
-            long operations) {
-        int index = stats.findIndex(iface, uid, set, tag, roaming);
+            int tag, int metered, int roaming, long rxBytes, long rxPackets, long txBytes,
+            long txPackets, long operations) {
+        int index = stats.findIndex(iface, uid, set, tag, metered, roaming);
         assertTrue(index != -1);
-        assertValues(stats, index, iface, uid, set, tag, roaming,
+        assertValues(stats, index, iface, uid, set, tag, metered, roaming,
                 rxBytes, rxPackets, txBytes, txPackets, operations);
     }
 
     private static void assertValues(NetworkStats stats, int index, String iface, int uid, int set,
-            int tag, int roaming, long rxBytes, long rxPackets, long txBytes, long txPackets,
-            long operations) {
+            int tag, int metered, int roaming, long rxBytes, long rxPackets, long txBytes,
+            long txPackets, long operations) {
         final NetworkStats.Entry entry = stats.getValues(index, null);
-        assertValues(entry, iface, uid, set, tag, roaming);
+        assertValues(entry, iface, uid, set, tag, metered, roaming);
         assertValues(entry, rxBytes, rxPackets, txBytes, txPackets, operations);
     }
 
     private static void assertValues(
-            NetworkStats.Entry entry, String iface, int uid, int set, int tag, int roaming) {
+            NetworkStats.Entry entry, String iface, int uid, int set, int tag, int metered,
+            int roaming) {
         assertEquals(iface, entry.iface);
         assertEquals(uid, entry.uid);
         assertEquals(set, entry.set);
         assertEquals(tag, entry.tag);
+        assertEquals(metered, entry.metered);
         assertEquals(roaming, entry.roaming);
     }
 
diff --git a/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java b/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java
new file mode 100644
index 0000000..a423c2a
--- /dev/null
+++ b/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2011 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.internal.net;
+
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.ROAMING_NO;
+import static android.net.NetworkStats.SET_ALL;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
+
+import android.content.res.Resources;
+import android.net.NetworkStats;
+import android.net.TrafficStats;
+import android.support.test.filters.SmallTest;
+import android.test.AndroidTestCase;
+
+import com.android.frameworks.tests.net.R;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+/**
+ * Tests for {@link NetworkStatsFactory}.
+ */
+@SmallTest
+public class NetworkStatsFactoryTest extends AndroidTestCase {
+    private File mTestProc;
+    private NetworkStatsFactory mFactory;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mTestProc = new File(getContext().getFilesDir(), "proc");
+        if (mTestProc.exists()) {
+            IoUtils.deleteContents(mTestProc);
+        }
+
+        mFactory = new NetworkStatsFactory(mTestProc);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mFactory = null;
+
+        if (mTestProc.exists()) {
+            IoUtils.deleteContents(mTestProc);
+        }
+
+        super.tearDown();
+    }
+
+    public void testNetworkStatsDetail() throws Exception {
+        final NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_typical);
+
+        assertEquals(70, stats.size());
+        assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 18621L, 2898L);
+        assertStatsEntry(stats, "wlan0", 10011, SET_DEFAULT, 0x0, 35777L, 5718L);
+        assertStatsEntry(stats, "wlan0", 10021, SET_DEFAULT, 0x7fffff01, 562386L, 49228L);
+        assertStatsEntry(stats, "rmnet1", 10021, SET_DEFAULT, 0x30100000, 219110L, 227423L);
+        assertStatsEntry(stats, "rmnet2", 10001, SET_DEFAULT, 0x0, 1125899906842624L, 984L);
+    }
+
+    public void testKernelTags() throws Exception {
+        assertEquals(0, kernelToTag("0x0000000000000000"));
+        assertEquals(0x32, kernelToTag("0x0000003200000000"));
+        assertEquals(2147483647, kernelToTag("0x7fffffff00000000"));
+        assertEquals(0, kernelToTag("0x0000000000000000"));
+        assertEquals(2147483136, kernelToTag("0x7FFFFE0000000000"));
+
+        assertEquals(0, kernelToTag("0x0"));
+        assertEquals(0, kernelToTag("0xf00d"));
+        assertEquals(1, kernelToTag("0x100000000"));
+        assertEquals(14438007, kernelToTag("0xdc4e7700000000"));
+        assertEquals(TrafficStats.TAG_SYSTEM_DOWNLOAD, kernelToTag("0xffffff0100000000"));
+    }
+
+    public void testNetworkStatsWithSet() throws Exception {
+        final NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_typical);
+        assertEquals(70, stats.size());
+        assertStatsEntry(stats, "rmnet1", 10021, SET_DEFAULT, 0x30100000, 219110L, 578L, 227423L,
+                676L);
+        assertStatsEntry(stats, "rmnet1", 10021, SET_FOREGROUND, 0x30100000, 742L, 3L, 1265L, 3L);
+    }
+
+    public void testNetworkStatsSingle() throws Exception {
+        stageFile(R.raw.xt_qtaguid_iface_typical, file("net/xt_qtaguid/iface_stat_all"));
+
+        final NetworkStats stats = mFactory.readNetworkStatsSummaryDev();
+        assertEquals(6, stats.size());
+        assertStatsEntry(stats, "rmnet0", UID_ALL, SET_ALL, TAG_NONE, 2112L, 24L, 700L, 10L);
+        assertStatsEntry(stats, "test1", UID_ALL, SET_ALL, TAG_NONE, 6L, 8L, 10L, 12L);
+        assertStatsEntry(stats, "test2", UID_ALL, SET_ALL, TAG_NONE, 1L, 2L, 3L, 4L);
+    }
+
+    public void testNetworkStatsXt() throws Exception {
+        stageFile(R.raw.xt_qtaguid_iface_fmt_typical, file("net/xt_qtaguid/iface_stat_fmt"));
+
+        final NetworkStats stats = mFactory.readNetworkStatsSummaryXt();
+        assertEquals(3, stats.size());
+        assertStatsEntry(stats, "rmnet0", UID_ALL, SET_ALL, TAG_NONE, 6824L, 16L, 5692L, 10L);
+        assertStatsEntry(stats, "rmnet1", UID_ALL, SET_ALL, TAG_NONE, 11153922L, 8051L, 190226L,
+                2468L);
+        assertStatsEntry(stats, "rmnet2", UID_ALL, SET_ALL, TAG_NONE, 4968L, 35L, 3081L, 39L);
+    }
+
+    public void testDoubleClatAccounting() throws Exception {
+        NetworkStatsFactory.noteStackedIface("v4-wlan0", "wlan0");
+
+        // xt_qtaguid_with_clat_simple is a synthetic file that simulates
+        //  - 213 received 464xlat packets of size 200 bytes
+        //  - 41 sent 464xlat packets of size 100 bytes
+        //  - no other traffic on base interface for root uid.
+        NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat_simple);
+        assertEquals(4, stats.size());
+
+        assertStatsEntry(stats, "v4-wlan0", 10060, SET_DEFAULT, 0x0, 46860L, 4920L);
+        assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 0L, 0L);
+
+        stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat);
+        assertEquals(42, stats.size());
+
+        assertStatsEntry(stats, "v4-wlan0", 0, SET_DEFAULT, 0x0, 356L, 276L);
+        assertStatsEntry(stats, "v4-wlan0", 1000, SET_DEFAULT, 0x0, 30812L, 2310L);
+        assertStatsEntry(stats, "v4-wlan0", 10102, SET_DEFAULT, 0x0, 10022L, 3330L);
+        assertStatsEntry(stats, "v4-wlan0", 10060, SET_DEFAULT, 0x0, 9532772L, 254112L);
+        assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 15229L, 5766L);
+        assertStatsEntry(stats, "wlan0", 1000, SET_DEFAULT, 0x0, 6126L, 2013L);
+        assertStatsEntry(stats, "wlan0", 10013, SET_DEFAULT, 0x0, 0L, 144L);
+        assertStatsEntry(stats, "wlan0", 10018, SET_DEFAULT, 0x0, 5980263L, 167667L);
+        assertStatsEntry(stats, "wlan0", 10060, SET_DEFAULT, 0x0, 134356L, 8705L);
+        assertStatsEntry(stats, "wlan0", 10079, SET_DEFAULT, 0x0, 10926L, 1507L);
+        assertStatsEntry(stats, "wlan0", 10102, SET_DEFAULT, 0x0, 25038L, 8245L);
+        assertStatsEntry(stats, "wlan0", 10103, SET_DEFAULT, 0x0, 0L, 192L);
+        assertStatsEntry(stats, "dummy0", 0, SET_DEFAULT, 0x0, 0L, 168L);
+        assertStatsEntry(stats, "lo", 0, SET_DEFAULT, 0x0, 1288L, 1288L);
+
+        NetworkStatsFactory.noteStackedIface("v4-wlan0", null);
+    }
+
+    public void testDoubleClatAccounting100MBDownload() throws Exception {
+        // Downloading 100mb from an ipv4 only destination in a foreground activity
+
+        long appRxBytesBefore = 328684029L;
+        long appRxBytesAfter = 439237478L;
+        assertEquals("App traffic should be ~100MB", 110553449, appRxBytesAfter - appRxBytesBefore);
+
+        long rootRxBytesBefore = 1394011L;
+        long rootRxBytesAfter = 1398634L;
+        assertEquals("UID 0 traffic should be ~0", 4623, rootRxBytesAfter - rootRxBytesBefore);
+
+        NetworkStatsFactory.noteStackedIface("v4-wlan0", "wlan0");
+        NetworkStats stats;
+
+        // Stats snapshot before the download
+        stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat_100mb_download_before);
+        assertStatsEntry(stats, "v4-wlan0", 10106, SET_FOREGROUND, 0x0, appRxBytesBefore, 5199872L);
+        assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, rootRxBytesBefore, 647888L);
+
+        // Stats snapshot after the download
+        stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat_100mb_download_after);
+        assertStatsEntry(stats, "v4-wlan0", 10106, SET_FOREGROUND, 0x0, appRxBytesAfter, 7867488L);
+        assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, rootRxBytesAfter, 647587L);
+
+        NetworkStatsFactory.noteStackedIface("v4-wlan0", null);
+    }
+
+    /**
+     * Copy a {@link Resources#openRawResource(int)} into {@link File} for
+     * testing purposes.
+     */
+    private void stageFile(int rawId, File file) throws Exception {
+        new File(file.getParent()).mkdirs();
+        InputStream in = null;
+        OutputStream out = null;
+        try {
+            in = getContext().getResources().openRawResource(rawId);
+            out = new FileOutputStream(file);
+            Streams.copy(in, out);
+        } finally {
+            IoUtils.closeQuietly(in);
+            IoUtils.closeQuietly(out);
+        }
+    }
+
+    private void stageLong(long value, File file) throws Exception {
+        new File(file.getParent()).mkdirs();
+        FileWriter out = null;
+        try {
+            out = new FileWriter(file);
+            out.write(Long.toString(value));
+        } finally {
+            IoUtils.closeQuietly(out);
+        }
+    }
+
+    private File file(String path) throws Exception {
+        return new File(mTestProc, path);
+    }
+
+    private NetworkStats parseDetailedStats(int resourceId) throws Exception {
+        stageFile(resourceId, file("net/xt_qtaguid/stats"));
+        return mFactory.readNetworkStatsDetail();
+    }
+
+    private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set,
+            int tag, long rxBytes, long txBytes) {
+        final int i = stats.findIndex(iface, uid, set, tag, METERED_NO, ROAMING_NO);
+        if (i < 0) {
+            fail(String.format("no NetworkStats for (iface: %s, uid: %d, set: %d, tag: %d)",
+                    iface, uid, set, tag));
+        }
+        final NetworkStats.Entry entry = stats.getValues(i, null);
+        assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+        assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+    }
+
+    private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set,
+            int tag, long rxBytes, long rxPackets, long txBytes, long txPackets) {
+        final int i = stats.findIndex(iface, uid, set, tag, METERED_NO, ROAMING_NO);
+        if (i < 0) {
+            fail(String.format("no NetworkStats for (iface: %s, uid: %d, set: %d, tag: %d)",
+                    iface, uid, set, tag));
+        }
+        final NetworkStats.Entry entry = stats.getValues(i, null);
+        assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+        assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
+        assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+        assertEquals("unexpected txPackets", txPackets, entry.txPackets);
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
similarity index 75%
rename from services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
rename to tests/net/java/com/android/server/ConnectivityServiceTest.java
index 65f9399..6e5ac8a 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -23,7 +23,12 @@
 import static android.net.ConnectivityManager.getNetworkTypeName;
 import static android.net.NetworkCapabilities.*;
 
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
 
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -33,6 +38,8 @@
 import android.content.ContextWrapper;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.net.CaptivePortal;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.ConnectivityManager.PacketKeepalive;
@@ -42,6 +49,7 @@
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.MatchAllNetworkSpecifier;
 import android.net.Network;
 import android.net.NetworkAgent;
 import android.net.NetworkCapabilities;
@@ -51,9 +59,11 @@
 import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkMisc;
 import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
 import android.net.RouteInfo;
+import android.net.StringNetworkSpecifier;
 import android.net.metrics.IpConnectivityLog;
-import android.net.util.AvoidBadWifiTracker;
+import android.net.util.MultinetworkPolicyTracker;
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -64,22 +74,32 @@
 import android.os.MessageQueue;
 import android.os.Messenger;
 import android.os.MessageQueue.IdleHandler;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.Process;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.test.AndroidTestCase;
 import android.test.mock.MockContentResolver;
-import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.LogPrinter;
 
-import com.android.internal.util.FakeSettingsProvider;
 import com.android.internal.util.WakeupMessage;
+import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.connectivity.MockableSystemProperties;
 import com.android.server.connectivity.NetworkAgentInfo;
 import com.android.server.connectivity.NetworkMonitor;
 import com.android.server.connectivity.NetworkMonitor.CaptivePortalProbeResult;
 import com.android.server.net.NetworkPinner;
+import com.android.server.net.NetworkPolicyManagerInternal;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
 
 import java.net.InetAddress;
 import java.util.ArrayList;
@@ -89,6 +109,7 @@
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BooleanSupplier;
 
 /**
  * Tests for {@link ConnectivityService}.
@@ -102,7 +123,7 @@
     private static final int TIMEOUT_MS = 500;
     private static final int TEST_LINGER_DELAY_MS = 120;
 
-    private BroadcastInterceptingContext mServiceContext;
+    private MockContext mServiceContext;
     private WrappedConnectivityService mService;
     private WrappedConnectivityManager mCm;
     private MockNetworkAgent mWiFiNetworkAgent;
@@ -132,13 +153,46 @@
     private class MockContext extends BroadcastInterceptingContext {
         private final MockContentResolver mContentResolver;
 
+        @Spy private Resources mResources;
+        private final LinkedBlockingQueue<Intent> mStartedActivities = new LinkedBlockingQueue<>();
+
         MockContext(Context base) {
             super(base);
+
+            mResources = spy(base.getResources());
+            when(mResources.getStringArray(com.android.internal.R.array.networkAttributes)).
+                    thenReturn(new String[] {
+                            "wifi,1,1,1,-1,true",
+                            "mobile,0,0,0,-1,true",
+                            "mobile_mms,2,0,2,60000,true",
+                    });
+
             mContentResolver = new MockContentResolver();
             mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
         }
 
         @Override
+        public void startActivityAsUser(Intent intent, UserHandle handle) {
+            mStartedActivities.offer(intent);
+        }
+
+        public Intent expectStartActivityIntent(int timeoutMs) {
+            Intent intent = null;
+            try {
+                intent = mStartedActivities.poll(timeoutMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {}
+            assertNotNull("Did not receive sign-in intent after " + timeoutMs + "ms", intent);
+            return intent;
+        }
+
+        public void expectNoStartActivityIntent(int timeoutMs) {
+            try {
+                assertNull("Received unexpected Intent to start activity",
+                        mStartedActivities.poll(timeoutMs, TimeUnit.MILLISECONDS));
+            } catch (InterruptedException e) {}
+        }
+
+        @Override
         public Object getSystemService(String name) {
             if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm;
             if (Context.NOTIFICATION_SERVICE.equals(name)) return mock(NotificationManager.class);
@@ -149,51 +203,28 @@
         public ContentResolver getContentResolver() {
             return mContentResolver;
         }
+
+        @Override
+        public Resources getResources() {
+            return mResources;
+        }
     }
 
     /**
-     * A subclass of HandlerThread that allows callers to wait for it to become idle. waitForIdle
-     * will return immediately if the handler is already idle.
+     * Block until the given handler becomes idle, or until timeoutMs has passed.
      */
-    private class IdleableHandlerThread extends HandlerThread {
-        private IdleHandler mIdleHandler;
-
-        public IdleableHandlerThread(String name) {
-            super(name);
-        }
-
-        public void waitForIdle(int timeoutMs) {
-            final ConditionVariable cv = new ConditionVariable();
-            final MessageQueue queue = getLooper().getQueue();
-
-            synchronized (queue) {
-                if (queue.isIdle()) {
-                    return;
-                }
-
-                assertNull("BUG: only one idle handler allowed", mIdleHandler);
-                mIdleHandler = new IdleHandler() {
-                    public boolean queueIdle() {
-                        synchronized (queue) {
-                            cv.open();
-                            mIdleHandler = null;
-                            return false;  // Remove the handler.
-                        }
-                    }
-                };
-                queue.addIdleHandler(mIdleHandler);
-            }
-
-            if (!cv.block(timeoutMs)) {
-                fail("HandlerThread " + getName() +
-                        " did not become idle after " + timeoutMs + " ms");
-                queue.removeIdleHandler(mIdleHandler);
-            }
+    private static void waitForIdleHandler(HandlerThread handlerThread, int timeoutMs) {
+        final ConditionVariable cv = new ConditionVariable();
+        final Handler handler = new Handler(handlerThread.getLooper());
+        handler.post(() -> cv.open());
+        if (!cv.block(timeoutMs)) {
+            fail("HandlerThread " + handlerThread.getName() +
+                    " did not become idle after " + timeoutMs + " ms");
         }
     }
 
-    // Tests that IdleableHandlerThread works as expected.
-    public void testIdleableHandlerThread() {
+    @SmallTest
+    public void testWaitForIdle() {
         final int attempts = 50;  // Causes the test to take about 200ms on bullhead-eng.
 
         // Tests that waitForIdle returns immediately if the service is already idle.
@@ -215,8 +246,21 @@
             mService.waitForIdle();
             assertEquals(i, mCm.getNetworkCapabilities(n).getSignalStrength());
         }
+    }
+
+    // This test has an inherent race condition in it, and cannot be enabled for continuous testing
+    // or presubmit tests. It is kept for manual runs and documentation purposes.
+    public void verifyThatNotWaitingForIdleCausesRaceConditions() {
+        // Bring up a network that we can use to send messages to ConnectivityService.
+        ConditionVariable cv = waitForConnectivityBroadcasts(1);
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(false);
+        waitFor(cv);
+        Network n = mWiFiNetworkAgent.getNetwork();
+        assertNotNull(n);
 
         // Ensure that not calling waitForIdle causes a race condition.
+        final int attempts = 50;  // Causes the test to take about 200ms on bullhead-eng.
         for (int i = 0; i < attempts; i++) {
             mWiFiNetworkAgent.setSignalStrength(i);
             if (i != mCm.getNetworkCapabilities(n).getSignalStrength()) {
@@ -233,7 +277,7 @@
         private final WrappedNetworkMonitor mWrappedNetworkMonitor;
         private final NetworkInfo mNetworkInfo;
         private final NetworkCapabilities mNetworkCapabilities;
-        private final IdleableHandlerThread mHandlerThread;
+        private final HandlerThread mHandlerThread;
         private final ConditionVariable mDisconnected = new ConditionVariable();
         private final ConditionVariable mNetworkStatusReceived = new ConditionVariable();
         private final ConditionVariable mPreventReconnectReceived = new ConditionVariable();
@@ -265,7 +309,7 @@
                 default:
                     throw new UnsupportedOperationException("unimplemented network type");
             }
-            mHandlerThread = new IdleableHandlerThread("Mock-" + typeName);
+            mHandlerThread = new HandlerThread("Mock-" + typeName);
             mHandlerThread.start();
             mNetworkAgent = new NetworkAgent(mHandlerThread.getLooper(), mServiceContext,
                     "Mock-" + typeName, mNetworkInfo, mNetworkCapabilities,
@@ -305,7 +349,7 @@
         }
 
         public void waitForIdle(int timeoutMs) {
-            mHandlerThread.waitForIdle(timeoutMs);
+            waitForIdleHandler(mHandlerThread, timeoutMs);
         }
 
         public void waitForIdle() {
@@ -332,6 +376,11 @@
             mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
         }
 
+        public void setNetworkSpecifier(NetworkSpecifier networkSpecifier) {
+            mNetworkCapabilities.setNetworkSpecifier(networkSpecifier);
+            mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+        }
+
         public void connectWithoutInternet() {
             mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null);
             mNetworkAgent.sendNetworkInfo(mNetworkInfo);
@@ -383,6 +432,11 @@
             connect(false);
         }
 
+        public void suspend() {
+            mNetworkInfo.setDetailedState(DetailedState.SUSPENDED, null, null);
+            mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+        }
+
         public void disconnect() {
             mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null);
             mNetworkAgent.sendNetworkInfo(mNetworkInfo);
@@ -607,10 +661,11 @@
         }
     }
 
-    private class WrappedAvoidBadWifiTracker extends AvoidBadWifiTracker {
-        public boolean configRestrictsAvoidBadWifi;
+    private class WrappedMultinetworkPolicyTracker extends MultinetworkPolicyTracker {
+        public volatile boolean configRestrictsAvoidBadWifi;
+        public volatile int configMeteredMultipathPreference;
 
-        public WrappedAvoidBadWifiTracker(Context c, Handler h, Runnable r) {
+        public WrappedMultinetworkPolicyTracker(Context c, Handler h, Runnable r) {
             super(c, h, r);
         }
 
@@ -618,11 +673,17 @@
         public boolean configRestrictsAvoidBadWifi() {
             return configRestrictsAvoidBadWifi;
         }
+
+        @Override
+        public int configMeteredMultipathPreference() {
+            return configMeteredMultipathPreference;
+        }
     }
 
     private class WrappedConnectivityService extends ConnectivityService {
-        public WrappedAvoidBadWifiTracker wrappedAvoidBadWifiTracker;
+        public WrappedMultinetworkPolicyTracker wrappedMultinetworkPolicyTracker;
         private WrappedNetworkMonitor mLastCreatedNetworkMonitor;
+        private MockableSystemProperties mSystemProperties;
 
         public WrappedConnectivityService(Context context, INetworkManagementService netManager,
                 INetworkStatsService statsService, INetworkPolicyManager policyManager,
@@ -632,14 +693,13 @@
         }
 
         @Override
-        protected HandlerThread createHandlerThread() {
-            return new IdleableHandlerThread("WrappedConnectivityService");
-        }
-
-        @Override
-        protected int getDefaultTcpRwnd() {
-            // Prevent wrapped ConnectivityService from trying to write to SystemProperties.
-            return 0;
+        protected MockableSystemProperties getSystemProperties() {
+            // Minimal approach to overriding system properties: let most calls fall through to real
+            // device values, and only override ones values that are important to this test.
+            mSystemProperties = spy(new MockableSystemProperties());
+            when(mSystemProperties.getInt("net.tcp.default_init_rwnd", 0)).thenReturn(0);
+            when(mSystemProperties.getBoolean("ro.radio.noril", false)).thenReturn(false);
+            return mSystemProperties;
         }
 
         @Override
@@ -673,14 +733,14 @@
         }
 
         @Override
-        public AvoidBadWifiTracker createAvoidBadWifiTracker(
+        public MultinetworkPolicyTracker createMultinetworkPolicyTracker(
                 Context c, Handler h, Runnable r) {
-            final WrappedAvoidBadWifiTracker tracker = new WrappedAvoidBadWifiTracker(c, h, r);
+            final WrappedMultinetworkPolicyTracker tracker = new WrappedMultinetworkPolicyTracker(c, h, r);
             return tracker;
         }
 
-        public WrappedAvoidBadWifiTracker getAvoidBadWifiTracker() {
-            return (WrappedAvoidBadWifiTracker) mAvoidBadWifiTracker;
+        public WrappedMultinetworkPolicyTracker getMultinetworkPolicyTracker() {
+            return (WrappedMultinetworkPolicyTracker) mMultinetworkPolicyTracker;
         }
 
         @Override
@@ -694,7 +754,7 @@
         }
 
         public void waitForIdle(int timeoutMs) {
-            ((IdleableHandlerThread) mHandlerThread).waitForIdle(timeoutMs);
+            waitForIdleHandler(mHandlerThread, timeoutMs);
         }
 
         public void waitForIdle() {
@@ -702,25 +762,6 @@
         }
     }
 
-    private interface Criteria {
-        public boolean get();
-    }
-
-    /**
-     * Wait up to 500ms for {@code criteria.get()} to become true, polling.
-     * Fails if 500ms goes by before {@code criteria.get()} to become true.
-     */
-    static private void waitFor(Criteria criteria) {
-        int delays = 0;
-        while (!criteria.get()) {
-            try {
-                Thread.sleep(50);
-            } catch (InterruptedException e) {
-            }
-            if (++delays == 10) fail();
-        }
-    }
-
     /**
      * Wait up to TIMEOUT_MS for {@code conditionVariable} to open.
      * Fails if TIMEOUT_MS goes by before {@code conditionVariable} opens.
@@ -740,6 +781,9 @@
         }
 
         mServiceContext = new MockContext(getContext());
+        LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
+        LocalServices.addService(
+                NetworkPolicyManagerInternal.class, mock(NetworkPolicyManagerInternal.class));
         mService = new WrappedConnectivityService(mServiceContext,
                 mock(INetworkManagementService.class),
                 mock(INetworkStatsService.class),
@@ -828,7 +872,15 @@
         return cv;
     }
 
-    @LargeTest
+    public void testNetworkTypes() {
+        // Ensure that our mocks for the networkAttributes config variable work as expected. If they
+        // don't, then tests that depend on CONNECTIVITY_ACTION broadcasts for these network types
+        // will fail. Failing here is much easier to debug.
+        assertTrue(mCm.isNetworkSupported(TYPE_WIFI));
+        assertTrue(mCm.isNetworkSupported(TYPE_MOBILE));
+    }
+
+    @SmallTest
     public void testLingering() throws Exception {
         verifyNoNetwork();
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -856,8 +908,9 @@
         assertTrue(mCm.getAllNetworks()[0].equals(mCellNetworkAgent.getNetwork()) ||
                 mCm.getAllNetworks()[1].equals(mCellNetworkAgent.getNetwork()));
         // Test cellular linger timeout.
-        waitFor(new Criteria() {
-                public boolean get() { return mCm.getAllNetworks().length == 1; } });
+        waitFor(mCellNetworkAgent.getDisconnectedCV());
+        mService.waitForIdle();
+        assertEquals(1, mCm.getAllNetworks().length);
         verifyActiveNetwork(TRANSPORT_WIFI);
         assertEquals(1, mCm.getAllNetworks().length);
         assertEquals(mCm.getAllNetworks()[0], mCm.getActiveNetwork());
@@ -868,7 +921,7 @@
         verifyNoNetwork();
     }
 
-    @LargeTest
+    @SmallTest
     public void testValidatedCellularOutscoresUnvalidatedWiFi() throws Exception {
         // Test bringing up unvalidated WiFi
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
@@ -903,7 +956,7 @@
         verifyNoNetwork();
     }
 
-    @LargeTest
+    @SmallTest
     public void testUnvalidatedWifiOutscoresUnvalidatedCellular() throws Exception {
         // Test bringing up unvalidated cellular.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -929,7 +982,7 @@
         verifyNoNetwork();
     }
 
-    @LargeTest
+    @SmallTest
     public void testUnlingeringDoesNotValidate() throws Exception {
         // Test bringing up unvalidated WiFi.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
@@ -957,7 +1010,7 @@
                 NET_CAPABILITY_VALIDATED));
     }
 
-    @LargeTest
+    @SmallTest
     public void testCellularOutscoresWeakWifi() throws Exception {
         // Test bringing up validated cellular.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -983,7 +1036,7 @@
         verifyActiveNetwork(TRANSPORT_WIFI);
     }
 
-    @LargeTest
+    @SmallTest
     public void testReapingNetwork() throws Exception {
         // Test bringing up WiFi without NET_CAPABILITY_INTERNET.
         // Expect it to be torn down immediately because it satisfies no requests.
@@ -1016,7 +1069,7 @@
         waitFor(cv);
     }
 
-    @LargeTest
+    @SmallTest
     public void testCellularFallback() throws Exception {
         // Test bringing up validated cellular.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -1054,7 +1107,7 @@
         verifyActiveNetwork(TRANSPORT_WIFI);
     }
 
-    @LargeTest
+    @SmallTest
     public void testWiFiFallback() throws Exception {
         // Test bringing up unvalidated WiFi.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
@@ -1085,8 +1138,33 @@
         AVAILABLE,
         NETWORK_CAPABILITIES,
         LINK_PROPERTIES,
+        SUSPENDED,
         LOSING,
-        LOST
+        LOST,
+        UNAVAILABLE
+    }
+
+    private static class CallbackInfo {
+        public final CallbackState state;
+        public final Network network;
+        public final Object arg;
+        public CallbackInfo(CallbackState s, Network n, Object o) {
+            state = s; network = n; arg = o;
+        }
+        public String toString() {
+            return String.format("%s (%s) (%s)", state, network, arg);
+        }
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof CallbackInfo)) return false;
+            // Ignore timeMs, since it's unpredictable.
+            CallbackInfo other = (CallbackInfo) o;
+            return (state == other.state) && Objects.equals(network, other.network);
+        }
+        @Override
+        public int hashCode() {
+            return Objects.hash(state, network);
+        }
     }
 
     /**
@@ -1098,23 +1176,8 @@
         // Chosen to be much less than the linger timeout. This ensures that we can distinguish
         // between a LOST callback that arrives immediately and a LOST callback that arrives after
         // the linger timeout.
-        private final static int TIMEOUT_MS = 50;
+        private final static int TIMEOUT_MS = 100;
 
-        private class CallbackInfo {
-            public final CallbackState state;
-            public final Network network;
-            public Object arg;
-            public CallbackInfo(CallbackState s, Network n, Object o) {
-                state = s; network = n; arg = o;
-            }
-            public String toString() { return String.format("%s (%s)", state, network); }
-            public boolean equals(Object o) {
-                if (!(o instanceof CallbackInfo)) return false;
-                // Ignore timeMs, since it's unpredictable.
-                CallbackInfo other = (CallbackInfo) o;
-                return state == other.state && Objects.equals(network, other.network);
-            }
-        }
         private final LinkedBlockingQueue<CallbackInfo> mCallbacks = new LinkedBlockingQueue<>();
 
         protected void setLastCallback(CallbackState state, Network network, Object o) {
@@ -1127,6 +1190,26 @@
         }
 
         @Override
+        public void onCapabilitiesChanged(Network network, NetworkCapabilities netCap) {
+            setLastCallback(CallbackState.NETWORK_CAPABILITIES, network, netCap);
+        }
+
+        @Override
+        public void onLinkPropertiesChanged(Network network, LinkProperties linkProp) {
+            setLastCallback(CallbackState.LINK_PROPERTIES, network, linkProp);
+        }
+
+        @Override
+        public void onUnavailable() {
+            setLastCallback(CallbackState.UNAVAILABLE, null, null);
+        }
+
+        @Override
+        public void onNetworkSuspended(Network network) {
+            setLastCallback(CallbackState.SUSPENDED, network, null);
+        }
+
+        @Override
         public void onLosing(Network network, int maxMsToLive) {
             setLastCallback(CallbackState.LOSING, network, maxMsToLive /* autoboxed int */);
         }
@@ -1136,17 +1219,25 @@
             setLastCallback(CallbackState.LOST, network, null);
         }
 
-        void expectCallback(CallbackState state, MockNetworkAgent mockAgent, int timeoutMs) {
-            CallbackInfo expected = new CallbackInfo(
-                    state, (mockAgent != null) ? mockAgent.getNetwork() : null, 0);
-            CallbackInfo actual;
+        CallbackInfo nextCallback(int timeoutMs) {
+            CallbackInfo cb = null;
             try {
-                actual = mCallbacks.poll(timeoutMs, TimeUnit.MILLISECONDS);
-                assertEquals("Unexpected callback:", expected, actual);
+                cb = mCallbacks.poll(timeoutMs, TimeUnit.MILLISECONDS);
             } catch (InterruptedException e) {
-                fail("Did not receive expected " + expected + " after " + TIMEOUT_MS + "ms");
-                actual = null;  // Or the compiler can't tell it's never used uninitialized.
             }
+            if (cb == null) {
+                // LinkedBlockingQueue.poll() returns null if it timeouts.
+                fail("Did not receive callback after " + timeoutMs + "ms");
+            }
+            return cb;
+        }
+
+        CallbackInfo expectCallback(CallbackState state, MockNetworkAgent agent, int timeoutMs) {
+            final Network expectedNetwork = (agent != null) ? agent.getNetwork() : null;
+            CallbackInfo expected = new CallbackInfo(state, expectedNetwork, 0);
+            CallbackInfo actual = nextCallback(timeoutMs);
+            assertEquals("Unexpected callback:", expected, actual);
+
             if (state == CallbackState.LOSING) {
                 String msg = String.format(
                         "Invalid linger time value %d, must be between %d and %d",
@@ -1154,10 +1245,46 @@
                 int maxMsToLive = (Integer) actual.arg;
                 assertTrue(msg, 0 <= maxMsToLive && maxMsToLive <= TEST_LINGER_DELAY_MS);
             }
+
+            return actual;
         }
 
-        void expectCallback(CallbackState state, MockNetworkAgent mockAgent) {
-            expectCallback(state, mockAgent, TIMEOUT_MS);
+        CallbackInfo expectCallback(CallbackState state, MockNetworkAgent agent) {
+            return expectCallback(state, agent, TIMEOUT_MS);
+        }
+
+        void expectAvailableCallbacks(MockNetworkAgent agent, boolean expectSuspended, int timeoutMs) {
+            expectCallback(CallbackState.AVAILABLE, agent, timeoutMs);
+            if (expectSuspended) {
+                expectCallback(CallbackState.SUSPENDED, agent, timeoutMs);
+            }
+            expectCallback(CallbackState.NETWORK_CAPABILITIES, agent, timeoutMs);
+            expectCallback(CallbackState.LINK_PROPERTIES, agent, timeoutMs);
+        }
+
+        void expectAvailableCallbacks(MockNetworkAgent agent) {
+            expectAvailableCallbacks(agent, false, TIMEOUT_MS);
+        }
+
+        void expectAvailableAndSuspendedCallbacks(MockNetworkAgent agent) {
+            expectAvailableCallbacks(agent, true, TIMEOUT_MS);
+        }
+
+        void expectAvailableAndValidatedCallbacks(MockNetworkAgent agent) {
+            expectAvailableCallbacks(agent, false, TIMEOUT_MS);
+            expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent);
+        }
+
+        void expectCapabilitiesWith(int capability, MockNetworkAgent agent) {
+            CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent);
+            NetworkCapabilities nc = (NetworkCapabilities) cbi.arg;
+            assertTrue(nc.hasCapability(capability));
+        }
+
+        void expectCapabilitiesWithout(int capability, MockNetworkAgent agent) {
+            CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent);
+            NetworkCapabilities nc = (NetworkCapabilities) cbi.arg;
+            assertFalse(nc.hasCapability(capability));
         }
 
         void assertNoCallback() {
@@ -1175,7 +1302,7 @@
         }
     }
 
-    @LargeTest
+    @SmallTest
     public void testStateChangeNetworkCallbacks() throws Exception {
         final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback();
         final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback();
@@ -1194,8 +1321,8 @@
         ConditionVariable cv = waitForConnectivityBroadcasts(1);
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(false);
-        genericNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        genericNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         waitFor(cv);
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
@@ -1209,8 +1336,8 @@
         cv = waitForConnectivityBroadcasts(2);
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
-        genericNetworkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
-        wifiNetworkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        genericNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        wifiNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         waitFor(cv);
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
@@ -1233,8 +1360,8 @@
         // Test validated networks
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        genericNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        genericNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
 
@@ -1246,9 +1373,10 @@
 
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        genericNetworkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        genericNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
         genericNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
-        wifiNetworkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        genericNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        wifiNetworkCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
         cellNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
@@ -1284,28 +1412,32 @@
         mEthernetNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
 
         mCellNetworkAgent.connect(true);
-        callback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         mWiFiNetworkAgent.connect(true);
         // We get AVAILABLE on wifi when wifi connects and satisfies our unmetered request.
         // We then get LOSING when wifi validates and cell is outscored.
-        callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        // TODO: Investigate sending validated before losing.
         callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         mEthernetNetworkAgent.connect(true);
-        callback.expectCallback(CallbackState.AVAILABLE, mEthernetNetworkAgent);
+        callback.expectAvailableCallbacks(mEthernetNetworkAgent);
+        // TODO: Investigate sending validated before losing.
         callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mEthernetNetworkAgent);
+        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent);
+        defaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
         assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         mEthernetNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
         defaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
 
         for (int i = 0; i < 4; i++) {
             MockNetworkAgent oldNetwork, newNetwork;
@@ -1322,7 +1454,7 @@
             callback.expectCallback(CallbackState.LOSING, oldNetwork);
             // TODO: should we send an AVAILABLE callback to newNetwork, to indicate that it is no
             // longer lingering?
-            defaultCallback.expectCallback(CallbackState.AVAILABLE, newNetwork);
+            defaultCallback.expectAvailableCallbacks(newNetwork);
             assertEquals(newNetwork.getNetwork(), mCm.getActiveNetwork());
         }
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -1330,17 +1462,19 @@
         // Verify that if a network no longer satisfies a request, we send LOST and not LOSING, even
         // if the network is still up.
         mWiFiNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
+        // We expect a notification about the capabilities change, and nothing else.
+        defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED, mWiFiNetworkAgent);
+        defaultCallback.assertNoCallback();
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
         // Wifi no longer satisfies our listen, which is for an unmetered network.
         // But because its score is 55, it's still up (and the default network).
-        defaultCallback.assertNoCallback();
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Disconnect our test networks.
         mWiFiNetworkAgent.disconnect();
         defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
         mCellNetworkAgent.disconnect();
         defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
 
@@ -1356,22 +1490,22 @@
 
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(false);   // Score: 10
-        callback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        callback.expectAvailableCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Bring up wifi with a score of 20.
         // Cell stays up because it would satisfy the default request if it validated.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);   // Score: 20
-        callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         mWiFiNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Bring up wifi with a score of 70.
@@ -1379,31 +1513,33 @@
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.adjustScore(50);
         mWiFiNetworkAgent.connect(false);   // Score: 70
-        callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
         callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Tear down wifi.
         mWiFiNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Bring up wifi, then validate it. Previous versions would immediately tear down cell, but
         // it's arguably correct to linger it, since it was the default network before it validated.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        // TODO: Investigate sending validated before losing.
         callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         mWiFiNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
         mCellNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
         defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
@@ -1411,13 +1547,15 @@
         // If a network is lingering, and we add and remove a request from it, resume lingering.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        callback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        // TODO: Investigate sending validated before losing.
         callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
 
         NetworkRequest cellRequest = new NetworkRequest.Builder()
                 .addTransportType(TRANSPORT_CELLULAR).build();
@@ -1433,7 +1571,7 @@
         mWiFiNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
 
         // Cell is now the default network. Pin it with a cell-specific request.
         noopCallback = new NetworkCallback();  // Can't reuse NetworkCallbacks. http://b/20701525
@@ -1442,8 +1580,8 @@
         // Now connect wifi, and expect it to become the default network.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        callback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
         // The default request is lingering on cell, but nothing happens to cell, and we send no
         // callbacks for it, because it's kept up by cellRequest.
         callback.assertNoCallback();
@@ -1453,8 +1591,8 @@
 
         // Let linger run its course.
         callback.assertNoCallback();
-        callback.expectCallback(CallbackState.LOST, mCellNetworkAgent,
-                TEST_LINGER_DELAY_MS /* timeoutMs */);
+        final int lingerTimeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4;
+        callback.expectCallback(CallbackState.LOST, mCellNetworkAgent, lingerTimeoutMs);
 
         // Clean up.
         mWiFiNetworkAgent.disconnect();
@@ -1558,7 +1696,7 @@
         handlerThread.quit();
     }
 
-    @LargeTest
+    @SmallTest
     public void testNetworkFactoryRequests() throws Exception {
         tryNetworkFactoryRequests(NET_CAPABILITY_MMS);
         tryNetworkFactoryRequests(NET_CAPABILITY_SUPL);
@@ -1578,7 +1716,7 @@
         // Skipping VALIDATED and CAPTIVE_PORTAL as they're disallowed.
     }
 
-    @LargeTest
+    @SmallTest
     public void testNoMutableNetworkRequests() throws Exception {
         PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, new Intent("a"), 0);
         NetworkRequest.Builder builder = new NetworkRequest.Builder();
@@ -1603,7 +1741,7 @@
         } catch (IllegalArgumentException expected) {}
     }
 
-    @LargeTest
+    @SmallTest
     public void testMMSonWiFi() throws Exception {
         // Test bringing up cellular without MMS NetworkRequest gets reaped
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -1611,8 +1749,8 @@
         ConditionVariable cv = mCellNetworkAgent.getDisconnectedCV();
         mCellNetworkAgent.connectWithoutInternet();
         waitFor(cv);
-        waitFor(new Criteria() {
-                public boolean get() { return mCm.getAllNetworks().length == 0; } });
+        mService.waitForIdle();
+        assertEquals(0, mCm.getAllNetworks().length);
         verifyNoNetwork();
         // Test bringing up validated WiFi.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
@@ -1629,7 +1767,7 @@
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS);
         mCellNetworkAgent.connectWithoutInternet();
-        networkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        networkCallback.expectAvailableCallbacks(mCellNetworkAgent);
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test releasing NetworkRequest disconnects cellular with MMS
         cv = mCellNetworkAgent.getDisconnectedCV();
@@ -1638,7 +1776,7 @@
         verifyActiveNetwork(TRANSPORT_WIFI);
     }
 
-    @LargeTest
+    @SmallTest
     public void testMMSonCell() throws Exception {
         // Test bringing up cellular without MMS
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -1655,7 +1793,7 @@
         MockNetworkAgent mmsNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS);
         mmsNetworkAgent.connectWithoutInternet();
-        networkCallback.expectCallback(CallbackState.AVAILABLE, mmsNetworkAgent);
+        networkCallback.expectAvailableCallbacks(mmsNetworkAgent);
         verifyActiveNetwork(TRANSPORT_CELLULAR);
         // Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent
         cv = mmsNetworkAgent.getDisconnectedCV();
@@ -1664,7 +1802,7 @@
         verifyActiveNetwork(TRANSPORT_CELLULAR);
     }
 
-    @LargeTest
+    @SmallTest
     public void testCaptivePortal() {
         final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
         final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
@@ -1681,7 +1819,7 @@
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         String firstRedirectUrl = "http://example.com/firstPath";
         mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl);
-        captivePortalCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), firstRedirectUrl);
 
         // Take down network.
@@ -1694,7 +1832,7 @@
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         String secondRedirectUrl = "http://example.com/secondPath";
         mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl);
-        captivePortalCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), secondRedirectUrl);
 
         // Make captive portal disappear then revalidate.
@@ -1704,7 +1842,9 @@
         captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
         // Expect NET_CAPABILITY_VALIDATED onAvailable callback.
-        validatedCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        // TODO: Investigate only sending available callbacks.
+        validatedCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
 
         // Break network connectivity.
         // Expect NET_CAPABILITY_VALIDATED onLost callback.
@@ -1713,7 +1853,53 @@
         validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
     }
 
-    @LargeTest
+    @SmallTest
+    public void testCaptivePortalApp() {
+        final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
+        final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
+        mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback);
+
+        final TestNetworkCallback validatedCallback = new TestNetworkCallback();
+        final NetworkRequest validatedRequest = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_VALIDATED).build();
+        mCm.registerNetworkCallback(validatedRequest, validatedCallback);
+
+        // Bring up wifi.
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(true);
+        validatedCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+        Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
+
+        // Check that calling startCaptivePortalApp does nothing.
+        final int fastTimeoutMs = 100;
+        mCm.startCaptivePortalApp(wifiNetwork);
+        mServiceContext.expectNoStartActivityIntent(fastTimeoutMs);
+
+        // Turn into a captive portal.
+        mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 302;
+        mCm.reportNetworkConnectivity(wifiNetwork, false);
+        captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+
+        // Check that startCaptivePortalApp sends the expected intent.
+        mCm.startCaptivePortalApp(wifiNetwork);
+        Intent intent = mServiceContext.expectStartActivityIntent(TIMEOUT_MS);
+        assertEquals(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN, intent.getAction());
+        assertEquals(wifiNetwork, intent.getExtra(ConnectivityManager.EXTRA_NETWORK));
+
+        // Have the app report that the captive portal is dismissed, and check that we revalidate.
+        mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204;
+        CaptivePortal c = (CaptivePortal) intent.getExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL);
+        c.reportCaptivePortalDismissed();
+        validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+
+        mCm.unregisterNetworkCallback(validatedCallback);
+        mCm.unregisterNetworkCallback(captivePortalCallback);
+    }
+
+    @SmallTest
     public void testAvoidOrIgnoreCaptivePortals() {
         final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
         final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
@@ -1749,42 +1935,138 @@
         mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl);
 
         // Expect NET_CAPABILITY_VALIDATED onAvailable callback.
-        validatedCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
         // But there should be no CaptivePortal callback.
         captivePortalCallback.assertNoCallback();
     }
 
-    @SmallTest
-    public void testInvalidNetworkSpecifier() {
-        boolean execptionCalled = true;
+    private NetworkRequest.Builder newWifiRequestBuilder() {
+        return new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI);
+    }
 
-        try {
-            NetworkRequest.Builder builder = new NetworkRequest.Builder();
-            builder.setNetworkSpecifier(MATCH_ALL_REQUESTS_NETWORK_SPECIFIER);
-            execptionCalled = false;
-        } catch (IllegalArgumentException e) {
-            // do nothing - should get here
+    @SmallTest
+    public void testNetworkSpecifier() {
+        NetworkRequest rEmpty1 = newWifiRequestBuilder().build();
+        NetworkRequest rEmpty2 = newWifiRequestBuilder().setNetworkSpecifier((String) null).build();
+        NetworkRequest rEmpty3 = newWifiRequestBuilder().setNetworkSpecifier("").build();
+        NetworkRequest rEmpty4 = newWifiRequestBuilder().setNetworkSpecifier(
+            (NetworkSpecifier) null).build();
+        NetworkRequest rFoo = newWifiRequestBuilder().setNetworkSpecifier("foo").build();
+        NetworkRequest rBar = newWifiRequestBuilder().setNetworkSpecifier(
+                new StringNetworkSpecifier("bar")).build();
+
+        TestNetworkCallback cEmpty1 = new TestNetworkCallback();
+        TestNetworkCallback cEmpty2 = new TestNetworkCallback();
+        TestNetworkCallback cEmpty3 = new TestNetworkCallback();
+        TestNetworkCallback cEmpty4 = new TestNetworkCallback();
+        TestNetworkCallback cFoo = new TestNetworkCallback();
+        TestNetworkCallback cBar = new TestNetworkCallback();
+        TestNetworkCallback[] emptyCallbacks = new TestNetworkCallback[] {
+                cEmpty1, cEmpty2, cEmpty3 };
+
+        mCm.registerNetworkCallback(rEmpty1, cEmpty1);
+        mCm.registerNetworkCallback(rEmpty2, cEmpty2);
+        mCm.registerNetworkCallback(rEmpty3, cEmpty3);
+        mCm.registerNetworkCallback(rEmpty4, cEmpty4);
+        mCm.registerNetworkCallback(rFoo, cFoo);
+        mCm.registerNetworkCallback(rBar, cBar);
+
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(false);
+        cEmpty1.expectAvailableCallbacks(mWiFiNetworkAgent);
+        cEmpty2.expectAvailableCallbacks(mWiFiNetworkAgent);
+        cEmpty3.expectAvailableCallbacks(mWiFiNetworkAgent);
+        cEmpty4.expectAvailableCallbacks(mWiFiNetworkAgent);
+        assertNoCallbacks(cFoo, cBar);
+
+        mWiFiNetworkAgent.setNetworkSpecifier(new StringNetworkSpecifier("foo"));
+        cFoo.expectAvailableCallbacks(mWiFiNetworkAgent);
+        for (TestNetworkCallback c: emptyCallbacks) {
+            c.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent);
+        }
+        cFoo.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent);
+        cFoo.assertNoCallback();
+
+        mWiFiNetworkAgent.setNetworkSpecifier(new StringNetworkSpecifier("bar"));
+        cFoo.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        cBar.expectAvailableCallbacks(mWiFiNetworkAgent);
+        for (TestNetworkCallback c: emptyCallbacks) {
+            c.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent);
+        }
+        cBar.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent);
+        cBar.assertNoCallback();
+
+        mWiFiNetworkAgent.setNetworkSpecifier(null);
+        cBar.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        for (TestNetworkCallback c: emptyCallbacks) {
+            c.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent);
         }
 
-        assertTrue("NetworkRequest builder with MATCH_ALL_REQUESTS_NETWORK_SPECIFIER",
-                execptionCalled);
+        assertNoCallbacks(cEmpty1, cEmpty2, cEmpty3, cFoo, cBar);
+    }
+
+    @SmallTest
+    public void testInvalidNetworkSpecifier() {
+        try {
+            NetworkRequest.Builder builder = new NetworkRequest.Builder();
+            builder.setNetworkSpecifier(new MatchAllNetworkSpecifier());
+            fail("NetworkRequest builder with MatchAllNetworkSpecifier");
+        } catch (IllegalArgumentException expected) {
+            // expected
+        }
 
         try {
             NetworkCapabilities networkCapabilities = new NetworkCapabilities();
             networkCapabilities.addTransportType(TRANSPORT_WIFI)
-                    .setNetworkSpecifier(NetworkCapabilities.MATCH_ALL_REQUESTS_NETWORK_SPECIFIER);
+                    .setNetworkSpecifier(new MatchAllNetworkSpecifier());
             mService.requestNetwork(networkCapabilities, null, 0, null,
                     ConnectivityManager.TYPE_WIFI);
-            execptionCalled = false;
-        } catch (IllegalArgumentException e) {
-            // do nothing - should get here
+            fail("ConnectivityService requestNetwork with MatchAllNetworkSpecifier");
+        } catch (IllegalArgumentException expected) {
+            // expected
         }
 
-        assertTrue("ConnectivityService requestNetwork with MATCH_ALL_REQUESTS_NETWORK_SPECIFIER",
-                execptionCalled);
+        class NonParcelableSpecifier extends NetworkSpecifier {
+            public boolean satisfiedBy(NetworkSpecifier other) { return false; }
+        };
+        class ParcelableSpecifier extends NonParcelableSpecifier implements Parcelable {
+            @Override public int describeContents() { return 0; }
+            @Override public void writeToParcel(Parcel p, int flags) {}
+        }
+        NetworkRequest.Builder builder;
+
+        builder = new NetworkRequest.Builder().addTransportType(TRANSPORT_ETHERNET);
+        try {
+            builder.setNetworkSpecifier(new NonParcelableSpecifier());
+            Parcel parcelW = Parcel.obtain();
+            builder.build().writeToParcel(parcelW, 0);
+            fail("Parceling a non-parcelable specifier did not throw an exception");
+        } catch (Exception e) {
+            // expected
+        }
+
+        builder = new NetworkRequest.Builder().addTransportType(TRANSPORT_ETHERNET);
+        builder.setNetworkSpecifier(new ParcelableSpecifier());
+        NetworkRequest nr = builder.build();
+        assertNotNull(nr);
+
+        try {
+            Parcel parcelW = Parcel.obtain();
+            nr.writeToParcel(parcelW, 0);
+            byte[] bytes = parcelW.marshall();
+            parcelW.recycle();
+
+            Parcel parcelR = Parcel.obtain();
+            parcelR.unmarshall(bytes, 0, bytes.length);
+            parcelR.setDataPosition(0);
+            NetworkRequest rereadNr = NetworkRequest.CREATOR.createFromParcel(parcelR);
+            fail("Unparceling a non-framework NetworkSpecifier did not throw an exception");
+        } catch (Exception e) {
+            // expected
+        }
     }
 
-    @LargeTest
+    @SmallTest
     public void testRegisterDefaultNetworkCallback() throws Exception {
         final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(defaultNetworkCallback);
@@ -1802,14 +2084,14 @@
         // Bring up cell and expect CALLBACK_AVAILABLE.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        defaultNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        defaultNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
 
         // Bring up wifi and expect CALLBACK_AVAILABLE.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
         cellNetworkCallback.assertNoCallback();
-        defaultNetworkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        defaultNetworkCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
 
         // Bring down cell. Expect no default network callback, since it wasn't the default.
         mCellNetworkAgent.disconnect();
@@ -1819,7 +2101,7 @@
         // Bring up cell. Expect no default network callback, since it won't be the default.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
         defaultNetworkCallback.assertNoCallback();
 
         // Bring down wifi. Expect the default network callback to notified of LOST wifi
@@ -1827,28 +2109,16 @@
         mWiFiNetworkAgent.disconnect();
         cellNetworkCallback.assertNoCallback();
         defaultNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        defaultNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent);
         mCellNetworkAgent.disconnect();
         cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
         defaultNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
     }
 
-    private class TestRequestUpdateCallback extends TestNetworkCallback {
-        @Override
-        public void onCapabilitiesChanged(Network network, NetworkCapabilities netCap) {
-            setLastCallback(CallbackState.NETWORK_CAPABILITIES, network, netCap);
-        }
-
-        @Override
-        public void onLinkPropertiesChanged(Network network, LinkProperties linkProp) {
-            setLastCallback(CallbackState.LINK_PROPERTIES, network, linkProp);
-        }
-    }
-
-    @LargeTest
-    public void testRequestCallbackUpdates() throws Exception {
+    @SmallTest
+    public void testAdditionalStateCallbacks() throws Exception {
         // File a network request for mobile.
-        final TestNetworkCallback cellNetworkCallback = new TestRequestUpdateCallback();
+        final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
         final NetworkRequest cellRequest = new NetworkRequest.Builder()
                 .addTransportType(TRANSPORT_CELLULAR).build();
         mCm.requestNetwork(cellRequest, cellNetworkCallback);
@@ -1857,10 +2127,10 @@
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
 
-        // We should get onAvailable().
-        cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        // We should get onCapabilitiesChanged(), when the mobile network successfully validates.
-        cellNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, mCellNetworkAgent);
+        // We should get onAvailable(), onCapabilitiesChanged(), and
+        // onLinkPropertiesChanged() in rapid succession. Additionally, we
+        // should get onCapabilitiesChanged() when the mobile network validates.
+        cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
 
         // Update LinkProperties.
@@ -1871,23 +2141,17 @@
         cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
 
+        // Suspend the network.
+        mCellNetworkAgent.suspend();
+        cellNetworkCallback.expectCallback(CallbackState.SUSPENDED, mCellNetworkAgent);
+        cellNetworkCallback.assertNoCallback();
+
         // Register a garden variety default network request.
-        final TestNetworkCallback dfltNetworkCallback = new TestRequestUpdateCallback();
+        final TestNetworkCallback dfltNetworkCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(dfltNetworkCallback);
-        // Only onAvailable() is called; no other information is delivered.
-        dfltNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        dfltNetworkCallback.assertNoCallback();
-
-        // Request a NetworkCapabilities update; only the requesting callback is notified.
-        mCm.requestNetworkCapabilities(dfltNetworkCallback);
-        dfltNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, mCellNetworkAgent);
-        cellNetworkCallback.assertNoCallback();
-        dfltNetworkCallback.assertNoCallback();
-
-        // Request a LinkProperties update; only the requesting callback is notified.
-        mCm.requestLinkProperties(dfltNetworkCallback);
-        dfltNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
-        cellNetworkCallback.assertNoCallback();
+        // We should get onAvailable(), onCapabilitiesChanged(), onLinkPropertiesChanged(),
+        // as well as onNetworkSuspended() in rapid succession.
+        dfltNetworkCallback.expectAvailableAndSuspendedCallbacks(mCellNetworkAgent);
         dfltNetworkCallback.assertNoCallback();
 
         mCm.unregisterNetworkCallback(dfltNetworkCallback);
@@ -1927,24 +2191,29 @@
 
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        callback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        fgCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        fgCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
         assertTrue(isForegroundNetwork(mCellNetworkAgent));
 
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
 
         // When wifi connects, cell lingers.
-        callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
-        fgCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
         callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        fgCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
         fgCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        fgCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         assertTrue(isForegroundNetwork(mCellNetworkAgent));
         assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
 
         // When lingering is complete, cell is still there but is now in the background.
-        fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent, TEST_LINGER_DELAY_MS);
-        callback.assertNoCallback();
+        mService.waitForIdle();
+        int timeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4;
+        fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent, timeoutMs);
+        // Expect a network capabilities update sans FOREGROUND.
+        callback.expectCapabilitiesWithout(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
         assertFalse(isForegroundNetwork(mCellNetworkAgent));
         assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
 
@@ -1953,9 +2222,15 @@
                 .addTransportType(TRANSPORT_CELLULAR).build();
         final TestNetworkCallback cellCallback = new TestNetworkCallback();
         mCm.requestNetwork(cellRequest, cellCallback);
-        cellCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        fgCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        callback.assertNoCallback();  // Because the network is already up.
+        // NOTE: This request causes the network's capabilities to change. This
+        // is currently delivered before the onAvailable() callbacks.
+        // TODO: Fix this.
+        cellCallback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
+        cellCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        fgCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        // Expect a network capabilities update with FOREGROUND, because the most recent
+        // request causes its state to change.
+        callback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
         assertTrue(isForegroundNetwork(mCellNetworkAgent));
         assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
 
@@ -1963,7 +2238,8 @@
         // lingering.
         mCm.unregisterNetworkCallback(cellCallback);
         fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
-        callback.assertNoCallback();
+        // Expect a network capabilities update sans FOREGROUND.
+        callback.expectCapabilitiesWithout(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
         assertFalse(isForegroundNetwork(mCellNetworkAgent));
         assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
 
@@ -1971,7 +2247,7 @@
         mWiFiNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         fgCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        fgCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        fgCallback.expectAvailableCallbacks(mCellNetworkAgent);
         assertTrue(isForegroundNetwork(mCellNetworkAgent));
 
         mCm.unregisterNetworkCallback(callback);
@@ -1980,6 +2256,7 @@
 
     @SmallTest
     public void testRequestBenchmark() throws Exception {
+        // TODO: turn this unit test into a real benchmarking test.
         // Benchmarks connecting and switching performance in the presence of a large number of
         // NetworkRequests.
         // 1. File NUM_REQUESTS requests.
@@ -1993,61 +2270,80 @@
         final CountDownLatch availableLatch = new CountDownLatch(NUM_REQUESTS);
         final CountDownLatch losingLatch = new CountDownLatch(NUM_REQUESTS);
 
-        final int REGISTER_TIME_LIMIT_MS = 100;
-        long startTime = System.currentTimeMillis();
         for (int i = 0; i < NUM_REQUESTS; i++) {
             callbacks[i] = new NetworkCallback() {
                 @Override public void onAvailable(Network n) { availableLatch.countDown(); }
                 @Override public void onLosing(Network n, int t) { losingLatch.countDown(); }
             };
-            mCm.registerNetworkCallback(request, callbacks[i]);
         }
-        long timeTaken = System.currentTimeMillis() - startTime;
-        String msg = String.format("Register %d callbacks: %dms, acceptable %dms",
-                NUM_REQUESTS, timeTaken, REGISTER_TIME_LIMIT_MS);
-        Log.d(TAG, msg);
-        assertTrue(msg, timeTaken < REGISTER_TIME_LIMIT_MS);
 
-        final int CONNECT_TIME_LIMIT_MS = 30;
+        final int REGISTER_TIME_LIMIT_MS = 180;
+        assertTimeLimit("Registering callbacks", REGISTER_TIME_LIMIT_MS, () -> {
+            for (NetworkCallback cb : callbacks) {
+                mCm.registerNetworkCallback(request, cb);
+            }
+        });
+
+        final int CONNECT_TIME_LIMIT_MS = 40;
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         // Don't request that the network validate, because otherwise connect() will block until
         // the network gets NET_CAPABILITY_VALIDATED, after all the callbacks below have fired,
         // and we won't actually measure anything.
         mCellNetworkAgent.connect(false);
-        startTime = System.currentTimeMillis();
-        if (!availableLatch.await(CONNECT_TIME_LIMIT_MS, TimeUnit.MILLISECONDS)) {
-            fail(String.format("Only dispatched %d/%d onAvailable callbacks in %dms",
-                    NUM_REQUESTS - availableLatch.getCount(), NUM_REQUESTS,
-                    CONNECT_TIME_LIMIT_MS));
-        }
-        timeTaken = System.currentTimeMillis() - startTime;
-        Log.d(TAG, String.format("Connect, %d callbacks: %dms, acceptable %dms",
-                NUM_REQUESTS, timeTaken, CONNECT_TIME_LIMIT_MS));
 
-        final int SWITCH_TIME_LIMIT_MS = 30;
+        long onAvailableDispatchingDuration = durationOf(() -> {
+            if (!awaitLatch(availableLatch, CONNECT_TIME_LIMIT_MS)) {
+                fail(String.format("Only dispatched %d/%d onAvailable callbacks in %dms",
+                        NUM_REQUESTS - availableLatch.getCount(), NUM_REQUESTS,
+                        CONNECT_TIME_LIMIT_MS));
+            }
+        });
+        Log.d(TAG, String.format("Connect, %d callbacks: %dms, acceptable %dms",
+                NUM_REQUESTS, onAvailableDispatchingDuration, CONNECT_TIME_LIMIT_MS));
+
+        final int SWITCH_TIME_LIMIT_MS = 40;
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         // Give wifi a high enough score that we'll linger cell when wifi comes up.
         mWiFiNetworkAgent.adjustScore(40);
         mWiFiNetworkAgent.connect(false);
-        startTime = System.currentTimeMillis();
-        if (!losingLatch.await(SWITCH_TIME_LIMIT_MS, TimeUnit.MILLISECONDS)) {
-            fail(String.format("Only dispatched %d/%d onLosing callbacks in %dms",
-                    NUM_REQUESTS - losingLatch.getCount(), NUM_REQUESTS, SWITCH_TIME_LIMIT_MS));
-        }
-        timeTaken = System.currentTimeMillis() - startTime;
+
+        long onLostDispatchingDuration = durationOf(() -> {
+            if (!awaitLatch(losingLatch, SWITCH_TIME_LIMIT_MS)) {
+                fail(String.format("Only dispatched %d/%d onLosing callbacks in %dms",
+                        NUM_REQUESTS - losingLatch.getCount(), NUM_REQUESTS, SWITCH_TIME_LIMIT_MS));
+            }
+        });
         Log.d(TAG, String.format("Linger, %d callbacks: %dms, acceptable %dms",
-                NUM_REQUESTS, timeTaken, SWITCH_TIME_LIMIT_MS));
+                NUM_REQUESTS, onLostDispatchingDuration, SWITCH_TIME_LIMIT_MS));
 
         final int UNREGISTER_TIME_LIMIT_MS = 10;
-        startTime = System.currentTimeMillis();
-        for (int i = 0; i < NUM_REQUESTS; i++) {
-            mCm.unregisterNetworkCallback(callbacks[i]);
-        }
-        timeTaken = System.currentTimeMillis() - startTime;
-        msg = String.format("Unregister %d callbacks: %dms, acceptable %dms",
-                NUM_REQUESTS, timeTaken, UNREGISTER_TIME_LIMIT_MS);
+        assertTimeLimit("Unregistering callbacks", UNREGISTER_TIME_LIMIT_MS, () -> {
+            for (NetworkCallback cb : callbacks) {
+                mCm.unregisterNetworkCallback(cb);
+            }
+        });
+    }
+
+    private long durationOf(Runnable fn) {
+        long startTime = SystemClock.elapsedRealtime();
+        fn.run();
+        return SystemClock.elapsedRealtime() - startTime;
+    }
+
+    private void assertTimeLimit(String descr, long timeLimit, Runnable fn) {
+        long timeTaken = durationOf(fn);
+        String msg = String.format("%s: took %dms, limit was %dms", descr, timeTaken, timeLimit);
         Log.d(TAG, msg);
-        assertTrue(msg, timeTaken < UNREGISTER_TIME_LIMIT_MS);
+        assertTrue(msg, timeTaken <= timeLimit);
+    }
+
+    private boolean awaitLatch(CountDownLatch l, long timeoutMs) {
+        try {
+            if (l.await(timeoutMs, TimeUnit.MILLISECONDS)) {
+                return true;
+            }
+        } catch (InterruptedException e) {}
+        return false;
     }
 
     @SmallTest
@@ -2092,7 +2388,7 @@
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         testFactory.expectAddRequests(2);  // Because the cell request changes score twice.
         mCellNetworkAgent.connect(true);
-        cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
         testFactory.waitForNetworkRequests(2);
         assertFalse(testFactory.getMyStartRequested());  // Because the cell network outscores us.
 
@@ -2118,7 +2414,7 @@
     @SmallTest
     public void testAvoidBadWifiSetting() throws Exception {
         final ContentResolver cr = mServiceContext.getContentResolver();
-        final WrappedAvoidBadWifiTracker tracker = mService.getAvoidBadWifiTracker();
+        final WrappedMultinetworkPolicyTracker tracker = mService.getMultinetworkPolicyTracker();
         final String settingName = Settings.Global.NETWORK_AVOID_BAD_WIFI;
 
         tracker.configRestrictsAvoidBadWifi = false;
@@ -2128,7 +2424,7 @@
             tracker.reevaluate();
             mService.waitForIdle();
             String msg = String.format("config=false, setting=%s", values[i]);
-            assertTrue(msg, mService.avoidBadWifi());
+            assertTrue(mService.avoidBadWifi());
             assertFalse(msg, tracker.shouldNotifyWifiUnvalidated());
         }
 
@@ -2156,7 +2452,7 @@
     @SmallTest
     public void testAvoidBadWifi() throws Exception {
         final ContentResolver cr = mServiceContext.getContentResolver();
-        final WrappedAvoidBadWifiTracker tracker = mService.getAvoidBadWifiTracker();
+        final WrappedMultinetworkPolicyTracker tracker = mService.getMultinetworkPolicyTracker();
 
         // Pretend we're on a carrier that restricts switching away from bad wifi.
         tracker.configRestrictsAvoidBadWifi = true;
@@ -2183,20 +2479,22 @@
         // Bring up validated cell.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
         Network cellNetwork = mCellNetworkAgent.getNetwork();
 
         // Bring up validated wifi.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
-        validatedWifiCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+        validatedWifiCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        validatedWifiCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
 
         // Fail validation on wifi.
         mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 599;
         mCm.reportNetworkConnectivity(wifiNetwork, false);
+        defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
         // Because avoid bad wifi is off, we don't switch to cellular.
@@ -2211,18 +2509,18 @@
         // that we switch back to cell.
         tracker.configRestrictsAvoidBadWifi = false;
         tracker.reevaluate();
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
         assertEquals(mCm.getActiveNetwork(), cellNetwork);
 
         // Switch back to a restrictive carrier.
         tracker.configRestrictsAvoidBadWifi = true;
         tracker.reevaluate();
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
         assertEquals(mCm.getActiveNetwork(), wifiNetwork);
 
         // Simulate the user selecting "switch" on the dialog, and check that we switch to cell.
         mCm.setAvoidUnvalidated(wifiNetwork);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
         assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability(
                 NET_CAPABILITY_VALIDATED));
         assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability(
@@ -2233,13 +2531,15 @@
         mWiFiNetworkAgent.disconnect();
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
-        validatedWifiCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+        validatedWifiCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        validatedWifiCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         wifiNetwork = mWiFiNetworkAgent.getNetwork();
 
         // Fail validation on wifi and expect the dialog to appear.
         mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 599;
         mCm.reportNetworkConnectivity(wifiNetwork, false);
+        defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
         // Simulate the user selecting "switch" and checking the don't ask again checkbox.
@@ -2247,7 +2547,7 @@
         tracker.reevaluate();
 
         // We now switch to cell.
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
         assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability(
                 NET_CAPABILITY_VALIDATED));
         assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability(
@@ -2258,17 +2558,17 @@
         // We switch to wifi and then to cell.
         Settings.Global.putString(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, null);
         tracker.reevaluate();
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
         assertEquals(mCm.getActiveNetwork(), wifiNetwork);
         Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1);
         tracker.reevaluate();
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
         assertEquals(mCm.getActiveNetwork(), cellNetwork);
 
         // If cell goes down, we switch to wifi.
         mCellNetworkAgent.disconnect();
         defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
         validatedWifiCallback.assertNoCallback();
 
         mCm.unregisterNetworkCallback(cellNetworkCallback);
@@ -2276,6 +2576,120 @@
         mCm.unregisterNetworkCallback(defaultCallback);
     }
 
+    @SmallTest
+    public void testMeteredMultipathPreferenceSetting() throws Exception {
+        final ContentResolver cr = mServiceContext.getContentResolver();
+        final WrappedMultinetworkPolicyTracker tracker = mService.getMultinetworkPolicyTracker();
+        final String settingName = Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
+
+        for (int config : Arrays.asList(0, 3, 2)) {
+            for (String setting: Arrays.asList(null, "0", "2", "1")) {
+                tracker.configMeteredMultipathPreference = config;
+                Settings.Global.putString(cr, settingName, setting);
+                tracker.reevaluate();
+                mService.waitForIdle();
+
+                final int expected = (setting != null) ? Integer.parseInt(setting) : config;
+                String msg = String.format("config=%d, setting=%s", config, setting);
+                assertEquals(msg, expected, mCm.getMultipathPreference(null));
+            }
+        }
+    }
+
+    /**
+     * Validate that a satisfied network request does not trigger onUnavailable() once the
+     * time-out period expires.
+     */
+    @SmallTest
+    public void testSatisfiedNetworkRequestDoesNotTriggerOnUnavailable() {
+        NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
+                NetworkCapabilities.TRANSPORT_WIFI).build();
+        final TestNetworkCallback networkCallback = new TestNetworkCallback();
+        final int timeoutMs = 150;
+        mCm.requestNetwork(nr, networkCallback, timeoutMs);
+
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(false);
+        networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, timeoutMs);
+
+        // pass timeout and validate that UNAVAILABLE is not called
+        networkCallback.assertNoCallback();
+    }
+
+    /**
+     * Validate that a satisfied network request followed by a disconnected (lost) network does
+     * not trigger onUnavailable() once the time-out period expires.
+     */
+    @SmallTest
+    public void testSatisfiedThenLostNetworkRequestDoesNotTriggerOnUnavailable() {
+        NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
+                NetworkCapabilities.TRANSPORT_WIFI).build();
+        final TestNetworkCallback networkCallback = new TestNetworkCallback();
+        final int requestTimeoutMs = 100;
+        mCm.requestNetwork(nr, networkCallback, requestTimeoutMs);
+
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(false);
+        final int assertTimeoutMs = 150;
+        networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, assertTimeoutMs);
+        sleepFor(20);
+        mWiFiNetworkAgent.disconnect();
+        networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+
+        // pass timeout and validate that UNAVAILABLE is not called
+        sleepFor(100);
+        networkCallback.assertNoCallback();
+    }
+
+    /**
+     * Validate that when a time-out is specified for a network request the onUnavailable()
+     * callback is called when time-out expires. Then validate that if network request is
+     * (somehow) satisfied - the callback isn't called later.
+     */
+    @SmallTest
+    public void testTimedoutNetworkRequest() {
+        NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
+                NetworkCapabilities.TRANSPORT_WIFI).build();
+        final TestNetworkCallback networkCallback = new TestNetworkCallback();
+        final int timeoutMs = 10;
+        mCm.requestNetwork(nr, networkCallback, timeoutMs);
+
+        // pass timeout and validate that UNAVAILABLE is called
+        networkCallback.expectCallback(CallbackState.UNAVAILABLE, null);
+
+        // create a network satisfying request - validate that request not triggered
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(false);
+        networkCallback.assertNoCallback();
+    }
+
+    /**
+     * Validate that when a network request is unregistered (cancelled) the time-out for that
+     * request doesn't trigger the onUnavailable() callback.
+     */
+    @SmallTest
+    public void testTimedoutAfterUnregisteredNetworkRequest() {
+        NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
+                NetworkCapabilities.TRANSPORT_WIFI).build();
+        final TestNetworkCallback networkCallback = new TestNetworkCallback();
+        final int timeoutMs = 10;
+        mCm.requestNetwork(nr, networkCallback, timeoutMs);
+
+        // remove request
+        mCm.unregisterNetworkCallback(networkCallback);
+
+        // pass timeout and validate that no callbacks
+        // Note: doesn't validate that nothing called from CS since even if called the CM already
+        // unregisters the callback and won't pass it through!
+        sleepFor(15);
+        networkCallback.assertNoCallback();
+
+        // create a network satisfying request - validate that request not triggered
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(false);
+        networkCallback.assertNoCallback();
+    }
+
     private static class TestKeepaliveCallback extends PacketKeepaliveCallback {
 
         public static enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };
@@ -2365,6 +2779,7 @@
         return mWiFiNetworkAgent.getNetwork();
     }
 
+    @SmallTest
     public void testPacketKeepalives() throws Exception {
         InetAddress myIPv4 = InetAddress.getByName("192.0.2.129");
         InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35");
@@ -2435,10 +2850,13 @@
         ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4);
         callback.expectStarted();
         mWiFiNetworkAgent.disconnect();
+        waitFor(mWiFiNetworkAgent.getDisconnectedCV());
         callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK);
 
         // ... and that stopping it after that has no adverse effects.
-        assertNull(mCm.getNetworkCapabilities(myNet));
+        mService.waitForIdle();
+        final Network myNetAlias = myNet;
+        assertNull(mCm.getNetworkCapabilities(myNetAlias));
         ka.stop();
 
         // Reconnect.
@@ -2450,6 +2868,7 @@
         callback.expectStarted();
         ka.stop();
         mWiFiNetworkAgent.disconnect();
+        waitFor(mWiFiNetworkAgent.getDisconnectedCV());
         mService.waitForIdle();
         callback.expectStopped();
 
@@ -2680,4 +3099,13 @@
             mCm.unregisterNetworkCallback(pendingIntent);
         }
     }
+
+    /* test utilities */
+    // TODO: eliminate all usages of sleepFor and replace by proper timeouts/waitForIdle.
+    static private void sleepFor(int ms) {
+        try {
+            Thread.sleep(ms);
+        } catch (InterruptedException e) {
+        }
+    }
 }
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
new file mode 100644
index 0000000..d11565a
--- /dev/null
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2016 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 com.android.server.connectivity.MetricsTestUtil.aBool;
+import static com.android.server.connectivity.MetricsTestUtil.aByteArray;
+import static com.android.server.connectivity.MetricsTestUtil.aLong;
+import static com.android.server.connectivity.MetricsTestUtil.aString;
+import static com.android.server.connectivity.MetricsTestUtil.aType;
+import static com.android.server.connectivity.MetricsTestUtil.anInt;
+import static com.android.server.connectivity.MetricsTestUtil.anIntArray;
+import static com.android.server.connectivity.MetricsTestUtil.b;
+import static com.android.server.connectivity.MetricsTestUtil.describeIpEvent;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.BLUETOOTH;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.CELLULAR;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.ETHERNET;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.MULTIPLE;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.WIFI;
+
+import android.net.ConnectivityMetricsEvent;
+import android.net.metrics.ApfProgramEvent;
+import android.net.metrics.ApfStats;
+import android.net.metrics.DefaultNetworkEvent;
+import android.net.metrics.DhcpClientEvent;
+import android.net.metrics.DhcpErrorEvent;
+import android.net.metrics.DnsEvent;
+import android.net.metrics.IpManagerEvent;
+import android.net.metrics.IpReachabilityEvent;
+import android.net.metrics.NetworkEvent;
+import android.net.metrics.RaEvent;
+import android.net.metrics.ValidationProbeEvent;
+import android.test.suitebuilder.annotation.SmallTest;
+import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
+import java.util.Arrays;
+import java.util.List;
+import junit.framework.TestCase;
+
+// TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
+public class IpConnectivityEventBuilderTest extends TestCase {
+
+    @SmallTest
+    public void testLinkLayerInferrence() {
+        ConnectivityMetricsEvent ev = describeIpEvent(
+                aType(IpReachabilityEvent.class),
+                anInt(IpReachabilityEvent.NUD_FAILED));
+
+        String want = String.join("\n",
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 0",
+                "  network_id: 0",
+                "  time_ms: 1",
+                "  transports: 0",
+                "  ip_reachability_event <",
+                "    event_type: 512",
+                "    if_name: \"\"",
+                "  >",
+                ">",
+                "version: 2\n");
+        verifySerialization(want, ev);
+
+        ev.netId = 123;
+        ev.transports = 3; // transports have priority for inferrence of link layer
+        ev.ifname = "wlan0";
+        want = String.join("\n",
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                String.format("  link_layer: %d", MULTIPLE),
+                "  network_id: 123",
+                "  time_ms: 1",
+                "  transports: 3",
+                "  ip_reachability_event <",
+                "    event_type: 512",
+                "    if_name: \"\"",
+                "  >",
+                ">",
+                "version: 2\n");
+        verifySerialization(want, ev);
+
+        ev.transports = 1;
+        ev.ifname = null;
+        want = String.join("\n",
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                String.format("  link_layer: %d", CELLULAR),
+                "  network_id: 123",
+                "  time_ms: 1",
+                "  transports: 1",
+                "  ip_reachability_event <",
+                "    event_type: 512",
+                "    if_name: \"\"",
+                "  >",
+                ">",
+                "version: 2\n");
+        verifySerialization(want, ev);
+
+        ev.transports = 0;
+        ev.ifname = "not_inferred";
+        want = String.join("\n",
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"not_inferred\"",
+                "  link_layer: 0",
+                "  network_id: 123",
+                "  time_ms: 1",
+                "  transports: 0",
+                "  ip_reachability_event <",
+                "    event_type: 512",
+                "    if_name: \"\"",
+                "  >",
+                ">",
+                "version: 2\n");
+        verifySerialization(want, ev);
+
+        ev.ifname = "bt-pan";
+        want = String.join("\n",
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                String.format("  link_layer: %d", BLUETOOTH),
+                "  network_id: 123",
+                "  time_ms: 1",
+                "  transports: 0",
+                "  ip_reachability_event <",
+                "    event_type: 512",
+                "    if_name: \"\"",
+                "  >",
+                ">",
+                "version: 2\n");
+        verifySerialization(want, ev);
+
+        ev.ifname = "rmnet_ipa0";
+        want = String.join("\n",
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                String.format("  link_layer: %d", CELLULAR),
+                "  network_id: 123",
+                "  time_ms: 1",
+                "  transports: 0",
+                "  ip_reachability_event <",
+                "    event_type: 512",
+                "    if_name: \"\"",
+                "  >",
+                ">",
+                "version: 2\n");
+        verifySerialization(want, ev);
+
+        ev.ifname = "wlan0";
+        want = String.join("\n",
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                String.format("  link_layer: %d", WIFI),
+                "  network_id: 123",
+                "  time_ms: 1",
+                "  transports: 0",
+                "  ip_reachability_event <",
+                "    event_type: 512",
+                "    if_name: \"\"",
+                "  >",
+                ">",
+                "version: 2\n");
+        verifySerialization(want, ev);
+    }
+
+    @SmallTest
+    public void testDefaultNetworkEventSerialization() {
+        ConnectivityMetricsEvent ev = describeIpEvent(
+                aType(DefaultNetworkEvent.class),
+                anInt(102),
+                anIntArray(1, 2, 3),
+                anInt(101),
+                aBool(true),
+                aBool(false));
+
+        String want = String.join("\n",
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 0",
+                "  network_id: 0",
+                "  time_ms: 1",
+                "  transports: 0",
+                "  default_network_event <",
+                "    network_id <",
+                "      network_id: 102",
+                "    >",
+                "    previous_network_id <",
+                "      network_id: 101",
+                "    >",
+                "    previous_network_ip_support: 1",
+                "    transport_types: 1",
+                "    transport_types: 2",
+                "    transport_types: 3",
+                "  >",
+                ">",
+                "version: 2\n");
+
+        verifySerialization(want, ev);
+    }
+
+    @SmallTest
+    public void testDhcpClientEventSerialization() {
+        ConnectivityMetricsEvent ev = describeIpEvent(
+                aType(DhcpClientEvent.class),
+                aString("SomeState"),
+                anInt(192));
+
+        String want = String.join("\n",
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 0",
+                "  network_id: 0",
+                "  time_ms: 1",
+                "  transports: 0",
+                "  dhcp_event <",
+                "    duration_ms: 192",
+                "    if_name: \"\"",
+                "    state_transition: \"SomeState\"",
+                "  >",
+                ">",
+                "version: 2\n");
+
+        verifySerialization(want, ev);
+    }
+
+    @SmallTest
+    public void testDhcpErrorEventSerialization() {
+        ConnectivityMetricsEvent ev = describeIpEvent(
+                aType(DhcpErrorEvent.class),
+                anInt(DhcpErrorEvent.L4_NOT_UDP));
+
+        String want = String.join("\n",
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 0",
+                "  network_id: 0",
+                "  time_ms: 1",
+                "  transports: 0",
+                "  dhcp_event <",
+                "    duration_ms: 0",
+                "    if_name: \"\"",
+                "    error_code: 50397184",
+                "  >",
+                ">",
+                "version: 2\n");
+
+        verifySerialization(want, ev);
+    }
+
+    @SmallTest
+    public void testIpManagerEventSerialization() {
+        ConnectivityMetricsEvent ev = describeIpEvent(
+                aType(IpManagerEvent.class),
+                anInt(IpManagerEvent.PROVISIONING_OK),
+                aLong(5678));
+
+        String want = String.join("\n",
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 0",
+                "  network_id: 0",
+                "  time_ms: 1",
+                "  transports: 0",
+                "  ip_provisioning_event <",
+                "    event_type: 1",
+                "    if_name: \"\"",
+                "    latency_ms: 5678",
+                "  >",
+                ">",
+                "version: 2\n");
+
+        verifySerialization(want, ev);
+    }
+
+    @SmallTest
+    public void testIpReachabilityEventSerialization() {
+        ConnectivityMetricsEvent ev = describeIpEvent(
+                aType(IpReachabilityEvent.class),
+                anInt(IpReachabilityEvent.NUD_FAILED));
+
+        String want = String.join("\n",
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 0",
+                "  network_id: 0",
+                "  time_ms: 1",
+                "  transports: 0",
+                "  ip_reachability_event <",
+                "    event_type: 512",
+                "    if_name: \"\"",
+                "  >",
+                ">",
+                "version: 2\n");
+
+        verifySerialization(want, ev);
+    }
+
+    @SmallTest
+    public void testNetworkEventSerialization() {
+        ConnectivityMetricsEvent ev = describeIpEvent(
+                aType(NetworkEvent.class),
+                anInt(100),
+                anInt(5),
+                aLong(20410));
+
+        String want = String.join("\n",
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 0",
+                "  network_id: 0",
+                "  time_ms: 1",
+                "  transports: 0",
+                "  network_event <",
+                "    event_type: 5",
+                "    latency_ms: 20410",
+                "    network_id <",
+                "      network_id: 100",
+                "    >",
+                "  >",
+                ">",
+                "version: 2\n");
+
+        verifySerialization(want, ev);
+    }
+
+    @SmallTest
+    public void testValidationProbeEventSerialization() {
+        ConnectivityMetricsEvent ev = describeIpEvent(
+                aType(ValidationProbeEvent.class),
+                aLong(40730),
+                anInt(ValidationProbeEvent.PROBE_HTTP),
+                anInt(204));
+
+        String want = String.join("\n",
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 0",
+                "  network_id: 0",
+                "  time_ms: 1",
+                "  transports: 0",
+                "  validation_probe_event <",
+                "    latency_ms: 40730",
+                "    probe_result: 204",
+                "    probe_type: 1",
+                "  >",
+                ">",
+                "version: 2\n");
+
+        verifySerialization(want, ev);
+    }
+
+    @SmallTest
+    public void testApfProgramEventSerialization() {
+        ConnectivityMetricsEvent ev = describeIpEvent(
+                aType(ApfProgramEvent.class),
+                aLong(200),
+                aLong(18),
+                anInt(7),
+                anInt(9),
+                anInt(2048),
+                anInt(3));
+
+        String want = String.join("\n",
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 0",
+                "  network_id: 0",
+                "  time_ms: 1",
+                "  transports: 0",
+                "  apf_program_event <",
+                "    current_ras: 9",
+                "    drop_multicast: true",
+                "    effective_lifetime: 18",
+                "    filtered_ras: 7",
+                "    has_ipv4_addr: true",
+                "    lifetime: 200",
+                "    program_length: 2048",
+                "  >",
+                ">",
+                "version: 2\n");
+
+        verifySerialization(want, ev);
+    }
+
+    @SmallTest
+    public void testApfStatsSerialization() {
+        ConnectivityMetricsEvent ev = describeIpEvent(
+                aType(ApfStats.class),
+                aLong(45000),
+                anInt(10),
+                anInt(2),
+                anInt(2),
+                anInt(1),
+                anInt(2),
+                anInt(4),
+                anInt(7),
+                anInt(3),
+                anInt(2048));
+
+        String want = String.join("\n",
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 0",
+                "  network_id: 0",
+                "  time_ms: 1",
+                "  transports: 0",
+                "  apf_statistics <",
+                "    dropped_ras: 2",
+                "    duration_ms: 45000",
+                "    matching_ras: 2",
+                "    max_program_size: 2048",
+                "    parse_errors: 2",
+                "    program_updates: 4",
+                "    program_updates_all: 7",
+                "    program_updates_allowing_multicast: 3",
+                "    received_ras: 10",
+                "    zero_lifetime_ras: 1",
+                "  >",
+                ">",
+                "version: 2\n");
+
+        verifySerialization(want, ev);
+    }
+
+    @SmallTest
+    public void testRaEventSerialization() {
+        ConnectivityMetricsEvent ev = describeIpEvent(
+                aType(RaEvent.class),
+                aLong(2000),
+                aLong(400),
+                aLong(300),
+                aLong(-1),
+                aLong(1000),
+                aLong(-1));
+
+        String want = String.join("\n",
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 0",
+                "  network_id: 0",
+                "  time_ms: 1",
+                "  transports: 0",
+                "  ra_event <",
+                "    dnssl_lifetime: -1",
+                "    prefix_preferred_lifetime: 300",
+                "    prefix_valid_lifetime: 400",
+                "    rdnss_lifetime: 1000",
+                "    route_info_lifetime: -1",
+                "    router_lifetime: 2000",
+                "  >",
+                ">",
+                "version: 2\n");
+
+        verifySerialization(want, ev);
+    }
+
+    static void verifySerialization(String want, ConnectivityMetricsEvent... input) {
+        try {
+            List<IpConnectivityEvent> proto =
+                    IpConnectivityEventBuilder.toProto(Arrays.asList(input));
+            byte[] got = IpConnectivityEventBuilder.serialize(0, proto);
+            IpConnectivityLog log = IpConnectivityLog.parseFrom(got);
+            assertEquals(want, log.toString());
+        } catch (Exception e) {
+            fail(e.toString());
+        }
+    }
+}
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
new file mode 100644
index 0000000..e01469b
--- /dev/null
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2016, 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.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
+import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityMetricsEvent;
+import android.net.IIpConnectivityMetrics;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.metrics.ApfProgramEvent;
+import android.net.metrics.ApfStats;
+import android.net.metrics.DefaultNetworkEvent;
+import android.net.metrics.DhcpClientEvent;
+import android.net.metrics.IpConnectivityLog;
+import android.net.metrics.IpManagerEvent;
+import android.net.metrics.IpReachabilityEvent;
+import android.net.metrics.RaEvent;
+import android.net.metrics.ValidationProbeEvent;
+import android.system.OsConstants;
+import android.os.Parcelable;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
+import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpConnectivityMetricsTest {
+    static final IpReachabilityEvent FAKE_EV =
+            new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED);
+
+    private static final String EXAMPLE_IPV4 = "192.0.2.1";
+    private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1";
+
+    @Mock Context mCtx;
+    @Mock IIpConnectivityMetrics mMockService;
+    @Mock ConnectivityManager mCm;
+
+    IpConnectivityMetrics mService;
+    NetdEventListenerService mNetdListener;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mService = new IpConnectivityMetrics(mCtx, (ctx) -> 2000);
+        mNetdListener = new NetdEventListenerService(mCm);
+        mService.mNetdListener = mNetdListener;
+    }
+
+    @Test
+    public void testLoggingEvents() throws Exception {
+        IpConnectivityLog logger = new IpConnectivityLog(mMockService);
+
+        assertTrue(logger.log(1, FAKE_EV));
+        assertTrue(logger.log(2, FAKE_EV));
+        assertTrue(logger.log(3, FAKE_EV));
+
+        List<ConnectivityMetricsEvent> got = verifyEvents(3);
+        assertEventsEqual(expectedEvent(1), got.get(0));
+        assertEventsEqual(expectedEvent(2), got.get(1));
+        assertEventsEqual(expectedEvent(3), got.get(2));
+    }
+
+    @Test
+    public void testLoggingEventsWithMultipleCallers() throws Exception {
+        IpConnectivityLog logger = new IpConnectivityLog(mMockService);
+
+        final int nCallers = 10;
+        final int nEvents = 10;
+        for (int n = 0; n < nCallers; n++) {
+            final int i = n;
+            new Thread() {
+                public void run() {
+                    for (int j = 0; j < nEvents; j++) {
+                        assertTrue(logger.log(1 + i * 100 + j, FAKE_EV));
+                    }
+                }
+            }.start();
+        }
+
+        List<ConnectivityMetricsEvent> got = verifyEvents(nCallers * nEvents, 200);
+        Collections.sort(got, EVENT_COMPARATOR);
+        Iterator<ConnectivityMetricsEvent> iter = got.iterator();
+        for (int i = 0; i < nCallers; i++) {
+            for (int j = 0; j < nEvents; j++) {
+                int expectedTimestamp = 1 + i * 100 + j;
+                assertEventsEqual(expectedEvent(expectedTimestamp), iter.next());
+            }
+        }
+    }
+
+    @Test
+    public void testBufferFlushing() {
+        String output1 = getdump("flush");
+        assertEquals("", output1);
+
+        new IpConnectivityLog(mService.impl).log(1, FAKE_EV);
+        String output2 = getdump("flush");
+        assertFalse("".equals(output2));
+
+        String output3 = getdump("flush");
+        assertEquals("", output3);
+    }
+
+    @Test
+    public void testRateLimiting() {
+        final IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
+        final ApfProgramEvent ev = new ApfProgramEvent();
+        final long fakeTimestamp = 1;
+
+        int attempt = 100; // More than burst quota, but less than buffer size.
+        for (int i = 0; i < attempt; i++) {
+            logger.log(ev);
+        }
+
+        String output1 = getdump("flush");
+        assertFalse("".equals(output1));
+
+        for (int i = 0; i < attempt; i++) {
+            assertFalse("expected event to be dropped", logger.log(fakeTimestamp, ev));
+        }
+
+        String output2 = getdump("flush");
+        assertEquals("", output2);
+    }
+
+    @Test
+    public void testEndToEndLogging() throws Exception {
+        // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
+        IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
+
+        NetworkCapabilities ncWifi = new NetworkCapabilities();
+        NetworkCapabilities ncCell = new NetworkCapabilities();
+        ncWifi.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
+        ncCell.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+
+        when(mCm.getNetworkCapabilities(new Network(100))).thenReturn(ncWifi);
+        when(mCm.getNetworkCapabilities(new Network(101))).thenReturn(ncCell);
+
+        ApfStats apfStats = new ApfStats();
+        apfStats.durationMs = 45000;
+        apfStats.receivedRas = 10;
+        apfStats.matchingRas = 2;
+        apfStats.droppedRas = 2;
+        apfStats.parseErrors = 2;
+        apfStats.zeroLifetimeRas = 1;
+        apfStats.programUpdates = 4;
+        apfStats.programUpdatesAll = 7;
+        apfStats.programUpdatesAllowingMulticast = 3;
+        apfStats.maxProgramSize = 2048;
+
+        ValidationProbeEvent validationEv = new ValidationProbeEvent();
+        validationEv.durationMs = 40730;
+        validationEv.probeType = ValidationProbeEvent.PROBE_HTTP;
+        validationEv.returnCode = 204;
+
+        Parcelable[] events = {
+            new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED),
+            new DhcpClientEvent("SomeState", 192),
+            new DefaultNetworkEvent(102, new int[]{1,2,3}, 101, true, false),
+            new IpManagerEvent(IpManagerEvent.PROVISIONING_OK, 5678),
+            validationEv,
+            apfStats,
+            new RaEvent(2000, 400, 300, -1, 1000, -1)
+        };
+
+        for (int i = 0; i < events.length; i++) {
+            ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent();
+            ev.timestamp = 100 * (i + 1);
+            ev.ifname = "wlan0";
+            ev.data = events[i];
+            logger.log(ev);
+        }
+
+        // netId, errno, latency, destination
+        connectEvent(100, OsConstants.EALREADY, 0, EXAMPLE_IPV4);
+        connectEvent(100, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6);
+        connectEvent(100, 0, 110, EXAMPLE_IPV4);
+        connectEvent(101, 0, 23, EXAMPLE_IPV4);
+        connectEvent(101, 0, 45, EXAMPLE_IPV6);
+        connectEvent(100, OsConstants.EAGAIN, 0, EXAMPLE_IPV4);
+
+        // netId, type, return code, latency
+        dnsEvent(100, EVENT_GETADDRINFO, 0, 3456);
+        dnsEvent(100, EVENT_GETADDRINFO, 3, 45);
+        dnsEvent(100, EVENT_GETHOSTBYNAME, 0, 638);
+        dnsEvent(101, EVENT_GETADDRINFO, 0, 56);
+        dnsEvent(101, EVENT_GETHOSTBYNAME, 0, 34);
+
+        String want = String.join("\n",
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 4",
+                "  network_id: 0",
+                "  time_ms: 100",
+                "  transports: 0",
+                "  ip_reachability_event <",
+                "    event_type: 512",
+                "    if_name: \"\"",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 4",
+                "  network_id: 0",
+                "  time_ms: 200",
+                "  transports: 0",
+                "  dhcp_event <",
+                "    duration_ms: 192",
+                "    if_name: \"\"",
+                "    state_transition: \"SomeState\"",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 4",
+                "  network_id: 0",
+                "  time_ms: 300",
+                "  transports: 0",
+                "  default_network_event <",
+                "    network_id <",
+                "      network_id: 102",
+                "    >",
+                "    previous_network_id <",
+                "      network_id: 101",
+                "    >",
+                "    previous_network_ip_support: 1",
+                "    transport_types: 1",
+                "    transport_types: 2",
+                "    transport_types: 3",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 4",
+                "  network_id: 0",
+                "  time_ms: 400",
+                "  transports: 0",
+                "  ip_provisioning_event <",
+                "    event_type: 1",
+                "    if_name: \"\"",
+                "    latency_ms: 5678",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 4",
+                "  network_id: 0",
+                "  time_ms: 500",
+                "  transports: 0",
+                "  validation_probe_event <",
+                "    latency_ms: 40730",
+                "    probe_result: 204",
+                "    probe_type: 1",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 4",
+                "  network_id: 0",
+                "  time_ms: 600",
+                "  transports: 0",
+                "  apf_statistics <",
+                "    dropped_ras: 2",
+                "    duration_ms: 45000",
+                "    matching_ras: 2",
+                "    max_program_size: 2048",
+                "    parse_errors: 2",
+                "    program_updates: 4",
+                "    program_updates_all: 7",
+                "    program_updates_allowing_multicast: 3",
+                "    received_ras: 10",
+                "    zero_lifetime_ras: 1",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 4",
+                "  network_id: 0",
+                "  time_ms: 700",
+                "  transports: 0",
+                "  ra_event <",
+                "    dnssl_lifetime: -1",
+                "    prefix_preferred_lifetime: 300",
+                "    prefix_valid_lifetime: 400",
+                "    rdnss_lifetime: 1000",
+                "    route_info_lifetime: -1",
+                "    router_lifetime: 2000",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 4",
+                "  network_id: 100",
+                "  time_ms: 0",
+                "  transports: 2",
+                "  connect_statistics <",
+                "    connect_blocking_count: 1",
+                "    connect_count: 3",
+                "    errnos_counters <",
+                "      key: 11",
+                "      value: 1",
+                "    >",
+                "    ipv6_addr_count: 1",
+                "    latencies_ms: 110",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 2",
+                "  network_id: 101",
+                "  time_ms: 0",
+                "  transports: 1",
+                "  connect_statistics <",
+                "    connect_blocking_count: 2",
+                "    connect_count: 2",
+                "    ipv6_addr_count: 1",
+                "    latencies_ms: 23",
+                "    latencies_ms: 45",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 4",
+                "  network_id: 100",
+                "  time_ms: 0",
+                "  transports: 2",
+                "  dns_lookup_batch <",
+                "    event_types: 1",
+                "    event_types: 1",
+                "    event_types: 2",
+                "    latencies_ms: 3456",
+                "    latencies_ms: 45",
+                "    latencies_ms: 638",
+                "    return_codes: 0",
+                "    return_codes: 3",
+                "    return_codes: 0",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 2",
+                "  network_id: 101",
+                "  time_ms: 0",
+                "  transports: 1",
+                "  dns_lookup_batch <",
+                "    event_types: 1",
+                "    event_types: 2",
+                "    latencies_ms: 56",
+                "    latencies_ms: 34",
+                "    return_codes: 0",
+                "    return_codes: 0",
+                "  >",
+                ">",
+                "version: 2\n");
+
+        verifySerialization(want, getdump("flush"));
+    }
+
+    String getdump(String ... command) {
+        StringWriter buffer = new StringWriter();
+        PrintWriter writer = new PrintWriter(buffer);
+        mService.impl.dump(null, writer, command);
+        return buffer.toString();
+    }
+
+    void connectEvent(int netid, int error, int latencyMs, String ipAddr) throws Exception {
+        mNetdListener.onConnectEvent(netid, error, latencyMs, ipAddr, 80, 1);
+    }
+
+    void dnsEvent(int netId, int type, int result, int latency) throws Exception {
+        mNetdListener.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
+    }
+
+    List<ConnectivityMetricsEvent> verifyEvents(int n, int timeoutMs) throws Exception {
+        ArgumentCaptor<ConnectivityMetricsEvent> captor =
+                ArgumentCaptor.forClass(ConnectivityMetricsEvent.class);
+        verify(mMockService, timeout(timeoutMs).times(n)).logEvent(captor.capture());
+        return captor.getAllValues();
+    }
+
+    List<ConnectivityMetricsEvent> verifyEvents(int n) throws Exception {
+        return verifyEvents(n, 10);
+    }
+
+    static void verifySerialization(String want, String output) {
+        try {
+            byte[] got = Base64.decode(output, Base64.DEFAULT);
+            IpConnectivityLogClass.IpConnectivityLog log =
+                    IpConnectivityLogClass.IpConnectivityLog.parseFrom(got);
+            assertEquals(want, log.toString());
+        } catch (Exception e) {
+            fail(e.toString());
+        }
+    }
+
+    static String joinLines(String ... elems) {
+        StringBuilder b = new StringBuilder();
+        for (String s : elems) {
+            b.append(s).append("\n");
+        }
+        return b.toString();
+    }
+
+    static ConnectivityMetricsEvent expectedEvent(int timestamp) {
+        ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent();
+        ev.timestamp = timestamp;
+        ev.data = FAKE_EV;
+        return ev;
+    }
+
+    /** Outer equality for ConnectivityMetricsEvent to avoid overriding equals() and hashCode(). */
+    static void assertEventsEqual(ConnectivityMetricsEvent expected, ConnectivityMetricsEvent got) {
+        assertEquals(expected.timestamp, got.timestamp);
+        assertEquals(expected.data, got.data);
+    }
+
+    static final Comparator<ConnectivityMetricsEvent> EVENT_COMPARATOR =
+        Comparator.comparingLong((ev) -> ev.timestamp);
+}
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
similarity index 97%
rename from services/tests/servicestests/src/com/android/server/connectivity/LingerMonitorTest.java
rename to tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
index bce5787..77956be 100644
--- a/services/tests/servicestests/src/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -24,6 +24,7 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkMisc;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.text.format.DateUtils;
 import com.android.internal.R;
 import com.android.server.ConnectivityService;
@@ -70,6 +71,7 @@
         mMonitor = new TestableLingerMonitor(mCtx, mNotifier, HIGH_DAILY_LIMIT, HIGH_RATE_LIMIT);
     }
 
+    @SmallTest
     public void testTransitions() {
         setNotificationSwitch(transition(WIFI, CELLULAR));
         NetworkAgentInfo nai1 = wifiNai(100);
@@ -79,6 +81,7 @@
         assertFalse(mMonitor.isNotificationEnabled(nai2, nai1));
     }
 
+    @SmallTest
     public void testNotificationOnLinger() {
         setNotificationSwitch(transition(WIFI, CELLULAR));
         setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION);
@@ -89,6 +92,7 @@
         verifyNotification(from, to);
     }
 
+    @SmallTest
     public void testToastOnLinger() {
         setNotificationSwitch(transition(WIFI, CELLULAR));
         setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST);
@@ -99,6 +103,7 @@
         verifyToast(from, to);
     }
 
+    @SmallTest
     public void testNotificationClearedAfterDisconnect() {
         setNotificationSwitch(transition(WIFI, CELLULAR));
         setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION);
@@ -112,6 +117,7 @@
         verify(mNotifier, times(1)).clearNotification(100);
     }
 
+    @SmallTest
     public void testNotificationClearedAfterSwitchingBack() {
         setNotificationSwitch(transition(WIFI, CELLULAR));
         setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION);
@@ -125,6 +131,7 @@
         verify(mNotifier, times(1)).clearNotification(100);
     }
 
+    @SmallTest
     public void testUniqueToast() {
         setNotificationSwitch(transition(WIFI, CELLULAR));
         setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST);
@@ -142,6 +149,7 @@
         verifyNoNotifications();
     }
 
+    @SmallTest
     public void testMultipleNotifications() {
         setNotificationSwitch(transition(WIFI, CELLULAR));
         setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION);
@@ -160,6 +168,7 @@
         verifyNotification(wifi2, cell);
     }
 
+    @SmallTest
     public void testRateLimiting() throws InterruptedException {
         mMonitor = new TestableLingerMonitor(mCtx, mNotifier, HIGH_DAILY_LIMIT, LOW_RATE_LIMIT);
 
@@ -185,6 +194,7 @@
         verifyNoNotifications();
     }
 
+    @SmallTest
     public void testDailyLimiting() throws InterruptedException {
         mMonitor = new TestableLingerMonitor(mCtx, mNotifier, LOW_DAILY_LIMIT, HIGH_RATE_LIMIT);
 
@@ -211,6 +221,7 @@
         verifyNoNotifications();
     }
 
+    @SmallTest
     public void testUniqueNotification() {
         setNotificationSwitch(transition(WIFI, CELLULAR));
         setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION);
@@ -227,6 +238,7 @@
         verifyNotification(from, to);
     }
 
+    @SmallTest
     public void testIgnoreNeverValidatedNetworks() {
         setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST);
         setNotificationSwitch(transition(WIFI, CELLULAR));
@@ -238,6 +250,7 @@
         verifyNoNotifications();
     }
 
+    @SmallTest
     public void testIgnoreCurrentlyValidatedNetworks() {
         setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST);
         setNotificationSwitch(transition(WIFI, CELLULAR));
@@ -249,6 +262,7 @@
         verifyNoNotifications();
     }
 
+    @SmallTest
     public void testNoNotificationType() {
         setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST);
         setNotificationSwitch();
@@ -259,6 +273,7 @@
         verifyNoNotifications();
     }
 
+    @SmallTest
     public void testNoTransitionToNotify() {
         setNotificationType(LingerMonitor.NOTIFY_TYPE_NONE);
         setNotificationSwitch(transition(WIFI, CELLULAR));
@@ -269,6 +284,7 @@
         verifyNoNotifications();
     }
 
+    @SmallTest
     public void testDifferentTransitionToNotify() {
         setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST);
         setNotificationSwitch(transition(CELLULAR, WIFI));
diff --git a/tests/net/java/com/android/server/connectivity/MetricsTestUtil.java b/tests/net/java/com/android/server/connectivity/MetricsTestUtil.java
new file mode 100644
index 0000000..5064b9b
--- /dev/null
+++ b/tests/net/java/com/android/server/connectivity/MetricsTestUtil.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2016 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 android.net.ConnectivityMetricsEvent;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.function.Consumer;
+
+abstract public class MetricsTestUtil {
+    private MetricsTestUtil() {
+    }
+
+    static ConnectivityMetricsEvent ev(Parcelable p) {
+        ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent();
+        ev.timestamp = 1L;
+        ev.data = p;
+        return ev;
+    }
+
+    static ConnectivityMetricsEvent describeIpEvent(Consumer<Parcel>... fs) {
+        Parcel p = Parcel.obtain();
+        for (Consumer<Parcel> f : fs) {
+            f.accept(p);
+        }
+        p.setDataPosition(0);
+        return ev(p.readParcelable(ClassLoader.getSystemClassLoader()));
+    }
+
+    static Consumer<Parcel> aType(Class<?> c) {
+        return aString(c.getName());
+    }
+
+    static Consumer<Parcel> aBool(boolean b) {
+        return aByte((byte) (b ? 1 : 0));
+    }
+
+    static Consumer<Parcel> aByte(byte b) {
+        return (p) -> p.writeByte(b);
+    }
+
+    static Consumer<Parcel> anInt(int i) {
+        return (p) -> p.writeInt(i);
+    }
+
+    static Consumer<Parcel> aLong(long l) {
+        return (p) -> p.writeLong(l);
+    }
+
+    static Consumer<Parcel> aString(String s) {
+        return (p) -> p.writeString(s);
+    }
+
+    static Consumer<Parcel> aByteArray(byte... ary) {
+        return (p) -> p.writeByteArray(ary);
+    }
+
+    static Consumer<Parcel> anIntArray(int... ary) {
+        return (p) -> p.writeIntArray(ary);
+    }
+
+    static byte b(int i) {
+        return (byte) i;
+    }
+}
diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
new file mode 100644
index 0000000..f98ab3d
--- /dev/null
+++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2016, 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.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
+import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.support.test.runner.AndroidJUnit4;
+import android.system.OsConstants;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
+import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.DNSLookupBatch;
+import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
+import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetdEventListenerServiceTest {
+    private static final String EXAMPLE_IPV4 = "192.0.2.1";
+    private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1";
+
+    NetdEventListenerService mNetdEventListenerService;
+    ConnectivityManager mCm;
+
+    @Before
+    public void setUp() {
+        NetworkCapabilities ncWifi = new NetworkCapabilities();
+        NetworkCapabilities ncCell = new NetworkCapabilities();
+        ncWifi.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
+        ncCell.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+
+        mCm = mock(ConnectivityManager.class);
+        when(mCm.getNetworkCapabilities(new Network(100))).thenReturn(ncWifi);
+        when(mCm.getNetworkCapabilities(new Network(101))).thenReturn(ncCell);
+
+        mNetdEventListenerService = new NetdEventListenerService(mCm);
+    }
+
+    @Test
+    public void testDnsLogging() throws Exception {
+        asyncDump(100);
+
+        dnsEvent(100, EVENT_GETADDRINFO, 0, 3456);
+        dnsEvent(100, EVENT_GETADDRINFO, 0, 267);
+        dnsEvent(100, EVENT_GETHOSTBYNAME, 22, 1230);
+        dnsEvent(100, EVENT_GETADDRINFO, 3, 45);
+        dnsEvent(100, EVENT_GETADDRINFO, 1, 2111);
+        dnsEvent(100, EVENT_GETADDRINFO, 0, 450);
+        dnsEvent(100, EVENT_GETHOSTBYNAME, 200, 638);
+        dnsEvent(100, EVENT_GETHOSTBYNAME, 178, 1300);
+        dnsEvent(101, EVENT_GETADDRINFO, 0, 56);
+        dnsEvent(101, EVENT_GETADDRINFO, 0, 78);
+        dnsEvent(101, EVENT_GETADDRINFO, 0, 14);
+        dnsEvent(101, EVENT_GETHOSTBYNAME, 0, 56);
+        dnsEvent(101, EVENT_GETADDRINFO, 0, 78);
+        dnsEvent(101, EVENT_GETADDRINFO, 0, 14);
+
+        String got = flushStatistics();
+        String want = String.join("\n",
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 4",
+                "  network_id: 100",
+                "  time_ms: 0",
+                "  transports: 2",
+                "  dns_lookup_batch <",
+                "    event_types: 1",
+                "    event_types: 1",
+                "    event_types: 2",
+                "    event_types: 1",
+                "    event_types: 1",
+                "    event_types: 1",
+                "    event_types: 2",
+                "    event_types: 2",
+                "    latencies_ms: 3456",
+                "    latencies_ms: 267",
+                "    latencies_ms: 1230",
+                "    latencies_ms: 45",
+                "    latencies_ms: 2111",
+                "    latencies_ms: 450",
+                "    latencies_ms: 638",
+                "    latencies_ms: 1300",
+                "    return_codes: 0",
+                "    return_codes: 0",
+                "    return_codes: 22",
+                "    return_codes: 3",
+                "    return_codes: 1",
+                "    return_codes: 0",
+                "    return_codes: 200",
+                "    return_codes: 178",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 2",
+                "  network_id: 101",
+                "  time_ms: 0",
+                "  transports: 1",
+                "  dns_lookup_batch <",
+                "    event_types: 1",
+                "    event_types: 1",
+                "    event_types: 1",
+                "    event_types: 2",
+                "    event_types: 1",
+                "    event_types: 1",
+                "    latencies_ms: 56",
+                "    latencies_ms: 78",
+                "    latencies_ms: 14",
+                "    latencies_ms: 56",
+                "    latencies_ms: 78",
+                "    latencies_ms: 14",
+                "    return_codes: 0",
+                "    return_codes: 0",
+                "    return_codes: 0",
+                "    return_codes: 0",
+                "    return_codes: 0",
+                "    return_codes: 0",
+                "  >",
+                ">",
+                "version: 2\n");
+        assertEquals(want, got);
+    }
+
+    @Test
+    public void testConnectLogging() throws Exception {
+        asyncDump(100);
+
+        final int OK = 0;
+        Thread[] logActions = {
+            // ignored
+            connectEventAction(100, OsConstants.EALREADY, 0, EXAMPLE_IPV4),
+            connectEventAction(100, OsConstants.EALREADY, 0, EXAMPLE_IPV6),
+            connectEventAction(100, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV4),
+            connectEventAction(101, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6),
+            connectEventAction(101, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6),
+            // valid latencies
+            connectEventAction(100, OK, 110, EXAMPLE_IPV4),
+            connectEventAction(100, OK, 23, EXAMPLE_IPV4),
+            connectEventAction(100, OK, 45, EXAMPLE_IPV4),
+            connectEventAction(101, OK, 56, EXAMPLE_IPV4),
+            connectEventAction(101, OK, 523, EXAMPLE_IPV6),
+            connectEventAction(101, OK, 214, EXAMPLE_IPV6),
+            connectEventAction(101, OK, 67, EXAMPLE_IPV6),
+            // errors
+            connectEventAction(100, OsConstants.EPERM, 0, EXAMPLE_IPV4),
+            connectEventAction(101, OsConstants.EPERM, 0, EXAMPLE_IPV4),
+            connectEventAction(100, OsConstants.EAGAIN, 0, EXAMPLE_IPV4),
+            connectEventAction(100, OsConstants.EACCES, 0, EXAMPLE_IPV4),
+            connectEventAction(101, OsConstants.EACCES, 0, EXAMPLE_IPV4),
+            connectEventAction(101, OsConstants.EACCES, 0, EXAMPLE_IPV6),
+            connectEventAction(100, OsConstants.EADDRINUSE, 0, EXAMPLE_IPV4),
+            connectEventAction(101, OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV4),
+            connectEventAction(100, OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6),
+            connectEventAction(100, OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6),
+            connectEventAction(101, OsConstants.ECONNREFUSED, 0, EXAMPLE_IPV4),
+        };
+
+        for (Thread t : logActions) {
+            t.start();
+        }
+        for (Thread t : logActions) {
+            t.join();
+        }
+
+        String got = flushStatistics();
+        String want = String.join("\n",
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 4",
+                "  network_id: 100",
+                "  time_ms: 0",
+                "  transports: 2",
+                "  connect_statistics <",
+                "    connect_blocking_count: 3",
+                "    connect_count: 6",
+                "    errnos_counters <",
+                "      key: 1",
+                "      value: 1",
+                "    >",
+                "    errnos_counters <",
+                "      key: 11",
+                "      value: 1",
+                "    >",
+                "    errnos_counters <",
+                "      key: 13",
+                "      value: 1",
+                "    >",
+                "    errnos_counters <",
+                "      key: 98",
+                "      value: 1",
+                "    >",
+                "    errnos_counters <",
+                "      key: 110",
+                "      value: 2",
+                "    >",
+                "    ipv6_addr_count: 1",
+                "    latencies_ms: 23",
+                "    latencies_ms: 45",
+                "    latencies_ms: 110",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 2",
+                "  network_id: 101",
+                "  time_ms: 0",
+                "  transports: 1",
+                "  connect_statistics <",
+                "    connect_blocking_count: 4",
+                "    connect_count: 6",
+                "    errnos_counters <",
+                "      key: 1",
+                "      value: 1",
+                "    >",
+                "    errnos_counters <",
+                "      key: 13",
+                "      value: 2",
+                "    >",
+                "    errnos_counters <",
+                "      key: 110",
+                "      value: 1",
+                "    >",
+                "    errnos_counters <",
+                "      key: 111",
+                "      value: 1",
+                "    >",
+                "    ipv6_addr_count: 5",
+                "    latencies_ms: 56",
+                "    latencies_ms: 67",
+                "    latencies_ms: 214",
+                "    latencies_ms: 523",
+                "  >",
+                ">",
+                "version: 2\n");
+        assertEquals(want, got);
+    }
+
+    Thread connectEventAction(int netId, int error, int latencyMs, String ipAddr) {
+        return new Thread(() -> {
+            try {
+                mNetdEventListenerService.onConnectEvent(netId, error, latencyMs, ipAddr, 80, 1);
+            } catch (Exception e) {
+                fail(e.toString());
+            }
+        });
+    }
+
+    void dnsEvent(int netId, int type, int result, int latency) throws Exception {
+        mNetdEventListenerService.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
+    }
+
+    void asyncDump(long durationMs) throws Exception {
+        final long stop = System.currentTimeMillis() + durationMs;
+        final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null"));
+        new Thread(() -> {
+            while (System.currentTimeMillis() < stop) {
+                mNetdEventListenerService.dump(pw);
+            }
+        }).start();
+    }
+
+    // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
+    String flushStatistics() throws Exception {
+        IpConnectivityMetrics metricsService =
+                new IpConnectivityMetrics(mock(Context.class), (ctx) -> 2000);
+        metricsService.mNetdListener = mNetdEventListenerService;
+
+        StringWriter buffer = new StringWriter();
+        PrintWriter writer = new PrintWriter(buffer);
+        metricsService.impl.dump(null, writer, new String[]{"flush"});
+        byte[] bytes = Base64.decode(buffer.toString(), Base64.DEFAULT);
+        IpConnectivityLog log = IpConnectivityLog.parseFrom(bytes);
+        for (IpConnectivityEvent ev : log.events) {
+            if (ev.getConnectStatistics() == null) {
+                continue;
+            }
+            // Sort repeated fields of connect() events arriving in non-deterministic order.
+            Arrays.sort(ev.getConnectStatistics().latenciesMs);
+            Arrays.sort(ev.getConnectStatistics().errnosCounters,
+                    Comparator.comparingInt((p) -> p.key));
+        }
+        return log.toString();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
similarity index 96%
rename from services/tests/servicestests/src/com/android/server/connectivity/NetworkNotificationManagerTest.java
rename to tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index 21c2de7..f201bc7 100644
--- a/services/tests/servicestests/src/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -91,7 +91,7 @@
         final int NETWORK_ID_BASE = 100;
         List<NotificationType> types = Arrays.asList(NotificationType.values());
         List<Integer> ids = new ArrayList<>(types.size());
-        for (int i = 0; i < ids.size(); i++) {
+        for (int i = 0; i < types.size(); i++) {
             ids.add(NETWORK_ID_BASE + i);
         }
         Collections.shuffle(ids);
@@ -101,9 +101,10 @@
             mManager.showNotification(ids.get(i), types.get(i), mWifiNai, mCellNai, null, false);
         }
 
-        Collections.shuffle(ids);
+        List<Integer> idsToClear = new ArrayList<>(ids);
+        Collections.shuffle(idsToClear);
         for (int i = 0; i < ids.size(); i++) {
-            mManager.clearNotification(ids.get(i));
+            mManager.clearNotification(idsToClear.get(i));
         }
 
         for (int i = 0; i < ids.size(); i++) {
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
similarity index 77%
rename from services/tests/servicestests/src/com/android/server/connectivity/VpnTest.java
rename to tests/net/java/com/android/server/connectivity/VpnTest.java
index 5d8b843..b51b277 100644
--- a/services/tests/servicestests/src/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -25,9 +25,11 @@
 
 import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
+import android.app.NotificationManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
+import android.net.NetworkInfo.DetailedState;
 import android.net.UidRange;
 import android.os.INetworkManagementService;
 import android.os.Looper;
@@ -43,6 +45,8 @@
 import java.util.Map;
 import java.util.Set;
 
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -88,14 +92,18 @@
     @Mock private PackageManager mPackageManager;
     @Mock private INetworkManagementService mNetService;
     @Mock private AppOpsManager mAppOps;
+    @Mock private NotificationManager mNotificationManager;
 
     @Override
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         setMockedPackages(mPackages);
+        when(mContext.getPackageName()).thenReturn(Vpn.class.getPackage().getName());
         when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
         when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps);
+        when(mContext.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
+                .thenReturn(mNotificationManager);
         doNothing().when(mNetService).registerObserver(any());
     }
 
@@ -103,7 +111,7 @@
     public void testRestrictedProfilesAreAddedToVpn() {
         setMockedUsers(primaryUser, secondaryUser, restrictedProfileA, restrictedProfileB);
 
-        final Vpn vpn = new MockVpn(primaryUser.id);
+        final Vpn vpn = spyVpn(primaryUser.id);
         final Set<UidRange> ranges = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id,
                 null, null);
 
@@ -117,7 +125,7 @@
     public void testManagedProfilesAreNotAddedToVpn() {
         setMockedUsers(primaryUser, managedProfileA);
 
-        final Vpn vpn = new MockVpn(primaryUser.id);
+        final Vpn vpn = spyVpn(primaryUser.id);
         final Set<UidRange> ranges = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id,
                 null, null);
 
@@ -130,7 +138,7 @@
     public void testAddUserToVpnOnlyAddsOneUser() {
         setMockedUsers(primaryUser, restrictedProfileA, managedProfileA);
 
-        final Vpn vpn = new MockVpn(primaryUser.id);
+        final Vpn vpn = spyVpn(primaryUser.id);
         final Set<UidRange> ranges = new ArraySet<>();
         vpn.addUserToRanges(ranges, primaryUser.id, null, null);
 
@@ -141,7 +149,7 @@
 
     @SmallTest
     public void testUidWhiteAndBlacklist() throws Exception {
-        final Vpn vpn = new MockVpn(primaryUser.id);
+        final Vpn vpn = spyVpn(primaryUser.id);
         final UidRange user = UidRange.createForUser(primaryUser.id);
         final String[] packages = {PKGS[0], PKGS[1], PKGS[2]};
 
@@ -166,15 +174,15 @@
 
     @SmallTest
     public void testLockdownChangingPackage() throws Exception {
-        final MockVpn vpn = new MockVpn(primaryUser.id);
+        final Vpn vpn = spyVpn(primaryUser.id);
         final UidRange user = UidRange.createForUser(primaryUser.id);
 
         // Default state.
-        vpn.assertUnblocked(user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
+        assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
 
         // Set always-on without lockdown.
         assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false));
-        vpn.assertUnblocked(user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
+        assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
 
         // Set always-on with lockdown.
         assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true));
@@ -182,8 +190,8 @@
             new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
             new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
         }));
-        vpn.assertBlocked(user.start + PKG_UIDS[0], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
-        vpn.assertUnblocked(user.start + PKG_UIDS[1]);
+        assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
+        assertUnblocked(vpn, user.start + PKG_UIDS[1]);
 
         // Switch to another app.
         assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true));
@@ -195,13 +203,13 @@
             new UidRange(user.start, user.start + PKG_UIDS[3] - 1),
             new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
         }));
-        vpn.assertBlocked(user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
-        vpn.assertUnblocked(user.start + PKG_UIDS[3]);
+        assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
+        assertUnblocked(vpn, user.start + PKG_UIDS[3]);
     }
 
     @SmallTest
     public void testLockdownAddingAProfile() throws Exception {
-        final MockVpn vpn = new MockVpn(primaryUser.id);
+        final Vpn vpn = spyVpn(primaryUser.id);
         setMockedUsers(primaryUser);
 
         // Make a copy of the restricted profile, as we're going to mark it deleted halfway through.
@@ -220,7 +228,7 @@
         }));
 
         // Verify restricted user isn't affected at first.
-        vpn.assertUnblocked(profile.start + PKG_UIDS[0]);
+        assertUnblocked(vpn, profile.start + PKG_UIDS[0]);
 
         // Add the restricted user.
         setMockedUsers(primaryUser, tempProfile);
@@ -239,24 +247,53 @@
         }));
     }
 
+    @SmallTest
+    public void testNotificationShownForAlwaysOnApp() {
+        final Vpn vpn = spyVpn(primaryUser.id);
+        final InOrder order = inOrder(vpn);
+        setMockedUsers(primaryUser);
+
+        // Don't show a notification for regular disconnected states.
+        vpn.updateState(DetailedState.DISCONNECTED, TAG);
+        order.verify(vpn).updateAlwaysOnNotificationInternal(false);
+
+        // Start showing a notification for disconnected once always-on.
+        vpn.setAlwaysOnPackage(PKGS[0], false);
+        order.verify(vpn).updateAlwaysOnNotificationInternal(true);
+
+        // Stop showing the notification once connected.
+        vpn.updateState(DetailedState.CONNECTED, TAG);
+        order.verify(vpn).updateAlwaysOnNotificationInternal(false);
+
+        // Show the notification if we disconnect again.
+        vpn.updateState(DetailedState.DISCONNECTED, TAG);
+        order.verify(vpn).updateAlwaysOnNotificationInternal(true);
+
+        // Notification should be cleared after unsetting always-on package.
+        vpn.setAlwaysOnPackage(null, false);
+        order.verify(vpn).updateAlwaysOnNotificationInternal(false);
+    }
+
     /**
-     * A subclass of {@link Vpn} with some of the fields pre-mocked.
+     * Mock some methods of vpn object.
      */
-    private class MockVpn extends Vpn {
-        public MockVpn(@UserIdInt int userId) {
-            super(Looper.myLooper(), mContext, mNetService, userId);
-        }
+    private Vpn spyVpn(@UserIdInt int userId) {
+        final Vpn vpn = spy(new Vpn(Looper.myLooper(), mContext, mNetService, userId));
 
-        public void assertBlocked(int... uids) {
-            for (int uid : uids) {
-                assertTrue("Uid " + uid + " should be blocked", isBlockingUid(uid));
-            }
-        }
+        // Block calls to the NotificationManager or PendingIntent#getActivity.
+        doNothing().when(vpn).updateAlwaysOnNotificationInternal(anyBoolean());
+        return vpn;
+    }
 
-        public void assertUnblocked(int... uids) {
-            for (int uid : uids) {
-                assertFalse("Uid " + uid + " should not be blocked", isBlockingUid(uid));
-            }
+    private static void assertBlocked(Vpn vpn, int... uids) {
+        for (int uid : uids) {
+            assertTrue("Uid " + uid + " should be blocked", vpn.isBlockingUid(uid));
+        }
+    }
+
+    private static void assertUnblocked(Vpn vpn, int... uids) {
+        for (int uid : uids) {
+            assertFalse("Uid " + uid + " should not be blocked", vpn.isBlockingUid(uid));
         }
     }
 
diff --git a/tests/net/res/raw/history_v1 b/tests/net/res/raw/history_v1
new file mode 100644
index 0000000..de79491
--- /dev/null
+++ b/tests/net/res/raw/history_v1
Binary files differ
diff --git a/core/tests/coretests/res/raw/xt_qtaguid_iface_fmt_typical b/tests/net/res/raw/xt_qtaguid_iface_fmt_typical
similarity index 100%
rename from core/tests/coretests/res/raw/xt_qtaguid_iface_fmt_typical
rename to tests/net/res/raw/xt_qtaguid_iface_fmt_typical
diff --git a/core/tests/coretests/res/raw/xt_qtaguid_iface_typical b/tests/net/res/raw/xt_qtaguid_iface_typical
similarity index 100%
rename from core/tests/coretests/res/raw/xt_qtaguid_iface_typical
rename to tests/net/res/raw/xt_qtaguid_iface_typical
diff --git a/core/tests/coretests/res/raw/xt_qtaguid_typical b/tests/net/res/raw/xt_qtaguid_typical
similarity index 100%
rename from core/tests/coretests/res/raw/xt_qtaguid_typical
rename to tests/net/res/raw/xt_qtaguid_typical
diff --git a/tests/net/res/raw/xt_qtaguid_with_clat b/tests/net/res/raw/xt_qtaguid_with_clat
new file mode 100644
index 0000000..77e5c7b
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_with_clat
@@ -0,0 +1,43 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 v4-wlan0 0x0 0 0 256 5 196 4 256 5 0 0 0 0 196 4 0 0 0 0
+3 v4-wlan0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+4 v4-wlan0 0x0 1000 0 30312 25 1770 27 30236 24 76 1 0 0 1694 26 76 1 0 0
+5 v4-wlan0 0x0 1000 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+6 v4-wlan0 0x0 10060 0 9398432 6717 169412 4235 9398432 6717 0 0 0 0 169412 4235 0 0 0 0
+7 v4-wlan0 0x0 10060 1 1448660 1041 31192 753 1448660 1041 0 0 0 0 31192 753 0 0 0 0
+8 v4-wlan0 0x0 10102 0 9702 16 2870 23 9702 16 0 0 0 0 2870 23 0 0 0 0
+9 v4-wlan0 0x0 10102 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+10 wlan0 0x0 0 0 11058671 7892 312046 5113 11043898 7811 13117 61 1656 20 306544 5046 3230 38 2272 29
+11 wlan0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+12 wlan0 0x0 1000 0 6126 13 2013 16 5934 11 192 2 0 0 1821 14 192 2 0 0
+13 wlan0 0x0 1000 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+14 wlan0 0x0 10013 0 0 0 144 2 0 0 0 0 0 0 144 2 0 0 0 0
+15 wlan0 0x0 10013 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+16 wlan0 0x0 10018 0 5980263 4715 167667 1922 5972583 4709 0 0 7680 6 167667 1922 0 0 0 0
+17 wlan0 0x0 10018 1 43995 37 2766 27 43995 37 0 0 0 0 2766 27 0 0 0 0
+18 wlan0 0x0 10060 0 134356 133 8705 74 134356 133 0 0 0 0 8705 74 0 0 0 0
+19 wlan0 0x0 10060 1 294709 326 26448 256 294709 326 0 0 0 0 26448 256 0 0 0 0
+20 wlan0 0x0 10079 0 10926 13 1507 13 10926 13 0 0 0 0 1507 13 0 0 0 0
+21 wlan0 0x0 10079 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+22 wlan0 0x0 10102 0 25038 42 8245 57 25038 42 0 0 0 0 8245 57 0 0 0 0
+23 wlan0 0x0 10102 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+24 wlan0 0x0 10103 0 0 0 192 2 0 0 0 0 0 0 0 0 192 2 0 0
+25 wlan0 0x0 10103 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+26 wlan0 0x1000040700000000 10018 0 831 6 655 5 831 6 0 0 0 0 655 5 0 0 0 0
+27 wlan0 0x1000040700000000 10018 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+28 wlan0 0x1000040b00000000 10018 0 1714 8 1561 7 1714 8 0 0 0 0 1561 7 0 0 0 0
+29 wlan0 0x1000040b00000000 10018 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+30 wlan0 0x1000120300000000 10018 0 8243 11 2234 12 8243 11 0 0 0 0 2234 12 0 0 0 0
+31 wlan0 0x1000120300000000 10018 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+32 wlan0 0x1000180300000000 10018 0 56368 49 4790 39 56368 49 0 0 0 0 4790 39 0 0 0 0
+33 wlan0 0x1000180300000000 10018 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+34 wlan0 0x1000300000000000 10018 0 9488 17 18813 25 1808 11 0 0 7680 6 18813 25 0 0 0 0
+35 wlan0 0x1000300000000000 10018 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+36 wlan0 0x3000180400000000 10018 0 131262 103 7416 103 131262 103 0 0 0 0 7416 103 0 0 0 0
+37 wlan0 0x3000180400000000 10018 1 43995 37 2766 27 43995 37 0 0 0 0 2766 27 0 0 0 0
+38 wlan0 0xffffff0100000000 10018 0 5771986 4518 131190 1725 5771986 4518 0 0 0 0 131190 1725 0 0 0 0
+39 wlan0 0xffffff0100000000 10018 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+40 dummy0 0x0 0 0 0 0 168 3 0 0 0 0 0 0 0 0 0 0 168 3
+41 dummy0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+42 lo 0x0 0 0 1288 16 1288 16 0 0 532 8 756 8 0 0 532 8 756 8
+43 lo 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
diff --git a/tests/net/res/raw/xt_qtaguid_with_clat_100mb_download_after b/tests/net/res/raw/xt_qtaguid_with_clat_100mb_download_after
new file mode 100644
index 0000000..c78f84f
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_with_clat_100mb_download_after
@@ -0,0 +1,187 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 r_rmnet_data0 0x0 0 0 0 0 392 6 0 0 0 0 0 0 0 0 0 0 392 6
+3 r_rmnet_data0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+4 v4-wlan0 0x0 0 0 58952 2072 2888 65 264 6 0 0 58688 2066 132 3 0 0 2756 62
+5 v4-wlan0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+6 v4-wlan0 0x0 10034 0 6192 11 1445 11 6192 11 0 0 0 0 1445 11 0 0 0 0
+7 v4-wlan0 0x0 10034 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+8 v4-wlan0 0x0 10057 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+9 v4-wlan0 0x0 10057 1 728 7 392 7 0 0 728 7 0 0 0 0 392 7 0 0
+10 v4-wlan0 0x0 10106 0 2232 18 2232 18 0 0 2232 18 0 0 0 0 2232 18 0 0
+11 v4-wlan0 0x0 10106 1 432952718 314238 5442288 121260 432950238 314218 2480 20 0 0 5433900 121029 8388 231 0 0
+12 wlan0 0x0 0 0 440746376 329772 8524052 130894 439660007 315369 232001 1276 854368 13127 7871216 121284 108568 1325 544268 8285
+13 wlan0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+14 wlan0 0x0 1000 0 77113 272 56151 575 77113 272 0 0 0 0 19191 190 36960 385 0 0
+15 wlan0 0x0 1000 1 20227 80 8356 72 18539 74 1688 6 0 0 7562 66 794 6 0 0
+16 wlan0 0x0 10006 0 80755 92 9122 99 80755 92 0 0 0 0 9122 99 0 0 0 0
+17 wlan0 0x0 10006 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+18 wlan0 0x0 10015 0 4390 7 14824 252 4390 7 0 0 0 0 14824 252 0 0 0 0
+19 wlan0 0x0 10015 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+20 wlan0 0x0 10018 0 4928 11 1741 14 4928 11 0 0 0 0 1741 14 0 0 0 0
+21 wlan0 0x0 10018 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+22 wlan0 0x0 10020 0 21163552 34395 2351650 15326 21162947 34390 605 5 0 0 2351045 15321 605 5 0 0
+23 wlan0 0x0 10020 1 13835740 12938 1548795 6365 13833754 12920 1986 18 0 0 1546809 6347 1986 18 0 0
+24 wlan0 0x0 10023 0 13405 40 5042 44 13405 40 0 0 0 0 5042 44 0 0 0 0
+25 wlan0 0x0 10023 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+26 wlan0 0x0 10034 0 436394741 342648 6237981 80442 436394741 342648 0 0 0 0 6237981 80442 0 0 0 0
+27 wlan0 0x0 10034 1 64860872 51297 1335539 15546 64860872 51297 0 0 0 0 1335539 15546 0 0 0 0
+28 wlan0 0x0 10044 0 17614444 14774 521004 5694 17329882 14432 284562 342 0 0 419974 5408 101030 286 0 0
+29 wlan0 0x0 10044 1 17701 33 3100 28 17701 33 0 0 0 0 3100 28 0 0 0 0
+30 wlan0 0x0 10057 0 12312074 9339 436098 5450 12248060 9263 64014 76 0 0 414224 5388 21874 62 0 0
+31 wlan0 0x0 10057 1 1332953195 954797 31849632 457698 1331933207 953569 1019988 1228 0 0 31702284 456899 147348 799 0 0
+32 wlan0 0x0 10060 0 32972 200 433705 380 32972 200 0 0 0 0 433705 380 0 0 0 0
+33 wlan0 0x0 10060 1 32106 66 37789 87 32106 66 0 0 0 0 37789 87 0 0 0 0
+34 wlan0 0x0 10061 0 7675 23 2509 22 7675 23 0 0 0 0 2509 22 0 0 0 0
+35 wlan0 0x0 10061 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+36 wlan0 0x0 10074 0 38355 82 10447 97 38355 82 0 0 0 0 10447 97 0 0 0 0
+37 wlan0 0x0 10074 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+38 wlan0 0x0 10078 0 49013 79 7167 69 49013 79 0 0 0 0 7167 69 0 0 0 0
+39 wlan0 0x0 10078 1 5872 8 1236 10 5872 8 0 0 0 0 1236 10 0 0 0 0
+40 wlan0 0x0 10082 0 8301 13 1981 15 8301 13 0 0 0 0 1981 15 0 0 0 0
+41 wlan0 0x0 10082 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+42 wlan0 0x0 10086 0 7001 14 1579 15 7001 14 0 0 0 0 1579 15 0 0 0 0
+43 wlan0 0x0 10086 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+44 wlan0 0x0 10090 0 24327795 20224 920502 14661 24327795 20224 0 0 0 0 920502 14661 0 0 0 0
+45 wlan0 0x0 10090 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+46 wlan0 0x0 10092 0 36849 78 12449 81 36849 78 0 0 0 0 12449 81 0 0 0 0
+47 wlan0 0x0 10092 1 60 1 103 1 60 1 0 0 0 0 103 1 0 0 0 0
+48 wlan0 0x0 10095 0 131962 223 37069 241 131962 223 0 0 0 0 37069 241 0 0 0 0
+49 wlan0 0x0 10095 1 12949 21 3930 21 12949 21 0 0 0 0 3930 21 0 0 0 0
+50 wlan0 0x0 10106 0 30899554 22679 632476 12296 30895334 22645 4220 34 0 0 628256 12262 4220 34 0 0
+51 wlan0 0x0 10106 1 88923475 64963 1606962 35612 88917201 64886 3586 29 2688 48 1602032 35535 4930 77 0 0
+52 wlan0 0x40700000000 10020 0 705732 10589 404428 5504 705732 10589 0 0 0 0 404428 5504 0 0 0 0
+53 wlan0 0x40700000000 10020 1 2376 36 1296 18 2376 36 0 0 0 0 1296 18 0 0 0 0
+54 wlan0 0x40800000000 10020 0 34624 146 122525 160 34624 146 0 0 0 0 122525 160 0 0 0 0
+55 wlan0 0x40800000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+56 wlan0 0x40b00000000 10020 0 22411 85 7364 57 22411 85 0 0 0 0 7364 57 0 0 0 0
+57 wlan0 0x40b00000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+58 wlan0 0x120300000000 10020 0 76641 241 32783 169 76641 241 0 0 0 0 32783 169 0 0 0 0
+59 wlan0 0x120300000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+60 wlan0 0x130100000000 10020 0 73101 287 23236 203 73101 287 0 0 0 0 23236 203 0 0 0 0
+61 wlan0 0x130100000000 10020 1 264 4 144 2 264 4 0 0 0 0 144 2 0 0 0 0
+62 wlan0 0x180300000000 10020 0 330648 399 24736 232 330648 399 0 0 0 0 24736 232 0 0 0 0
+63 wlan0 0x180300000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+64 wlan0 0x180400000000 10020 0 21865 59 5022 42 21865 59 0 0 0 0 5022 42 0 0 0 0
+65 wlan0 0x180400000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+66 wlan0 0x300000000000 10020 0 15984 65 26927 57 15984 65 0 0 0 0 26927 57 0 0 0 0
+67 wlan0 0x300000000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+68 wlan0 0x1065fff00000000 10020 0 131871 599 93783 445 131871 599 0 0 0 0 93783 445 0 0 0 0
+69 wlan0 0x1065fff00000000 10020 1 264 4 144 2 264 4 0 0 0 0 144 2 0 0 0 0
+70 wlan0 0x1b24f4600000000 10034 0 15445 42 23329 45 15445 42 0 0 0 0 23329 45 0 0 0 0
+71 wlan0 0x1b24f4600000000 10034 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+72 wlan0 0x1000010000000000 10020 0 5542 9 1364 10 5542 9 0 0 0 0 1364 10 0 0 0 0
+73 wlan0 0x1000010000000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+74 wlan0 0x1000040100000000 10020 0 47196 184 213319 257 47196 184 0 0 0 0 213319 257 0 0 0 0
+75 wlan0 0x1000040100000000 10020 1 60 1 103 1 60 1 0 0 0 0 103 1 0 0 0 0
+76 wlan0 0x1000040700000000 10020 0 11599 50 10786 47 11599 50 0 0 0 0 10786 47 0 0 0 0
+77 wlan0 0x1000040700000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+78 wlan0 0x1000040800000000 10020 0 21902 145 174139 166 21902 145 0 0 0 0 174139 166 0 0 0 0
+79 wlan0 0x1000040800000000 10020 1 8568 88 105743 90 8568 88 0 0 0 0 105743 90 0 0 0 0
+80 wlan0 0x1000100300000000 10020 0 55213 118 194551 199 55213 118 0 0 0 0 194551 199 0 0 0 0
+81 wlan0 0x1000100300000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+82 wlan0 0x1000120300000000 10020 0 50826 74 21153 70 50826 74 0 0 0 0 21153 70 0 0 0 0
+83 wlan0 0x1000120300000000 10020 1 72 1 175 2 72 1 0 0 0 0 175 2 0 0 0 0
+84 wlan0 0x1000180300000000 10020 0 744198 657 65437 592 744198 657 0 0 0 0 65437 592 0 0 0 0
+85 wlan0 0x1000180300000000 10020 1 144719 132 10989 108 144719 132 0 0 0 0 10989 108 0 0 0 0
+86 wlan0 0x1000180600000000 10020 0 4599 8 1928 10 4599 8 0 0 0 0 1928 10 0 0 0 0
+87 wlan0 0x1000180600000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+88 wlan0 0x1000250000000000 10020 0 57740 98 13076 88 57740 98 0 0 0 0 13076 88 0 0 0 0
+89 wlan0 0x1000250000000000 10020 1 328 3 414 4 207 2 121 1 0 0 293 3 121 1 0 0
+90 wlan0 0x1000300000000000 10020 0 7675 30 31331 32 7675 30 0 0 0 0 31331 32 0 0 0 0
+91 wlan0 0x1000300000000000 10020 1 30173 97 101335 100 30173 97 0 0 0 0 101335 100 0 0 0 0
+92 wlan0 0x1000310200000000 10020 0 1681 9 2194 9 1681 9 0 0 0 0 2194 9 0 0 0 0
+93 wlan0 0x1000310200000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+94 wlan0 0x1000360000000000 10020 0 5606 20 2831 20 5606 20 0 0 0 0 2831 20 0 0 0 0
+95 wlan0 0x1000360000000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+96 wlan0 0x11065fff00000000 10020 0 18363 91 83367 104 18363 91 0 0 0 0 83367 104 0 0 0 0
+97 wlan0 0x11065fff00000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+98 wlan0 0x3000009600000000 10020 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+99 wlan0 0x3000009600000000 10020 1 6163 18 2424 18 6163 18 0 0 0 0 2424 18 0 0 0 0
+100 wlan0 0x3000009800000000 10020 0 23337 46 8723 39 23337 46 0 0 0 0 8723 39 0 0 0 0
+101 wlan0 0x3000009800000000 10020 1 33744 93 72437 89 33744 93 0 0 0 0 72437 89 0 0 0 0
+102 wlan0 0x3000020000000000 10020 0 4124 11 8969 19 4124 11 0 0 0 0 8969 19 0 0 0 0
+103 wlan0 0x3000020000000000 10020 1 5993 11 3815 14 5993 11 0 0 0 0 3815 14 0 0 0 0
+104 wlan0 0x3000040100000000 10020 0 113809 342 135666 308 113809 342 0 0 0 0 135666 308 0 0 0 0
+105 wlan0 0x3000040100000000 10020 1 142508 642 500579 637 142508 642 0 0 0 0 500579 637 0 0 0 0
+106 wlan0 0x3000040700000000 10020 0 365815 5119 213340 2733 365815 5119 0 0 0 0 213340 2733 0 0 0 0
+107 wlan0 0x3000040700000000 10020 1 30747 130 18408 100 30747 130 0 0 0 0 18408 100 0 0 0 0
+108 wlan0 0x3000040800000000 10020 0 34672 112 68623 92 34672 112 0 0 0 0 68623 92 0 0 0 0
+109 wlan0 0x3000040800000000 10020 1 78443 199 140944 192 78443 199 0 0 0 0 140944 192 0 0 0 0
+110 wlan0 0x3000040b00000000 10020 0 14949 33 4017 26 14949 33 0 0 0 0 4017 26 0 0 0 0
+111 wlan0 0x3000040b00000000 10020 1 996 15 576 8 996 15 0 0 0 0 576 8 0 0 0 0
+112 wlan0 0x3000090000000000 10020 0 11826 67 7309 52 11826 67 0 0 0 0 7309 52 0 0 0 0
+113 wlan0 0x3000090000000000 10020 1 24805 41 4785 41 24805 41 0 0 0 0 4785 41 0 0 0 0
+114 wlan0 0x3000100300000000 10020 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+115 wlan0 0x3000100300000000 10020 1 3112 10 1628 10 3112 10 0 0 0 0 1628 10 0 0 0 0
+116 wlan0 0x3000120300000000 10020 0 38249 107 20374 85 38249 107 0 0 0 0 20374 85 0 0 0 0
+117 wlan0 0x3000120300000000 10020 1 122581 174 36792 143 122581 174 0 0 0 0 36792 143 0 0 0 0
+118 wlan0 0x3000130100000000 10020 0 2700 41 1524 21 2700 41 0 0 0 0 1524 21 0 0 0 0
+119 wlan0 0x3000130100000000 10020 1 22515 59 8366 52 22515 59 0 0 0 0 8366 52 0 0 0 0
+120 wlan0 0x3000180200000000 10020 0 6411 18 14511 20 6411 18 0 0 0 0 14511 20 0 0 0 0
+121 wlan0 0x3000180200000000 10020 1 336 5 319 4 336 5 0 0 0 0 319 4 0 0 0 0
+122 wlan0 0x3000180300000000 10020 0 129301 136 17622 97 129301 136 0 0 0 0 17622 97 0 0 0 0
+123 wlan0 0x3000180300000000 10020 1 464787 429 41703 336 464787 429 0 0 0 0 41703 336 0 0 0 0
+124 wlan0 0x3000180400000000 10020 0 11014 39 2787 25 11014 39 0 0 0 0 2787 25 0 0 0 0
+125 wlan0 0x3000180400000000 10020 1 144040 139 7540 80 144040 139 0 0 0 0 7540 80 0 0 0 0
+126 wlan0 0x3000210100000000 10020 0 10278 44 4579 33 10278 44 0 0 0 0 4579 33 0 0 0 0
+127 wlan0 0x3000210100000000 10020 1 31151 73 14159 47 31151 73 0 0 0 0 14159 47 0 0 0 0
+128 wlan0 0x3000250000000000 10020 0 132 2 72 1 132 2 0 0 0 0 72 1 0 0 0 0
+129 wlan0 0x3000250000000000 10020 1 76614 143 17711 130 76080 137 534 6 0 0 17177 124 534 6 0 0
+130 wlan0 0x3000260100000000 10020 0 9426 26 3535 20 9426 26 0 0 0 0 3535 20 0 0 0 0
+131 wlan0 0x3000260100000000 10020 1 468 7 288 4 468 7 0 0 0 0 288 4 0 0 0 0
+132 wlan0 0x3000300000000000 10020 0 7241 29 12055 26 7241 29 0 0 0 0 12055 26 0 0 0 0
+133 wlan0 0x3000300000000000 10020 1 3273 23 11232 21 3273 23 0 0 0 0 11232 21 0 0 0 0
+134 wlan0 0x3000310000000000 10020 0 132 2 72 1 132 2 0 0 0 0 72 1 0 0 0 0
+135 wlan0 0x3000310000000000 10020 1 53425 64 8721 62 53425 64 0 0 0 0 8721 62 0 0 0 0
+136 wlan0 0x3000310500000000 10020 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+137 wlan0 0x3000310500000000 10020 1 9929 16 3879 18 9929 16 0 0 0 0 3879 18 0 0 0 0
+138 wlan0 0x3000320100000000 10020 0 6844 14 3745 13 6844 14 0 0 0 0 3745 13 0 0 0 0
+139 wlan0 0x3000320100000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+140 wlan0 0x3000360000000000 10020 0 8855 43 4749 31 8855 43 0 0 0 0 4749 31 0 0 0 0
+141 wlan0 0x3000360000000000 10020 1 5597 19 2456 19 5597 19 0 0 0 0 2456 19 0 0 0 0
+142 wlan0 0x3010000000000000 10090 0 605140 527 38435 429 605140 527 0 0 0 0 38435 429 0 0 0 0
+143 wlan0 0x3010000000000000 10090 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+144 wlan0 0x31065fff00000000 10020 0 22011 67 29665 64 22011 67 0 0 0 0 29665 64 0 0 0 0
+145 wlan0 0x31065fff00000000 10020 1 10695 34 18347 35 10695 34 0 0 0 0 18347 35 0 0 0 0
+146 wlan0 0x32e544f900000000 10034 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+147 wlan0 0x32e544f900000000 10034 1 40143 54 7299 61 40143 54 0 0 0 0 7299 61 0 0 0 0
+148 wlan0 0x58872a4400000000 10018 0 4928 11 1669 13 4928 11 0 0 0 0 1669 13 0 0 0 0
+149 wlan0 0x58872a4400000000 10018 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+150 wlan0 0x5caeaa7b00000000 10034 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+151 wlan0 0x5caeaa7b00000000 10034 1 74971 73 7103 75 74971 73 0 0 0 0 7103 75 0 0 0 0
+152 wlan0 0x9e00923800000000 10034 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+153 wlan0 0x9e00923800000000 10034 1 72385 98 13072 110 72385 98 0 0 0 0 13072 110 0 0 0 0
+154 wlan0 0xb972bdd400000000 10034 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+155 wlan0 0xb972bdd400000000 10034 1 15282 24 3034 27 15282 24 0 0 0 0 3034 27 0 0 0 0
+156 wlan0 0xc7c9f7ba00000000 10034 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+157 wlan0 0xc7c9f7ba00000000 10034 1 194915 185 13316 138 194915 185 0 0 0 0 13316 138 0 0 0 0
+158 wlan0 0xc9395b2600000000 10034 0 6991 13 6215 14 6991 13 0 0 0 0 6215 14 0 0 0 0
+159 wlan0 0xc9395b2600000000 10034 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+160 wlan0 0xdaddf21100000000 10034 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+161 wlan0 0xdaddf21100000000 10034 1 928676 849 81570 799 928676 849 0 0 0 0 81570 799 0 0 0 0
+162 wlan0 0xe8d195d100000000 10020 0 516 8 288 4 516 8 0 0 0 0 288 4 0 0 0 0
+163 wlan0 0xe8d195d100000000 10020 1 5905 15 2622 15 5905 15 0 0 0 0 2622 15 0 0 0 0
+164 wlan0 0xe8d195d100000000 10034 0 236640 524 312523 555 236640 524 0 0 0 0 312523 555 0 0 0 0
+165 wlan0 0xe8d195d100000000 10034 1 319028 539 188776 553 319028 539 0 0 0 0 188776 553 0 0 0 0
+166 wlan0 0xffffff0100000000 10006 0 80755 92 9122 99 80755 92 0 0 0 0 9122 99 0 0 0 0
+167 wlan0 0xffffff0100000000 10006 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+168 wlan0 0xffffff0100000000 10020 0 17874405 14068 223987 3065 17874405 14068 0 0 0 0 223987 3065 0 0 0 0
+169 wlan0 0xffffff0100000000 10020 1 11011258 8672 177693 2407 11011258 8672 0 0 0 0 177693 2407 0 0 0 0
+170 wlan0 0xffffff0100000000 10034 0 436062595 341880 5843990 79630 436062595 341880 0 0 0 0 5843990 79630 0 0 0 0
+171 wlan0 0xffffff0100000000 10034 1 63201220 49447 1005882 13713 63201220 49447 0 0 0 0 1005882 13713 0 0 0 0
+172 wlan0 0xffffff0100000000 10044 0 17159287 13702 356212 4778 17159287 13702 0 0 0 0 356212 4778 0 0 0 0
+173 wlan0 0xffffff0100000000 10044 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+174 wlan0 0xffffff0100000000 10078 0 10439 17 1665 15 10439 17 0 0 0 0 1665 15 0 0 0 0
+175 wlan0 0xffffff0100000000 10078 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+176 wlan0 0xffffff0100000000 10090 0 23722655 19697 881995 14231 23722655 19697 0 0 0 0 881995 14231 0 0 0 0
+177 wlan0 0xffffff0100000000 10090 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+178 wlan0 0xffffff0500000000 1000 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+179 wlan0 0xffffff0500000000 1000 1 1592 5 314 1 0 0 1592 5 0 0 0 0 314 1 0 0
+180 wlan0 0xffffff0600000000 1000 0 0 0 36960 385 0 0 0 0 0 0 0 0 36960 385 0 0
+181 wlan0 0xffffff0600000000 1000 1 96 1 480 5 0 0 96 1 0 0 0 0 480 5 0 0
+182 wlan0 0xffffff0700000000 1000 0 38732 229 16567 163 38732 229 0 0 0 0 16567 163 0 0 0 0
+183 wlan0 0xffffff0700000000 1000 1 18539 74 7562 66 18539 74 0 0 0 0 7562 66 0 0 0 0
+184 wlan0 0xffffff0900000000 1000 0 38381 43 2624 27 38381 43 0 0 0 0 2624 27 0 0 0 0
+185 wlan0 0xffffff0900000000 1000 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+186 dummy0 0x0 0 0 0 0 168 3 0 0 0 0 0 0 0 0 0 0 168 3
+187 dummy0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
diff --git a/tests/net/res/raw/xt_qtaguid_with_clat_100mb_download_before b/tests/net/res/raw/xt_qtaguid_with_clat_100mb_download_before
new file mode 100644
index 0000000..d035387
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_with_clat_100mb_download_before
@@ -0,0 +1,185 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 r_rmnet_data0 0x0 0 0 0 0 392 6 0 0 0 0 0 0 0 0 0 0 392 6
+3 r_rmnet_data0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+4 v4-wlan0 0x0 0 0 58848 2070 2836 64 160 4 0 0 58688 2066 80 2 0 0 2756 62
+5 v4-wlan0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+6 v4-wlan0 0x0 10034 0 6192 11 1445 11 6192 11 0 0 0 0 1445 11 0 0 0 0
+7 v4-wlan0 0x0 10034 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+8 v4-wlan0 0x0 10057 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+9 v4-wlan0 0x0 10057 1 728 7 392 7 0 0 728 7 0 0 0 0 392 7 0 0
+10 v4-wlan0 0x0 10106 0 1488 12 1488 12 0 0 1488 12 0 0 0 0 1488 12 0 0
+11 v4-wlan0 0x0 10106 1 323981189 235142 3509032 84542 323979453 235128 1736 14 0 0 3502676 84363 6356 179 0 0
+12 wlan0 0x0 0 0 330187296 250652 5855801 94173 329106990 236273 226202 1255 854104 13124 5208040 84634 103637 1256 544124 8283
+13 wlan0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+14 wlan0 0x0 1000 0 77113 272 56151 575 77113 272 0 0 0 0 19191 190 36960 385 0 0
+15 wlan0 0x0 1000 1 20227 80 8356 72 18539 74 1688 6 0 0 7562 66 794 6 0 0
+16 wlan0 0x0 10006 0 80755 92 9122 99 80755 92 0 0 0 0 9122 99 0 0 0 0
+17 wlan0 0x0 10006 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+18 wlan0 0x0 10015 0 4390 7 14824 252 4390 7 0 0 0 0 14824 252 0 0 0 0
+19 wlan0 0x0 10015 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+20 wlan0 0x0 10018 0 4928 11 1741 14 4928 11 0 0 0 0 1741 14 0 0 0 0
+21 wlan0 0x0 10018 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+22 wlan0 0x0 10020 0 21141412 34316 2329881 15262 21140807 34311 605 5 0 0 2329276 15257 605 5 0 0
+23 wlan0 0x0 10020 1 13835740 12938 1548555 6362 13833754 12920 1986 18 0 0 1546569 6344 1986 18 0 0
+24 wlan0 0x0 10023 0 13405 40 5042 44 13405 40 0 0 0 0 5042 44 0 0 0 0
+25 wlan0 0x0 10023 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+26 wlan0 0x0 10034 0 436394741 342648 6237981 80442 436394741 342648 0 0 0 0 6237981 80442 0 0 0 0
+27 wlan0 0x0 10034 1 64860872 51297 1335539 15546 64860872 51297 0 0 0 0 1335539 15546 0 0 0 0
+28 wlan0 0x0 10044 0 17614444 14774 521004 5694 17329882 14432 284562 342 0 0 419974 5408 101030 286 0 0
+29 wlan0 0x0 10044 1 17701 33 3100 28 17701 33 0 0 0 0 3100 28 0 0 0 0
+30 wlan0 0x0 10057 0 12311735 9335 435954 5448 12247721 9259 64014 76 0 0 414080 5386 21874 62 0 0
+31 wlan0 0x0 10057 1 1332953195 954797 31849632 457698 1331933207 953569 1019988 1228 0 0 31702284 456899 147348 799 0 0
+32 wlan0 0x0 10060 0 32972 200 433705 380 32972 200 0 0 0 0 433705 380 0 0 0 0
+33 wlan0 0x0 10060 1 32106 66 37789 87 32106 66 0 0 0 0 37789 87 0 0 0 0
+34 wlan0 0x0 10061 0 7675 23 2509 22 7675 23 0 0 0 0 2509 22 0 0 0 0
+35 wlan0 0x0 10061 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+36 wlan0 0x0 10074 0 38355 82 10447 97 38355 82 0 0 0 0 10447 97 0 0 0 0
+37 wlan0 0x0 10074 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+38 wlan0 0x0 10078 0 49013 79 7167 69 49013 79 0 0 0 0 7167 69 0 0 0 0
+39 wlan0 0x0 10078 1 5872 8 1236 10 5872 8 0 0 0 0 1236 10 0 0 0 0
+40 wlan0 0x0 10082 0 8301 13 1981 15 8301 13 0 0 0 0 1981 15 0 0 0 0
+41 wlan0 0x0 10082 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+42 wlan0 0x0 10086 0 7001 14 1579 15 7001 14 0 0 0 0 1579 15 0 0 0 0
+43 wlan0 0x0 10086 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+44 wlan0 0x0 10090 0 24327795 20224 920502 14661 24327795 20224 0 0 0 0 920502 14661 0 0 0 0
+45 wlan0 0x0 10090 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+46 wlan0 0x0 10092 0 36849 78 12449 81 36849 78 0 0 0 0 12449 81 0 0 0 0
+47 wlan0 0x0 10092 1 60 1 103 1 60 1 0 0 0 0 103 1 0 0 0 0
+48 wlan0 0x0 10095 0 131962 223 37069 241 131962 223 0 0 0 0 37069 241 0 0 0 0
+49 wlan0 0x0 10095 1 12949 21 3930 21 12949 21 0 0 0 0 3930 21 0 0 0 0
+50 wlan0 0x0 10106 0 30899554 22679 632476 12296 30895334 22645 4220 34 0 0 628256 12262 4220 34 0 0
+51 wlan0 0x0 10106 1 88922349 64952 1605126 35599 88916075 64875 3586 29 2688 48 1600196 35522 4930 77 0 0
+52 wlan0 0x40700000000 10020 0 705732 10589 404428 5504 705732 10589 0 0 0 0 404428 5504 0 0 0 0
+53 wlan0 0x40700000000 10020 1 2376 36 1296 18 2376 36 0 0 0 0 1296 18 0 0 0 0
+54 wlan0 0x40800000000 10020 0 34624 146 122525 160 34624 146 0 0 0 0 122525 160 0 0 0 0
+55 wlan0 0x40800000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+56 wlan0 0x40b00000000 10020 0 22411 85 7364 57 22411 85 0 0 0 0 7364 57 0 0 0 0
+57 wlan0 0x40b00000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+58 wlan0 0x120300000000 10020 0 76641 241 32783 169 76641 241 0 0 0 0 32783 169 0 0 0 0
+59 wlan0 0x120300000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+60 wlan0 0x130100000000 10020 0 73101 287 23236 203 73101 287 0 0 0 0 23236 203 0 0 0 0
+61 wlan0 0x130100000000 10020 1 264 4 144 2 264 4 0 0 0 0 144 2 0 0 0 0
+62 wlan0 0x180300000000 10020 0 330648 399 24736 232 330648 399 0 0 0 0 24736 232 0 0 0 0
+63 wlan0 0x180300000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+64 wlan0 0x180400000000 10020 0 21865 59 5022 42 21865 59 0 0 0 0 5022 42 0 0 0 0
+65 wlan0 0x180400000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+66 wlan0 0x300000000000 10020 0 15984 65 26927 57 15984 65 0 0 0 0 26927 57 0 0 0 0
+67 wlan0 0x300000000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+68 wlan0 0x1065fff00000000 10020 0 131871 599 93783 445 131871 599 0 0 0 0 93783 445 0 0 0 0
+69 wlan0 0x1065fff00000000 10020 1 264 4 144 2 264 4 0 0 0 0 144 2 0 0 0 0
+70 wlan0 0x1b24f4600000000 10034 0 15445 42 23329 45 15445 42 0 0 0 0 23329 45 0 0 0 0
+71 wlan0 0x1b24f4600000000 10034 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+72 wlan0 0x1000010000000000 10020 0 5542 9 1364 10 5542 9 0 0 0 0 1364 10 0 0 0 0
+73 wlan0 0x1000010000000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+74 wlan0 0x1000040100000000 10020 0 47196 184 213319 257 47196 184 0 0 0 0 213319 257 0 0 0 0
+75 wlan0 0x1000040100000000 10020 1 60 1 103 1 60 1 0 0 0 0 103 1 0 0 0 0
+76 wlan0 0x1000040700000000 10020 0 11599 50 10786 47 11599 50 0 0 0 0 10786 47 0 0 0 0
+77 wlan0 0x1000040700000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+78 wlan0 0x1000040800000000 10020 0 21902 145 174139 166 21902 145 0 0 0 0 174139 166 0 0 0 0
+79 wlan0 0x1000040800000000 10020 1 8568 88 105743 90 8568 88 0 0 0 0 105743 90 0 0 0 0
+80 wlan0 0x1000100300000000 10020 0 55213 118 194551 199 55213 118 0 0 0 0 194551 199 0 0 0 0
+81 wlan0 0x1000100300000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+82 wlan0 0x1000120300000000 10020 0 50826 74 21153 70 50826 74 0 0 0 0 21153 70 0 0 0 0
+83 wlan0 0x1000120300000000 10020 1 72 1 175 2 72 1 0 0 0 0 175 2 0 0 0 0
+84 wlan0 0x1000180300000000 10020 0 744198 657 65437 592 744198 657 0 0 0 0 65437 592 0 0 0 0
+85 wlan0 0x1000180300000000 10020 1 144719 132 10989 108 144719 132 0 0 0 0 10989 108 0 0 0 0
+86 wlan0 0x1000180600000000 10020 0 4599 8 1928 10 4599 8 0 0 0 0 1928 10 0 0 0 0
+87 wlan0 0x1000180600000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+88 wlan0 0x1000250000000000 10020 0 57740 98 13076 88 57740 98 0 0 0 0 13076 88 0 0 0 0
+89 wlan0 0x1000250000000000 10020 1 328 3 414 4 207 2 121 1 0 0 293 3 121 1 0 0
+90 wlan0 0x1000300000000000 10020 0 7675 30 31331 32 7675 30 0 0 0 0 31331 32 0 0 0 0
+91 wlan0 0x1000300000000000 10020 1 30173 97 101335 100 30173 97 0 0 0 0 101335 100 0 0 0 0
+92 wlan0 0x1000310200000000 10020 0 1681 9 2194 9 1681 9 0 0 0 0 2194 9 0 0 0 0
+93 wlan0 0x1000310200000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+94 wlan0 0x1000360000000000 10020 0 5606 20 2831 20 5606 20 0 0 0 0 2831 20 0 0 0 0
+95 wlan0 0x1000360000000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+96 wlan0 0x11065fff00000000 10020 0 18363 91 83367 104 18363 91 0 0 0 0 83367 104 0 0 0 0
+97 wlan0 0x11065fff00000000 10020 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+98 wlan0 0x3000009600000000 10020 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+99 wlan0 0x3000009600000000 10020 1 6163 18 2424 18 6163 18 0 0 0 0 2424 18 0 0 0 0
+100 wlan0 0x3000009800000000 10020 0 23337 46 8723 39 23337 46 0 0 0 0 8723 39 0 0 0 0
+101 wlan0 0x3000009800000000 10020 1 33744 93 72437 89 33744 93 0 0 0 0 72437 89 0 0 0 0
+102 wlan0 0x3000020000000000 10020 0 4124 11 8969 19 4124 11 0 0 0 0 8969 19 0 0 0 0
+103 wlan0 0x3000020000000000 10020 1 5993 11 3815 14 5993 11 0 0 0 0 3815 14 0 0 0 0
+104 wlan0 0x3000040100000000 10020 0 106718 322 121557 287 106718 322 0 0 0 0 121557 287 0 0 0 0
+105 wlan0 0x3000040100000000 10020 1 142508 642 500579 637 142508 642 0 0 0 0 500579 637 0 0 0 0
+106 wlan0 0x3000040700000000 10020 0 365419 5113 213124 2730 365419 5113 0 0 0 0 213124 2730 0 0 0 0
+107 wlan0 0x3000040700000000 10020 1 30747 130 18408 100 30747 130 0 0 0 0 18408 100 0 0 0 0
+108 wlan0 0x3000040800000000 10020 0 34672 112 68623 92 34672 112 0 0 0 0 68623 92 0 0 0 0
+109 wlan0 0x3000040800000000 10020 1 78443 199 140944 192 78443 199 0 0 0 0 140944 192 0 0 0 0
+110 wlan0 0x3000040b00000000 10020 0 14949 33 4017 26 14949 33 0 0 0 0 4017 26 0 0 0 0
+111 wlan0 0x3000040b00000000 10020 1 996 15 576 8 996 15 0 0 0 0 576 8 0 0 0 0
+112 wlan0 0x3000090000000000 10020 0 4017 28 3610 25 4017 28 0 0 0 0 3610 25 0 0 0 0
+113 wlan0 0x3000090000000000 10020 1 24805 41 4545 38 24805 41 0 0 0 0 4545 38 0 0 0 0
+114 wlan0 0x3000100300000000 10020 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+115 wlan0 0x3000100300000000 10020 1 3112 10 1628 10 3112 10 0 0 0 0 1628 10 0 0 0 0
+116 wlan0 0x3000120300000000 10020 0 38249 107 20374 85 38249 107 0 0 0 0 20374 85 0 0 0 0
+117 wlan0 0x3000120300000000 10020 1 122581 174 36792 143 122581 174 0 0 0 0 36792 143 0 0 0 0
+118 wlan0 0x3000130100000000 10020 0 2700 41 1524 21 2700 41 0 0 0 0 1524 21 0 0 0 0
+119 wlan0 0x3000130100000000 10020 1 22515 59 8366 52 22515 59 0 0 0 0 8366 52 0 0 0 0
+120 wlan0 0x3000180200000000 10020 0 6411 18 14511 20 6411 18 0 0 0 0 14511 20 0 0 0 0
+121 wlan0 0x3000180200000000 10020 1 336 5 319 4 336 5 0 0 0 0 319 4 0 0 0 0
+122 wlan0 0x3000180300000000 10020 0 129301 136 17622 97 129301 136 0 0 0 0 17622 97 0 0 0 0
+123 wlan0 0x3000180300000000 10020 1 464787 429 41703 336 464787 429 0 0 0 0 41703 336 0 0 0 0
+124 wlan0 0x3000180400000000 10020 0 11014 39 2787 25 11014 39 0 0 0 0 2787 25 0 0 0 0
+125 wlan0 0x3000180400000000 10020 1 144040 139 7540 80 144040 139 0 0 0 0 7540 80 0 0 0 0
+126 wlan0 0x3000210100000000 10020 0 10278 44 4579 33 10278 44 0 0 0 0 4579 33 0 0 0 0
+127 wlan0 0x3000210100000000 10020 1 31151 73 14159 47 31151 73 0 0 0 0 14159 47 0 0 0 0
+128 wlan0 0x3000250000000000 10020 0 132 2 72 1 132 2 0 0 0 0 72 1 0 0 0 0
+129 wlan0 0x3000250000000000 10020 1 76614 143 17711 130 76080 137 534 6 0 0 17177 124 534 6 0 0
+130 wlan0 0x3000260100000000 10020 0 9426 26 3535 20 9426 26 0 0 0 0 3535 20 0 0 0 0
+131 wlan0 0x3000260100000000 10020 1 468 7 288 4 468 7 0 0 0 0 288 4 0 0 0 0
+132 wlan0 0x3000300000000000 10020 0 7241 29 12055 26 7241 29 0 0 0 0 12055 26 0 0 0 0
+133 wlan0 0x3000300000000000 10020 1 3273 23 11232 21 3273 23 0 0 0 0 11232 21 0 0 0 0
+134 wlan0 0x3000310000000000 10020 0 132 2 72 1 132 2 0 0 0 0 72 1 0 0 0 0
+135 wlan0 0x3000310000000000 10020 1 53425 64 8721 62 53425 64 0 0 0 0 8721 62 0 0 0 0
+136 wlan0 0x3000310500000000 10020 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+137 wlan0 0x3000310500000000 10020 1 9929 16 3879 18 9929 16 0 0 0 0 3879 18 0 0 0 0
+138 wlan0 0x3000360000000000 10020 0 8855 43 4749 31 8855 43 0 0 0 0 4749 31 0 0 0 0
+139 wlan0 0x3000360000000000 10020 1 5597 19 2456 19 5597 19 0 0 0 0 2456 19 0 0 0 0
+140 wlan0 0x3010000000000000 10090 0 605140 527 38435 429 605140 527 0 0 0 0 38435 429 0 0 0 0
+141 wlan0 0x3010000000000000 10090 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+142 wlan0 0x31065fff00000000 10020 0 22011 67 29665 64 22011 67 0 0 0 0 29665 64 0 0 0 0
+143 wlan0 0x31065fff00000000 10020 1 10695 34 18347 35 10695 34 0 0 0 0 18347 35 0 0 0 0
+144 wlan0 0x32e544f900000000 10034 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+145 wlan0 0x32e544f900000000 10034 1 40143 54 7299 61 40143 54 0 0 0 0 7299 61 0 0 0 0
+146 wlan0 0x58872a4400000000 10018 0 4928 11 1669 13 4928 11 0 0 0 0 1669 13 0 0 0 0
+147 wlan0 0x58872a4400000000 10018 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+148 wlan0 0x5caeaa7b00000000 10034 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+149 wlan0 0x5caeaa7b00000000 10034 1 74971 73 7103 75 74971 73 0 0 0 0 7103 75 0 0 0 0
+150 wlan0 0x9e00923800000000 10034 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+151 wlan0 0x9e00923800000000 10034 1 72385 98 13072 110 72385 98 0 0 0 0 13072 110 0 0 0 0
+152 wlan0 0xb972bdd400000000 10034 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+153 wlan0 0xb972bdd400000000 10034 1 15282 24 3034 27 15282 24 0 0 0 0 3034 27 0 0 0 0
+154 wlan0 0xc7c9f7ba00000000 10034 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+155 wlan0 0xc7c9f7ba00000000 10034 1 194915 185 13316 138 194915 185 0 0 0 0 13316 138 0 0 0 0
+156 wlan0 0xc9395b2600000000 10034 0 6991 13 6215 14 6991 13 0 0 0 0 6215 14 0 0 0 0
+157 wlan0 0xc9395b2600000000 10034 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+158 wlan0 0xdaddf21100000000 10034 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+159 wlan0 0xdaddf21100000000 10034 1 928676 849 81570 799 928676 849 0 0 0 0 81570 799 0 0 0 0
+160 wlan0 0xe8d195d100000000 10020 0 516 8 288 4 516 8 0 0 0 0 288 4 0 0 0 0
+161 wlan0 0xe8d195d100000000 10020 1 5905 15 2622 15 5905 15 0 0 0 0 2622 15 0 0 0 0
+162 wlan0 0xe8d195d100000000 10034 0 236640 524 312523 555 236640 524 0 0 0 0 312523 555 0 0 0 0
+163 wlan0 0xe8d195d100000000 10034 1 319028 539 188776 553 319028 539 0 0 0 0 188776 553 0 0 0 0
+164 wlan0 0xffffff0100000000 10006 0 80755 92 9122 99 80755 92 0 0 0 0 9122 99 0 0 0 0
+165 wlan0 0xffffff0100000000 10006 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+166 wlan0 0xffffff0100000000 10020 0 17874405 14068 223987 3065 17874405 14068 0 0 0 0 223987 3065 0 0 0 0
+167 wlan0 0xffffff0100000000 10020 1 11011258 8672 177693 2407 11011258 8672 0 0 0 0 177693 2407 0 0 0 0
+168 wlan0 0xffffff0100000000 10034 0 436062595 341880 5843990 79630 436062595 341880 0 0 0 0 5843990 79630 0 0 0 0
+169 wlan0 0xffffff0100000000 10034 1 63201220 49447 1005882 13713 63201220 49447 0 0 0 0 1005882 13713 0 0 0 0
+170 wlan0 0xffffff0100000000 10044 0 17159287 13702 356212 4778 17159287 13702 0 0 0 0 356212 4778 0 0 0 0
+171 wlan0 0xffffff0100000000 10044 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+172 wlan0 0xffffff0100000000 10078 0 10439 17 1665 15 10439 17 0 0 0 0 1665 15 0 0 0 0
+173 wlan0 0xffffff0100000000 10078 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+174 wlan0 0xffffff0100000000 10090 0 23722655 19697 881995 14231 23722655 19697 0 0 0 0 881995 14231 0 0 0 0
+175 wlan0 0xffffff0100000000 10090 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+176 wlan0 0xffffff0500000000 1000 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+177 wlan0 0xffffff0500000000 1000 1 1592 5 314 1 0 0 1592 5 0 0 0 0 314 1 0 0
+178 wlan0 0xffffff0600000000 1000 0 0 0 36960 385 0 0 0 0 0 0 0 0 36960 385 0 0
+179 wlan0 0xffffff0600000000 1000 1 96 1 480 5 0 0 96 1 0 0 0 0 480 5 0 0
+180 wlan0 0xffffff0700000000 1000 0 38732 229 16567 163 38732 229 0 0 0 0 16567 163 0 0 0 0
+181 wlan0 0xffffff0700000000 1000 1 18539 74 7562 66 18539 74 0 0 0 0 7562 66 0 0 0 0
+182 wlan0 0xffffff0900000000 1000 0 38381 43 2624 27 38381 43 0 0 0 0 2624 27 0 0 0 0
+183 wlan0 0xffffff0900000000 1000 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+184 dummy0 0x0 0 0 0 0 168 3 0 0 0 0 0 0 0 0 0 0 168 3
+185 dummy0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
diff --git a/tests/net/res/raw/xt_qtaguid_with_clat_simple b/tests/net/res/raw/xt_qtaguid_with_clat_simple
new file mode 100644
index 0000000..7f0e56f
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_with_clat_simple
@@ -0,0 +1,5 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 v4-wlan0 0x0 10060 0 42600 213 4100 41 42600 213 4100 41 0 0 0 0 0 0 0 0
+3 v4-wlan0 0x0 10060 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+4 wlan0 0x0 0 0 46860 213 4920 41 46860 213 4920 41 0 0 0 0 0 0 0 0
+5 wlan0 0x0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0