Merge branch \'mnc-mr-docs\' into mnc-ub-dev
am: 3e5055b  -s ours

* commit '3e5055bec9570280638d5da0ef209765bba9dfaf': (546 commits)
  Update studio landing page and add all-new install flow docs. bug: 27850063 (update system reqs for 2.0) bug: 27725026 (update download page for 2.0) bug: 23908299 (clean up download and get started)
  docs: April 2016 Dashboard update
  docs: Added  new  partner logos to the car brands section of auto index page.
  Revert "Updates for Play" April Fool's joke.
  Updates for Play.
  docs: "Result is:" paragraph now in correct position on page.
  docs: Fixed namespace shown in a line of style XML
  docs: Add note about missing recording functionality in Live Channels
  docs: NDK r11c - release notes and downloads
  docs: Removed dead link in "create project" TOC.
  docs: Corrected reference link for NotificationManager.notify().
  docs: Added link to "preparing for multiwindow" blog post
  docs: Clarified how multi-window applies to activities in task stack
  [RenderScript] Update documentation about API level and RS Support Lib.
  cherrypick from mnc-docs docs: Updated API changes for Dinar wearable support.
  simplification and clarification to the Studio Overview
  Substitute for placeholder text.
  docs: Added missing semicolon within code sample.
  docs: Fixed Marshmallow links in API level table.
  docs: Update Nexus 9, 9G OTA images for N Preview
  ...

Change-Id: I2a64872d24ff2780503766f94e6d693edbdd47b8
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 4d9b759..a025337 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -17,15 +17,18 @@
 
 import static com.android.internal.util.Preconditions.checkNotNull;
 
+import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.net.NetworkUtils;
 import android.os.Binder;
 import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -36,22 +39,27 @@
 import android.os.Messenger;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.provider.Settings;
 import android.telephony.SubscriptionManager;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.SparseArray;
 
 import com.android.internal.telephony.ITelephony;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.util.Protocol;
-
-import java.net.InetAddress;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.HashMap;
+import com.android.internal.util.MessageUtils;
 
 import libcore.net.event.NetworkEventDispatcher;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.InetAddress;
+import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
 /**
  * Class that answers queries about the state of network connectivity. It also
  * notifies applications when network connectivity changes. Get an instance
@@ -73,6 +81,13 @@
 public class ConnectivityManager {
     private static final String TAG = "ConnectivityManager";
 
+    private static final SparseArray<String> sMagicDecoderRing = MessageUtils.findMessageNames(
+            new Class[]{ConnectivityManager.class}, new String[]{"CALLBACK_"});
+
+    private static final String whatToString(int what) {
+        return sMagicDecoderRing.get(what, Integer.toString(what));
+    }
+
     /**
      * A change in network connectivity has occurred. A default connection has either
      * been established or lost. The NetworkInfo for the affected network is
@@ -208,6 +223,12 @@
      * {@link android.content.Intent#getParcelableExtra(String)}.
      */
     public static final String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL";
+
+    /**
+     * Key for passing a URL to the captive portal login activity.
+     */
+    public static final String EXTRA_CAPTIVE_PORTAL_URL = "android.net.extra.CAPTIVE_PORTAL_URL";
+
     /**
      * Broadcast action to indicate the change of data activity status
      * (idle or active) on a network in a recent period.
@@ -329,6 +350,71 @@
     public static final String ACTION_PROMPT_UNVALIDATED = "android.net.conn.PROMPT_UNVALIDATED";
 
     /**
+     * Invalid tethering type.
+     * @see #startTethering(int, OnStartTetheringCallback, boolean)
+     * @hide
+     */
+    public static final int TETHERING_INVALID   = -1;
+
+    /**
+     * Wifi tethering type.
+     * @see #startTethering(int, OnStartTetheringCallback, boolean)
+     * @hide
+     */
+    @SystemApi
+    public static final int TETHERING_WIFI      = 0;
+
+    /**
+     * USB tethering type.
+     * @see #startTethering(int, OnStartTetheringCallback, boolean)
+     * @hide
+     */
+    @SystemApi
+    public static final int TETHERING_USB       = 1;
+
+    /**
+     * Bluetooth tethering type.
+     * @see #startTethering(int, OnStartTetheringCallback, boolean)
+     * @hide
+     */
+    @SystemApi
+    public static final int TETHERING_BLUETOOTH = 2;
+
+    /**
+     * Extra used for communicating with the TetherService. Includes the type of tethering to
+     * enable if any.
+     * @hide
+     */
+    public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
+
+    /**
+     * Extra used for communicating with the TetherService. Includes the type of tethering for
+     * which to cancel provisioning.
+     * @hide
+     */
+    public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType";
+
+    /**
+     * Extra used for communicating with the TetherService. True to schedule a recheck of tether
+     * provisioning.
+     * @hide
+     */
+    public static final String EXTRA_SET_ALARM = "extraSetAlarm";
+
+    /**
+     * Tells the TetherService to run a provision check now.
+     * @hide
+     */
+    public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
+
+    /**
+     * Extra used for communicating with the TetherService. Contains the {@link ResultReceiver}
+     * which will receive provisioning results. Can be left empty.
+     * @hide
+     */
+    public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback";
+
+    /**
      * The absence of a connection type.
      * @hide
      */
@@ -505,6 +591,7 @@
     private final Context mContext;
 
     private INetworkManagementService mNMService;
+    private INetworkPolicyManager mNPManager;
 
     /**
      * Tests if a given integer represents a valid network type.
@@ -655,7 +742,7 @@
         try {
             return mService.getActiveNetworkInfo();
         } catch (RemoteException e) {
-            return null;
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -675,7 +762,70 @@
         try {
             return mService.getActiveNetwork();
         } catch (RemoteException e) {
-            return null;
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns a {@link Network} object corresponding to the currently active
+     * default data network for a specific UID.  In the event that the default data
+     * 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
+     */
+    public Network getActiveNetworkForUid(int uid) {
+        try {
+            return mService.getActiveNetworkForUid(uid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Configures an always-on VPN connection through a specific application.
+     * This connection is automatically granted and persisted after a reboot.
+     *
+     * <p>The designated package should declare a {@link VpnService} in its
+     *    manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE},
+     *    otherwise the call will fail.
+     *
+     * @param userId The identifier of the user to set an always-on VPN for.
+     * @param vpnPackage The package name for an installed VPN app on the device, or {@code null}
+     *                   to remove an existing always-on VPN configuration.
+
+     * @return {@code true} if the package is set as always-on VPN controller;
+     *         {@code false} otherwise.
+     * @hide
+     */
+    public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage) {
+        try {
+            return mService.setAlwaysOnVpnPackage(userId, vpnPackage);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the package name of the currently set always-on VPN application.
+     * If there is no always-on VPN set, or the VPN is provided by the system instead
+     * of by an app, {@code null} will be returned.
+     *
+     * @return Package name of VPN controller responsible for always-on VPN,
+     *         or {@code null} if none is set.
+     * @hide
+     */
+    public String getAlwaysOnVpnPackageForUser(int userId) {
+        try {
+            return mService.getAlwaysOnVpnPackage(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -696,7 +846,7 @@
         try {
             return mService.getActiveNetworkInfoForUid(uid);
         } catch (RemoteException e) {
-            return null;
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -720,7 +870,7 @@
         try {
             return mService.getNetworkInfo(networkType);
         } catch (RemoteException e) {
-            return null;
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -740,7 +890,7 @@
         try {
             return mService.getNetworkInfoForNetwork(network);
         } catch (RemoteException e) {
-            return null;
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -761,7 +911,7 @@
         try {
             return mService.getAllNetworkInfo();
         } catch (RemoteException e) {
-            return null;
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -781,7 +931,7 @@
         try {
             return mService.getNetworkForType(networkType);
         } catch (RemoteException e) {
-            return null;
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -797,7 +947,7 @@
         try {
             return mService.getAllNetworks();
         } catch (RemoteException e) {
-            return null;
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -810,7 +960,7 @@
         try {
             return mService.getDefaultNetworkCapabilitiesForUser(userId);
         } catch (RemoteException e) {
-            return null;
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -829,7 +979,7 @@
         try {
             return mService.getActiveLinkProperties();
         } catch (RemoteException e) {
-            return null;
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -853,7 +1003,7 @@
         try {
             return mService.getLinkPropertiesForType(networkType);
         } catch (RemoteException e) {
-            return null;
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -870,7 +1020,7 @@
         try {
             return mService.getLinkProperties(network);
         } catch (RemoteException e) {
-            return null;
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -887,7 +1037,25 @@
         try {
             return mService.getNetworkCapabilities(network);
         } catch (RemoteException e) {
-            return null;
+            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.
+     * 2. This URL must be HTTP as redirect responses are used to find captive portal
+     *    sign-in pages. Captive portals cannot respond to HTTPS requests with redirects.
+     *
+     * @hide
+     */
+    @SystemApi
+    public String getCaptivePortalServerUrl() {
+        try {
+            return mService.getCaptivePortalServerUrl();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1022,13 +1190,25 @@
      * Guess what the network request was trying to say so that the resulting
      * network is accessible via the legacy (deprecated) API such as
      * requestRouteToHost.
-     * This means we should try to be fairly preceise about transport and
+     *
+     * This means we should try to be fairly precise about transport and
      * capability but ignore things such as networkSpecifier.
      * If the request has more than one transport or capability it doesn't
      * match the old legacy requests (they selected only single transport/capability)
      * so this function cannot map the request to a single legacy type and
      * the resulting network will not be available to the legacy APIs.
      *
+     * This code is only called from the requestNetwork API (L and above).
+     *
+     * Setting a legacy type causes CONNECTIVITY_ACTION broadcasts, which are expensive
+     * because they wake up lots of apps - see http://b/23350688 . So we currently only
+     * do this for SUPL requests, which are the only ones that we know need it. If
+     * omitting these broadcasts causes unacceptable app breakage, then for backwards
+     * compatibility we can send them:
+     *
+     * if (targetSdkVersion < Build.VERSION_CODES.M) &&        // legacy API unsupported >= M
+     *     targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP))  // requestNetwork not present < L
+     *
      * TODO - This should be removed when the legacy APIs are removed.
      */
     private int inferLegacyTypeForNetworkCapabilities(NetworkCapabilities netCap) {
@@ -1040,6 +1220,14 @@
             return TYPE_NONE;
         }
 
+        // Do this only for SUPL, until GnssLocationProvider is fixed. http://b/25876485 .
+        if (!netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
+            // NOTE: if this causes app breakage, we should not just comment out this early return;
+            // instead, we should make this early return conditional on the requesting app's target
+            // SDK version, as described in the comment above.
+            return TYPE_NONE;
+        }
+
         String type = null;
         int result = TYPE_NONE;
 
@@ -1056,7 +1244,7 @@
             type = "enableDUN";
             result = TYPE_MOBILE_DUN;
         } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
-           type = "enableSUPL";
+            type = "enableSUPL";
             result = TYPE_MOBILE_SUPL;
         // back out this hack for mms as they no longer need this and it's causing
         // device slowdowns - b/23350688 (note, supl still needs this)
@@ -1167,7 +1355,9 @@
         int type = legacyTypeForNetworkCapabilities(netCap);
         try {
             delay = mService.getRestoreDefaultNetworkDelay(type);
-        } catch (RemoteException e) {}
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
         LegacyRequest l = new LegacyRequest();
         l.networkCapabilities = netCap;
         l.delay = delay;
@@ -1385,7 +1575,7 @@
         try {
             return mService.requestRouteToHostAddress(networkType, hostAddress.getAddress());
         } catch (RemoteException e) {
-            return false;
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1441,7 +1631,7 @@
         try {
             return mService.getActiveNetworkQuotaInfo();
         } catch (RemoteException e) {
-            return null;
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1454,13 +1644,15 @@
         if (b != null) {
             try {
                 ITelephony it = ITelephony.Stub.asInterface(b);
-                int subId = SubscriptionManager.getDefaultDataSubId();
+                int subId = SubscriptionManager.getDefaultDataSubscriptionId();
                 Log.d("ConnectivityManager", "getMobileDataEnabled()+ subId=" + subId);
                 boolean retVal = it.getDataEnabled(subId);
                 Log.d("ConnectivityManager", "getMobileDataEnabled()- subId=" + subId
                         + " retVal=" + retVal);
                 return retVal;
-            } catch (RemoteException e) { }
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
         }
         Log.d("ConnectivityManager", "getMobileDataEnabled()- remote exception retVal=false");
         return false;
@@ -1521,6 +1713,7 @@
             getNetworkManagementService().registerNetworkActivityListener(rl);
             mNetworkActivityListeners.put(l, rl);
         } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1538,6 +1731,7 @@
         try {
             getNetworkManagementService().unregisterNetworkActivityListener(rl);
         } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1553,8 +1747,8 @@
         try {
             return getNetworkManagementService().isNetworkActive();
         } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
-        return false;
     }
 
     /**
@@ -1585,7 +1779,7 @@
             // Have a provisioning app - must only let system apps (which check this app)
             // turn on tethering
             context.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.CONNECTIVITY_INTERNAL, "ConnectivityService");
+                    android.Manifest.permission.TETHER_PRIVILEGED, "ConnectivityService");
         } else {
             int uid = Binder.getCallingUid();
             Settings.checkAndNoteWriteSettingsOperation(context, uid, Settings
@@ -1628,7 +1822,7 @@
         try {
             return mService.getTetherableIfaces();
         } catch (RemoteException e) {
-            return new String[0];
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1645,7 +1839,7 @@
         try {
             return mService.getTetheredIfaces();
         } catch (RemoteException e) {
-            return new String[0];
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1668,7 +1862,7 @@
         try {
             return mService.getTetheringErroredIfaces();
         } catch (RemoteException e) {
-            return new String[0];
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1682,7 +1876,7 @@
         try {
             return mService.getTetheredDhcpRanges();
         } catch (RemoteException e) {
-            return new String[0];
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1700,6 +1894,11 @@
      * or the ability to modify system settings as determined by
      * {@link android.provider.Settings.System#canWrite}.</p>
      *
+     * <p>WARNING: New clients should not use this function. The only usages should be in PanService
+     * and WifiStateMachine which need direct access. All other clients should use
+     * {@link #startTethering} and {@link #stopTethering} which encapsulate proper provisioning
+     * logic.</p>
+     *
      * @param iface the interface name to tether.
      * @return error a {@code TETHER_ERROR} value indicating success or failure type
      *
@@ -1709,7 +1908,7 @@
         try {
             return mService.tether(iface);
         } catch (RemoteException e) {
-            return TETHER_ERROR_SERVICE_UNAVAIL;
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1721,6 +1920,11 @@
      * or the ability to modify system settings as determined by
      * {@link android.provider.Settings.System#canWrite}.</p>
      *
+     * <p>WARNING: New clients should not use this function. The only usages should be in PanService
+     * and WifiStateMachine which need direct access. All other clients should use
+     * {@link #startTethering} and {@link #stopTethering} which encapsulate proper provisioning
+     * logic.</p>
+     *
      * @param iface the interface name to untether.
      * @return error a {@code TETHER_ERROR} value indicating success or failure type
      *
@@ -1730,7 +1934,7 @@
         try {
             return mService.untether(iface);
         } catch (RemoteException e) {
-            return TETHER_ERROR_SERVICE_UNAVAIL;
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1745,11 +1949,100 @@
      *
      * {@hide}
      */
+    @SystemApi
     public boolean isTetheringSupported() {
         try {
             return mService.isTetheringSupported();
         } catch (RemoteException e) {
-            return false;
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Callback for use with {@link #startTethering} to find out whether tethering succeeded.
+     * @hide
+     */
+    @SystemApi
+    public static abstract class OnStartTetheringCallback {
+        /**
+         * Called when tethering has been successfully started.
+         */
+        public void onTetheringStarted() {};
+
+        /**
+         * Called when starting tethering failed.
+         */
+        public void onTetheringFailed() {};
+    }
+
+    /**
+     * Convenient overload for
+     * {@link #startTethering(int, boolean, OnStartTetheringCallback, Handler)} which passes a null
+     * handler to run on the current thread's {@link Looper}.
+     * @hide
+     */
+    @SystemApi
+    public void startTethering(int type, boolean showProvisioningUi,
+            final OnStartTetheringCallback callback) {
+        startTethering(type, showProvisioningUi, callback, null);
+    }
+
+    /**
+     * Runs tether provisioning for the given type if needed and then starts tethering if
+     * the check succeeds. If no carrier provisioning is required for tethering, tethering is
+     * enabled immediately. If provisioning fails, tethering will not be enabled. It also
+     * schedules tether provisioning re-checks if appropriate.
+     *
+     * @param type The type of tethering to start. Must be one of
+     *         {@link ConnectivityManager.TETHERING_WIFI},
+     *         {@link ConnectivityManager.TETHERING_USB}, or
+     *         {@link ConnectivityManager.TETHERING_BLUETOOTH}.
+     * @param showProvisioningUi a boolean indicating to show the provisioning app UI if there
+     *         is one. This should be true the first time this function is called and also any time
+     *         the user can see this UI. It gives users information from their carrier about the
+     *         check failing and how they can sign up for tethering if possible.
+     * @param callback an {@link OnStartTetheringCallback} which will be called to notify the caller
+     *         of the result of trying to tether.
+     * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+     * @hide
+     */
+    @SystemApi
+    public void startTethering(int type, boolean showProvisioningUi,
+            final OnStartTetheringCallback callback, Handler handler) {
+        ResultReceiver wrappedCallback = new ResultReceiver(handler) {
+            @Override
+            protected void onReceiveResult(int resultCode, Bundle resultData) {
+                if (resultCode == TETHER_ERROR_NO_ERROR) {
+                    callback.onTetheringStarted();
+                } else {
+                    callback.onTetheringFailed();
+                }
+            }
+        };
+        try {
+            mService.startTethering(type, wrappedCallback, showProvisioningUi);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception trying to start tethering.", e);
+            wrappedCallback.send(TETHER_ERROR_SERVICE_UNAVAIL, null);
+        }
+    }
+
+    /**
+     * Stops tethering for the given type. Also cancels any provisioning rechecks for that type if
+     * applicable.
+     *
+     * @param type The type of tethering to stop. Must be one of
+     *         {@link ConnectivityManager.TETHERING_WIFI},
+     *         {@link ConnectivityManager.TETHERING_USB}, or
+     *         {@link ConnectivityManager.TETHERING_BLUETOOTH}.
+     * @hide
+     */
+    @SystemApi
+    public void stopTethering(int type) {
+        try {
+            mService.stopTethering(type);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1769,7 +2062,7 @@
         try {
             return mService.getTetherableUsbRegexs();
         } catch (RemoteException e) {
-            return new String[0];
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1789,7 +2082,7 @@
         try {
             return mService.getTetherableWifiRegexs();
         } catch (RemoteException e) {
-            return new String[0];
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1809,7 +2102,7 @@
         try {
             return mService.getTetherableBluetoothRegexs();
         } catch (RemoteException e) {
-            return new String[0];
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1834,7 +2127,7 @@
         try {
             return mService.setUsbTethering(enable);
         } catch (RemoteException e) {
-            return TETHER_ERROR_SERVICE_UNAVAIL;
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1860,6 +2153,8 @@
     public static final int TETHER_ERROR_DISABLE_NAT_ERROR    = 9;
     /** {@hide} */
     public static final int TETHER_ERROR_IFACE_CFG_ERROR      = 10;
+    /** {@hide} */
+    public static final int TETHER_ERROR_PROVISION_FAILED     = 11;
 
     /**
      * Get a more detailed error code after a Tethering or Untethering
@@ -1877,7 +2172,7 @@
         try {
             return mService.getLastTetherError(iface);
         } catch (RemoteException e) {
-            return TETHER_ERROR_SERVICE_UNAVAIL;
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1895,6 +2190,7 @@
         try {
             mService.reportInetCondition(networkType, percentage);
         } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1916,6 +2212,7 @@
             mService.reportNetworkConnectivity(network, true);
             mService.reportNetworkConnectivity(network, false);
         } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1934,6 +2231,7 @@
         try {
             mService.reportNetworkConnectivity(network, hasConnectivity);
         } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1953,6 +2251,7 @@
         try {
             mService.setGlobalProxy(p);
         } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1967,7 +2266,7 @@
         try {
             return mService.getGlobalProxy();
         } catch (RemoteException e) {
-            return null;
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1989,7 +2288,7 @@
         try {
             return mService.getProxyForNetwork(network);
         } catch (RemoteException e) {
-            return null;
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -2024,8 +2323,9 @@
     public boolean isNetworkSupported(int networkType) {
         try {
             return mService.isNetworkSupported(networkType);
-        } catch (RemoteException e) {}
-        return false;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -2045,7 +2345,7 @@
         try {
             return mService.isActiveNetworkMetered();
         } catch (RemoteException e) {
-            return false;
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -2062,7 +2362,7 @@
         try {
             return mService.updateLockdownVpn();
         } catch (RemoteException e) {
-            return false;
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -2081,6 +2381,7 @@
         try {
             timeOutMs = mService.checkMobileProvisioning(suggestedTimeOutMs);
         } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
         return timeOutMs;
     }
@@ -2093,8 +2394,8 @@
         try {
             return mService.getMobileProvisioningUrl();
         } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
-        return null;
     }
 
     /**
@@ -2111,6 +2412,7 @@
         try {
             mService.setProvisioningNotificationVisible(visible, networkType, action);
         } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -2127,6 +2429,7 @@
         try {
             mService.setAirplaneMode(enable);
         } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -2134,14 +2437,18 @@
     public void registerNetworkFactory(Messenger messenger, String name) {
         try {
             mService.registerNetworkFactory(messenger, name);
-        } catch (RemoteException e) { }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /** {@hide} */
     public void unregisterNetworkFactory(Messenger messenger) {
         try {
             mService.unregisterNetworkFactory(messenger);
-        } catch (RemoteException e) { }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -2154,7 +2461,7 @@
         try {
             return mService.registerNetworkAgent(messenger, ni, lp, nc, score, misc);
         } catch (RemoteException e) {
-            return NETID_UNSET;
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -2305,9 +2612,11 @@
 
         @Override
         public void handleMessage(Message message) {
-            if (DBG) Log.d(TAG, "CM callback handler got msg " + message.what);
             NetworkRequest request = (NetworkRequest) getObject(message, NetworkRequest.class);
             Network network = (Network) getObject(message, Network.class);
+            if (DBG) {
+                Log.d(TAG, whatToString(message.what) + " for network " + network);
+            }
             switch (message.what) {
                 case CALLBACK_PRECHECK: {
                     NetworkCallback callback = getCallback(request, "PRECHECK");
@@ -2457,7 +2766,9 @@
         if (networkCallback == null) {
             throw new IllegalArgumentException("null NetworkCallback");
         }
-        if (need == null) throw new IllegalArgumentException("null NetworkCapabilities");
+        if (need == null && action != REQUEST) {
+            throw new IllegalArgumentException("null NetworkCapabilities");
+        }
         try {
             incCallbackHandlerRefCount();
             synchronized(sNetworkCallback) {
@@ -2472,12 +2783,31 @@
                     sNetworkCallback.put(networkCallback.networkRequest, networkCallback);
                 }
             }
-        } catch (RemoteException e) {}
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
         if (networkCallback.networkRequest == null) decCallbackHandlerRefCount();
         return networkCallback.networkRequest;
     }
 
     /**
+     * Helper function to request a network with a particular legacy type.
+     *
+     * This is temporarily public @hide so it can be called by system code that uses the
+     * NetworkRequest API to request networks but relies on CONNECTIVITY_ACTION broadcasts for
+     * instead network notifications.
+     *
+     * TODO: update said system code to rely on NetworkCallbacks and make this method private.
+     *
+     * @hide
+     */
+    public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
+            int timeoutMs, int legacyType) {
+        sendRequestForNetwork(request.networkCapabilities, networkCallback, timeoutMs, REQUEST,
+                legacyType);
+    }
+
+    /**
      * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}.
      *
      * This {@link NetworkRequest} will live until released via
@@ -2507,8 +2837,8 @@
      *         {@code NetworkCapabilities}.
      */
     public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback) {
-        sendRequestForNetwork(request.networkCapabilities, networkCallback, 0,
-                REQUEST, inferLegacyTypeForNetworkCapabilities(request.networkCapabilities));
+        requestNetwork(request, networkCallback, 0,
+                inferLegacyTypeForNetworkCapabilities(request.networkCapabilities));
     }
 
     /**
@@ -2518,7 +2848,7 @@
      * 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 releaseNetworkRequest}.
+     * still be released normally by calling {@link unregisterNetworkCallback}.
      *
      * <p>This method requires the caller to hold either the
      * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
@@ -2531,12 +2861,15 @@
      *                        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
      */
     public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
             int timeoutMs) {
-        sendRequestForNetwork(request.networkCapabilities, networkCallback, timeoutMs,
-                REQUEST, inferLegacyTypeForNetworkCapabilities(request.networkCapabilities));
+        requestNetwork(request, networkCallback, timeoutMs,
+                inferLegacyTypeForNetworkCapabilities(request.networkCapabilities));
     }
 
     /**
@@ -2615,7 +2948,9 @@
         checkPendingIntent(operation);
         try {
             mService.pendingRequestForNetwork(request.networkCapabilities, operation);
-        } catch (RemoteException e) {}
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -2633,7 +2968,9 @@
         checkPendingIntent(operation);
         try {
             mService.releasePendingNetworkRequest(operation);
-        } catch (RemoteException e) {}
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     private void checkPendingIntent(PendingIntent intent) {
@@ -2692,7 +3029,30 @@
         checkPendingIntent(operation);
         try {
             mService.pendingListenForNetwork(request.networkCapabilities, operation);
-        } catch (RemoteException e) {}
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Registers to receive notifications about whichever network currently satisfies the
+     * system default {@link NetworkRequest}.  The callbacks will continue to be called until
+     * either the application exits or {@link #unregisterNetworkCallback} 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.
+     */
+    public void registerDefaultNetworkCallback(NetworkCallback networkCallback) {
+        // This works because if the NetworkCapabilities are null,
+        // ConnectivityService takes them from the default request.
+        //
+        // Since the capabilities are exactly the same as the default request's
+        // 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);
     }
 
     /**
@@ -2710,7 +3070,7 @@
         try {
             return mService.requestBandwidthUpdate(network);
         } catch (RemoteException e) {
-            return false;
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -2730,7 +3090,9 @@
         }
         try {
             mService.releaseNetworkRequest(networkCallback.networkRequest);
-        } catch (RemoteException e) {}
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -2766,7 +3128,9 @@
     public void setAcceptUnvalidated(Network network, boolean accept, boolean always) {
         try {
             mService.setAcceptUnvalidated(network, accept, always);
-        } catch (RemoteException e) {}
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -2777,6 +3141,7 @@
         try {
             mService.factoryReset();
         } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -2892,7 +3257,7 @@
 
     // Checks whether the calling app can use the legacy routing API (startUsingNetworkFeature,
     // stopUsingNetworkFeature, requestRouteToHost), and if not throw UnsupportedOperationException.
-    // TODO: convert the existing system users (Tethering, GpsLocationProvider) to the new APIs and
+    // TODO: convert the existing system users (Tethering, GnssLocationProvider) to the new APIs and
     // remove these exemptions. Note that this check is not secure, and apps can still access these
     // functions by accessing ConnectivityService directly. However, it should be clear that doing
     // so is unsupported and may break in the future. http://b/22728205
@@ -2919,4 +3284,72 @@
         return NetworkUtils.bindProcessToNetworkForHostResolution(
                 network == null ? NETID_UNSET : network.netId);
     }
+
+    /**
+     * Device is not restricting metered network activity while application is running on
+     * background.
+     */
+    public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1;
+
+    /**
+     * Device is restricting metered network activity while application is running on background,
+     * but application is allowed to bypass it.
+     * <p>
+     * In this state, application should take action to mitigate metered network access.
+     * For example, a music streaming application should switch to a low-bandwidth bitrate.
+     */
+    public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2;
+
+    /**
+     * Device is restricting metered network activity while application is running on background.
+     * <p>
+     * In this state, application should not try to use the network while running on background,
+     * because it would be denied.
+     */
+    public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3;
+
+    /**
+     * A change in the background metered network activity restriction has occurred.
+     * <p>
+     * Applications should call {@link #getRestrictBackgroundStatus()} to check if the restriction
+     * applies to them.
+     * <p>
+     * This is only sent to registered receivers, not manifest receivers.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_RESTRICT_BACKGROUND_CHANGED =
+            "android.net.conn.RESTRICT_BACKGROUND_CHANGED";
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = false, value = {
+            RESTRICT_BACKGROUND_STATUS_DISABLED,
+            RESTRICT_BACKGROUND_STATUS_WHITELISTED,
+            RESTRICT_BACKGROUND_STATUS_ENABLED,
+    })
+    public @interface RestrictBackgroundStatus {
+    }
+
+    private INetworkPolicyManager getNetworkPolicyManager() {
+        synchronized (this) {
+            if (mNPManager != null) {
+                return mNPManager;
+            }
+            mNPManager = INetworkPolicyManager.Stub.asInterface(ServiceManager
+                    .getService(Context.NETWORK_POLICY_SERVICE));
+            return mNPManager;
+        }
+    }
+
+    /**
+     * Determines if the calling application is subject to metered network restrictions while
+     * running on background.
+     */
+    public @RestrictBackgroundStatus int getRestrictBackgroundStatus() {
+        try {
+            return getNetworkPolicyManager().getRestrictBackgroundByCaller();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/net/ConnectivityThread.java b/core/java/android/net/ConnectivityThread.java
new file mode 100644
index 0000000..55c3402
--- /dev/null
+++ b/core/java/android/net/ConnectivityThread.java
@@ -0,0 +1,51 @@
+/*
+ * 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 android.net;
+
+import android.os.HandlerThread;
+import android.os.Looper;
+
+/**
+ * Shared singleton connectivity thread for the system.  This is a thread for
+ * connectivity operations such as AsyncChannel connections to system services.
+ * Various connectivity manager objects can use this singleton as a common
+ * resource for their handlers instead of creating separate threads of their own.
+ * @hide
+ */
+public final class ConnectivityThread extends HandlerThread {
+    private static ConnectivityThread sInstance;
+
+    private ConnectivityThread() {
+        super("ConnectivityThread");
+    }
+
+    private static synchronized ConnectivityThread getInstance() {
+        if (sInstance == null) {
+            sInstance = new ConnectivityThread();
+            sInstance.start();
+        }
+        return sInstance;
+    }
+
+    public static ConnectivityThread get() {
+        return getInstance();
+    }
+
+    public static Looper getInstanceLooper() {
+        return getInstance().getLooper();
+    }
+}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index d4dd669..c897c45 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -44,6 +44,7 @@
 interface IConnectivityManager
 {
     Network getActiveNetwork();
+    Network getActiveNetworkForUid(int uid);
     NetworkInfo getActiveNetworkInfo();
     NetworkInfo getActiveNetworkInfoForUid(int uid);
     NetworkInfo getNetworkInfo(int networkType);
@@ -76,6 +77,10 @@
 
     boolean isTetheringSupported();
 
+    void startTethering(int type, in ResultReceiver receiver, boolean showProvisioningUi);
+
+    void stopTethering(int type);
+
     String[] getTetherableIfaces();
 
     String[] getTetheredIfaces();
@@ -117,6 +122,8 @@
     VpnInfo[] getAllVpnInfo();
 
     boolean updateLockdownVpn();
+    boolean setAlwaysOnVpnPackage(int userId, String packageName);
+    String getAlwaysOnVpnPackage(int userId);
 
     int checkMobileProvisioning(int suggestedTimeOutMs);
 
@@ -165,4 +172,6 @@
             in IBinder binder, String srcAddr, int srcPort, String dstAddr);
 
     void stopKeepalive(in Network network, int slot);
+
+    String getCaptivePortalServerUrl();
 }
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index c4de4a2..1bb0fbb 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -119,8 +119,12 @@
             //
             // For one such example of this, see b/18867306.
             //
-            // TODO: Remove this special case altogether.
-            if (before.isIPv4Provisioned() && !after.isIPv4Provisioned()) {
+            // Additionally, losing IPv6 provisioning can result in TCP
+            // connections getting stuck until timeouts fire and other
+            // baffling failures. Therefore, loss of either IPv4 or IPv6 on a
+            // previously dualstack network is deemed a lost of provisioning.
+            if ((before.isIPv4Provisioned() && !after.isIPv4Provisioned()) ||
+                (before.isIPv6Provisioned() && !after.isIPv6Provisioned())) {
                 return ProvisioningChange.LOST_PROVISIONING;
             }
             return ProvisioningChange.STILL_PROVISIONED;
@@ -667,7 +671,8 @@
      * @return {@code true} if there is an IPv4 address, {@code false} otherwise.
      */
     private boolean hasIPv4AddressOnInterface(String iface) {
-        return (mIfaceName.equals(iface) && hasIPv4Address()) ||
+        // mIfaceName can be null.
+        return (Objects.equals(iface, mIfaceName) && hasIPv4Address()) ||
                 (iface != null && mStackedLinks.containsKey(iface) &&
                         mStackedLinks.get(iface).hasIPv4Address());
     }
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 3bd12c0..6418614 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -197,6 +197,19 @@
             (1 << NET_CAPABILITY_CAPTIVE_PORTAL);
 
     /**
+     * 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
@@ -235,7 +248,7 @@
      * for a network to satisfy a request, all capabilities requested must be satisfied.
      *
      * @param capability the {@code NetworkCapabilities.NET_CAPABILITY_*} to be added.
-     * @return This NetworkCapability to facilitate chaining.
+     * @return This NetworkCapabilities instance, to facilitate chaining.
      * @hide
      */
     public NetworkCapabilities addCapability(int capability) {
@@ -250,7 +263,7 @@
      * Removes (if found) the given capability from this {@code NetworkCapability} instance.
      *
      * @param capability the {@code NetworkCapabilities.NET_CAPABILTIY_*} to be removed.
-     * @return This NetworkCapability to facilitate chaining.
+     * @return This NetworkCapabilities instance, to facilitate chaining.
      * @hide
      */
     public NetworkCapabilities removeCapability(int capability) {
@@ -405,7 +418,7 @@
      * {@code NetworkCapabilities.NET_CAPABILITY_*} listed above.
      *
      * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be added.
-     * @return This NetworkCapability to facilitate chaining.
+     * @return This NetworkCapabilities instance, to facilitate chaining.
      * @hide
      */
     public NetworkCapabilities addTransportType(int transportType) {
@@ -421,7 +434,7 @@
      * Removes (if found) the given transport from this {@code NetworkCapability} instance.
      *
      * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be removed.
-     * @return This NetworkCapability to facilitate chaining.
+     * @return This NetworkCapabilities instance, to facilitate chaining.
      * @hide
      */
     public NetworkCapabilities removeTransportType(int transportType) {
@@ -565,14 +578,16 @@
      * @param networkSpecifier An {@code String} of opaque format used to specify the bearer
      *                         specific network specifier where the bearer has a choice of
      *                         networks.
+     * @return This NetworkCapabilities instance, to facilitate chaining.
      * @hide
      */
-    public void setNetworkSpecifier(String networkSpecifier) {
+    public NetworkCapabilities setNetworkSpecifier(String networkSpecifier) {
         if (TextUtils.isEmpty(networkSpecifier) == false && Long.bitCount(mTransportTypes) != 1) {
             throw new IllegalStateException("Must have a single transport specified to use " +
                     "setNetworkSpecifier");
         }
         mNetworkSpecifier = networkSpecifier;
+        return this;
     }
 
     /**
@@ -596,7 +611,8 @@
     }
     private boolean satisfiedBySpecifier(NetworkCapabilities nc) {
         return (TextUtils.isEmpty(mNetworkSpecifier) ||
-                mNetworkSpecifier.equals(nc.mNetworkSpecifier));
+                mNetworkSpecifier.equals(nc.mNetworkSpecifier) ||
+                MATCH_ALL_REQUESTS_NETWORK_SPECIFIER.equals(nc.mNetworkSpecifier));
     }
     private boolean equalsSpecifier(NetworkCapabilities nc) {
         if (TextUtils.isEmpty(mNetworkSpecifier)) {
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 7da4818..f1edcbe 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -188,6 +188,10 @@
          *                         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 + "'");
+            }
             mNetworkCapabilities.setNetworkSpecifier(networkSpecifier);
             return this;
         }
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 4487cab..141af3d 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -39,113 +39,19 @@
 
     private static final String TAG = "NetworkUtils";
 
-    /** Setting bit 0 indicates reseting of IPv4 addresses required */
-    public static final int RESET_IPV4_ADDRESSES = 0x01;
-
-    /** Setting bit 1 indicates reseting of IPv4 addresses required */
-    public static final int RESET_IPV6_ADDRESSES = 0x02;
-
-    /** Reset all addresses */
-    public static final int RESET_ALL_ADDRESSES = RESET_IPV4_ADDRESSES | RESET_IPV6_ADDRESSES;
-
-    /**
-     * Reset IPv6 or IPv4 sockets that are connected via the named interface.
-     *
-     * @param interfaceName is the interface to reset
-     * @param mask {@see #RESET_IPV4_ADDRESSES} and {@see #RESET_IPV6_ADDRESSES}
-     */
-    public native static int resetConnections(String interfaceName, int mask);
-
-    /**
-     * Start the DHCP client daemon, in order to have it request addresses
-     * for the named interface.  This returns {@code true} if the DHCPv4 daemon
-     * starts, {@code false} otherwise.  This call blocks until such time as a
-     * result is available or the default discovery timeout has been reached.
-     * Callers should check {@link #getDhcpResults} to determine whether DHCP
-     * succeeded or failed, and if it succeeded, to fetch the {@link DhcpResults}.
-     * @param interfaceName the name of the interface to configure
-     * @return {@code true} for success, {@code false} for failure
-     */
-    public native static boolean startDhcp(String interfaceName);
-
-    /**
-     * Initiate renewal on the DHCP client daemon for the named interface.  This
-     * returns {@code true} if the DHCPv4 daemon has been notified, {@code false}
-     * otherwise.  This call blocks until such time as a result is available or
-     * the default renew timeout has been reached.  Callers should check
-     * {@link #getDhcpResults} to determine whether DHCP succeeded or failed,
-     * and if it succeeded, to fetch the {@link DhcpResults}.
-     * @param interfaceName the name of the interface to configure
-     * @return {@code true} for success, {@code false} for failure
-     */
-    public native static boolean startDhcpRenew(String interfaceName);
-
-    /**
-     * Start the DHCP client daemon, in order to have it request addresses
-     * for the named interface, and then configure the interface with those
-     * addresses. This call blocks until it obtains a result (either success
-     * or failure) from the daemon.
-     * @param interfaceName the name of the interface to configure
-     * @param dhcpResults if the request succeeds, this object is filled in with
-     * the IP address information.
-     * @return {@code true} for success, {@code false} for failure
-     */
-    public static boolean runDhcp(String interfaceName, DhcpResults dhcpResults) {
-        return startDhcp(interfaceName) && getDhcpResults(interfaceName, dhcpResults);
-    }
-
-    /**
-     * Initiate renewal on the DHCP client daemon. This call blocks until it obtains
-     * a result (either success or failure) from the daemon.
-     * @param interfaceName the name of the interface to configure
-     * @param dhcpResults if the request succeeds, this object is filled in with
-     * the IP address information.
-     * @return {@code true} for success, {@code false} for failure
-     */
-    public static boolean runDhcpRenew(String interfaceName, DhcpResults dhcpResults) {
-        return startDhcpRenew(interfaceName) && getDhcpResults(interfaceName, dhcpResults);
-    }
-
-    /**
-     * Fetch results from the DHCP client daemon. This call returns {@code true} if
-     * if there are results available to be read, {@code false} otherwise.
-     * @param interfaceName the name of the interface to configure
-     * @param dhcpResults if the request succeeds, this object is filled in with
-     * the IP address information.
-     * @return {@code true} for success, {@code false} for failure
-     */
-    public native static boolean getDhcpResults(String interfaceName, DhcpResults dhcpResults);
-
-    /**
-     * Shut down the DHCP client daemon.
-     * @param interfaceName the name of the interface for which the daemon
-     * should be stopped
-     * @return {@code true} for success, {@code false} for failure
-     */
-    public native static boolean stopDhcp(String interfaceName);
-
-    /**
-     * Release the current DHCP lease.
-     * @param interfaceName the name of the interface for which the lease should
-     * be released
-     * @return {@code true} for success, {@code false} for failure
-     */
-    public native static boolean releaseDhcpLease(String interfaceName);
-
-    /**
-     * Return the last DHCP-related error message that was recorded.
-     * <p/>NOTE: This string is not localized, but currently it is only
-     * used in logging.
-     * @return the most recent error message, if any
-     */
-    public native static String getDhcpError();
-
     /**
      * Attaches a socket filter that accepts DHCP packets to the given socket.
      */
     public native static void attachDhcpFilter(FileDescriptor fd) throws SocketException;
 
     /**
+     * Attaches a socket filter that accepts ICMP6 router advertisement packets to the given socket.
+     * @param fd the socket's {@link FileDescriptor}.
+     * @param packetType the hardware address type, one of ARPHRD_*.
+     */
+    public native static void attachRaFilter(FileDescriptor fd, int packetType) throws SocketException;
+
+    /**
      * Binds the current process to the network designated by {@code netId}.  All sockets created
      * in the future (and not explicitly bound via a bound {@link SocketFactory} (see
      * {@link Network#getSocketFactory}) will be bound to this network.  Note that if this
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index fada7ac..2364787 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -26,10 +26,13 @@
 #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/icmp6.h>
 #include <netinet/ip.h>
+#include <netinet/ip6.h>
 #include <netinet/udp.h>
 #include <cutils/properties.h>
 
@@ -38,24 +41,6 @@
 extern "C" {
 int ifc_enable(const char *ifname);
 int ifc_disable(const char *ifname);
-int ifc_reset_connections(const char *ifname, int reset_mask);
-
-int dhcp_start(const char * const ifname);
-int dhcp_start_renew(const char * const ifname);
-int dhcp_get_results(const char * const ifname,
-                     const char *ipaddr,
-                     const char *gateway,
-                     uint32_t *prefixLength,
-                     const char *dns[],
-                     const char *server,
-                     uint32_t *lease,
-                     const char *vendorInfo,
-                     const char *domains,
-                     const char *mtu);
-
-int dhcp_stop(const char *ifname);
-int dhcp_release_lease(const char *ifname);
-char *dhcp_get_errmsg();
 }
 
 #define NETUTILS_PKG_NAME "android/net/NetworkUtils"
@@ -64,174 +49,11 @@
 
 static const uint16_t kDhcpClientPort = 68;
 
-/*
- * The following remembers the jfieldID's of the fields
- * of the DhcpInfo Java object, so that we don't have
- * to look them up every time.
- */
-static struct fieldIds {
-    jmethodID clear;
-    jmethodID setIpAddress;
-    jmethodID setGateway;
-    jmethodID addDns;
-    jmethodID setDomains;
-    jmethodID setServerAddress;
-    jmethodID setLeaseDuration;
-    jmethodID setVendorInfo;
-} dhcpResultsFieldIds;
-
-static jint android_net_utils_resetConnections(JNIEnv* env, jobject clazz,
-      jstring ifname, jint mask)
-{
-    int result;
-
-    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
-
-    ALOGD("android_net_utils_resetConnections in env=%p clazz=%p iface=%s mask=0x%x\n",
-          env, clazz, nameStr, mask);
-
-    result = ::ifc_reset_connections(nameStr, mask);
-    env->ReleaseStringUTFChars(ifname, nameStr);
-    return (jint)result;
-}
-
-static jboolean android_net_utils_getDhcpResults(JNIEnv* env, jobject clazz, jstring ifname,
-        jobject dhcpResults)
-{
-    int result;
-    char  ipaddr[PROPERTY_VALUE_MAX];
-    uint32_t prefixLength;
-    char gateway[PROPERTY_VALUE_MAX];
-    char    dns1[PROPERTY_VALUE_MAX];
-    char    dns2[PROPERTY_VALUE_MAX];
-    char    dns3[PROPERTY_VALUE_MAX];
-    char    dns4[PROPERTY_VALUE_MAX];
-    const char *dns[5] = {dns1, dns2, dns3, dns4, NULL};
-    char  server[PROPERTY_VALUE_MAX];
-    uint32_t lease;
-    char vendorInfo[PROPERTY_VALUE_MAX];
-    char domains[PROPERTY_VALUE_MAX];
-    char mtu[PROPERTY_VALUE_MAX];
-
-    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
-    if (nameStr == NULL) return (jboolean)false;
-
-    result = ::dhcp_get_results(nameStr, ipaddr, gateway, &prefixLength,
-            dns, server, &lease, vendorInfo, domains, mtu);
-    if (result != 0) {
-        ALOGD("dhcp_get_results failed : %s (%s)", nameStr, ::dhcp_get_errmsg());
-    }
-
-    env->ReleaseStringUTFChars(ifname, nameStr);
-    if (result == 0) {
-        env->CallVoidMethod(dhcpResults, dhcpResultsFieldIds.clear);
-
-        // set the linkAddress
-        // dhcpResults->addLinkAddress(inetAddress, prefixLength)
-        result = env->CallBooleanMethod(dhcpResults, dhcpResultsFieldIds.setIpAddress,
-                env->NewStringUTF(ipaddr), prefixLength);
-    }
-
-    if (result == 0) {
-        // set the gateway
-        result = env->CallBooleanMethod(dhcpResults,
-                dhcpResultsFieldIds.setGateway, env->NewStringUTF(gateway));
-    }
-
-    if (result == 0) {
-        // dhcpResults->addDns(new InetAddress(dns1))
-        result = env->CallBooleanMethod(dhcpResults,
-                dhcpResultsFieldIds.addDns, env->NewStringUTF(dns1));
-    }
-
-    if (result == 0) {
-        env->CallVoidMethod(dhcpResults, dhcpResultsFieldIds.setDomains,
-                env->NewStringUTF(domains));
-
-        result = env->CallBooleanMethod(dhcpResults,
-                dhcpResultsFieldIds.addDns, env->NewStringUTF(dns2));
-
-        if (result == 0) {
-            result = env->CallBooleanMethod(dhcpResults,
-                    dhcpResultsFieldIds.addDns, env->NewStringUTF(dns3));
-            if (result == 0) {
-                result = env->CallBooleanMethod(dhcpResults,
-                        dhcpResultsFieldIds.addDns, env->NewStringUTF(dns4));
-            }
-        }
-    }
-
-    if (result == 0) {
-        // dhcpResults->setServerAddress(new InetAddress(server))
-        result = env->CallBooleanMethod(dhcpResults, dhcpResultsFieldIds.setServerAddress,
-                env->NewStringUTF(server));
-    }
-
-    if (result == 0) {
-        // dhcpResults->setLeaseDuration(lease)
-        env->CallVoidMethod(dhcpResults,
-                dhcpResultsFieldIds.setLeaseDuration, lease);
-
-        // dhcpResults->setVendorInfo(vendorInfo)
-        env->CallVoidMethod(dhcpResults, dhcpResultsFieldIds.setVendorInfo,
-                env->NewStringUTF(vendorInfo));
-    }
-    return (jboolean)(result == 0);
-}
-
-static jboolean android_net_utils_startDhcp(JNIEnv* env, jobject clazz, jstring ifname)
-{
-    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
-    if (nameStr == NULL) return (jboolean)false;
-    if (::dhcp_start(nameStr) != 0) {
-        ALOGD("dhcp_start failed : %s", nameStr);
-        return (jboolean)false;
-    }
-    return (jboolean)true;
-}
-
-static jboolean android_net_utils_startDhcpRenew(JNIEnv* env, jobject clazz, jstring ifname)
-{
-    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
-    if (nameStr == NULL) return (jboolean)false;
-    if (::dhcp_start_renew(nameStr) != 0) {
-        ALOGD("dhcp_start_renew failed : %s", nameStr);
-        return (jboolean)false;
-    }
-    return (jboolean)true;
-}
-
-static jboolean android_net_utils_stopDhcp(JNIEnv* env, jobject clazz, jstring ifname)
-{
-    int result;
-
-    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
-    result = ::dhcp_stop(nameStr);
-    env->ReleaseStringUTFChars(ifname, nameStr);
-    return (jboolean)(result == 0);
-}
-
-static jboolean android_net_utils_releaseDhcpLease(JNIEnv* env, jobject clazz, jstring ifname)
-{
-    int result;
-
-    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
-    result = ::dhcp_release_lease(nameStr);
-    env->ReleaseStringUTFChars(ifname, nameStr);
-    return (jboolean)(result == 0);
-}
-
-static jstring android_net_utils_getDhcpError(JNIEnv* env, jobject clazz)
-{
-    return env->NewStringUTF(::dhcp_get_errmsg());
-}
-
 static void android_net_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd)
 {
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
     uint32_t ip_offset = sizeof(ether_header);
     uint32_t proto_offset = ip_offset + offsetof(iphdr, protocol);
-    uint32_t flags_offset = ip_offset +  offsetof(iphdr, frag_off);
+    uint32_t flags_offset = ip_offset + offsetof(iphdr, frag_off);
     uint32_t dport_indirect_offset = ip_offset + offsetof(udphdr, dest);
     struct sock_filter filter_code[] = {
         // Check the protocol is UDP.
@@ -258,6 +80,45 @@
         filter_code,
     };
 
+    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
+        jniThrowExceptionFmt(env, "java/net/SocketException",
+                "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
+    }
+}
+
+static void android_net_utils_attachRaFilter(JNIEnv *env, jobject clazz, jobject javaFd,
+        jint hardwareAddressType)
+{
+    if (hardwareAddressType != ARPHRD_ETHER) {
+        jniThrowExceptionFmt(env, "java/net/SocketException",
+                "attachRaFilter only supports ARPHRD_ETHER");
+        return;
+    }
+
+    uint32_t ipv6_offset = sizeof(ether_header);
+    uint32_t ipv6_next_header_offset = ipv6_offset + offsetof(ip6_hdr, ip6_nxt);
+    uint32_t icmp6_offset = ipv6_offset + sizeof(ip6_hdr);
+    uint32_t icmp6_type_offset = icmp6_offset + offsetof(icmp6_hdr, icmp6_type);
+    struct sock_filter filter_code[] = {
+        // Check IPv6 Next Header is ICMPv6.
+        BPF_STMT(BPF_LD  | BPF_B   | BPF_ABS,  ipv6_next_header_offset),
+        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    IPPROTO_ICMPV6, 0, 3),
+
+        // Check ICMPv6 type is Router Advertisement.
+        BPF_STMT(BPF_LD  | BPF_B   | BPF_ABS,  icmp6_type_offset),
+        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    ND_ROUTER_ADVERT, 0, 1),
+
+        // Accept or reject.
+        BPF_STMT(BPF_RET | BPF_K,              0xffff),
+        BPF_STMT(BPF_RET | BPF_K,              0)
+    };
+    struct sock_fprog filter = {
+        sizeof(filter_code) / sizeof(filter_code[0]),
+        filter_code,
+    };
+
+    int fd = jniGetFDFromFileDescriptor(env, javaFd);
     if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
         jniThrowExceptionFmt(env, "java/net/SocketException",
                 "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
@@ -302,15 +163,8 @@
 /*
  * JNI registration.
  */
-static JNINativeMethod gNetworkUtilMethods[] = {
+static const JNINativeMethod gNetworkUtilMethods[] = {
     /* name, signature, funcPtr */
-    { "resetConnections", "(Ljava/lang/String;I)I",  (void *)android_net_utils_resetConnections },
-    { "startDhcp", "(Ljava/lang/String;)Z",  (void *)android_net_utils_startDhcp },
-    { "startDhcpRenew", "(Ljava/lang/String;)Z",  (void *)android_net_utils_startDhcpRenew },
-    { "getDhcpResults", "(Ljava/lang/String;Landroid/net/DhcpResults;)Z",  (void *)android_net_utils_getDhcpResults },
-    { "stopDhcp", "(Ljava/lang/String;)Z",  (void *)android_net_utils_stopDhcp },
-    { "releaseDhcpLease", "(Ljava/lang/String;)Z",  (void *)android_net_utils_releaseDhcpLease },
-    { "getDhcpError", "()Ljava/lang/String;", (void*) android_net_utils_getDhcpError },
     { "bindProcessToNetwork", "(I)Z", (void*) android_net_utils_bindProcessToNetwork },
     { "getBoundNetworkForProcess", "()I", (void*) android_net_utils_getBoundNetworkForProcess },
     { "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
@@ -318,28 +172,11 @@
     { "protectFromVpn", "(I)Z", (void*)android_net_utils_protectFromVpn },
     { "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess },
     { "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDhcpFilter },
+    { "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter },
 };
 
 int register_android_net_NetworkUtils(JNIEnv* env)
 {
-    jclass dhcpResultsClass = FindClassOrDie(env, "android/net/DhcpResults");
-
-    dhcpResultsFieldIds.clear = GetMethodIDOrDie(env, dhcpResultsClass, "clear", "()V");
-    dhcpResultsFieldIds.setIpAddress =GetMethodIDOrDie(env, dhcpResultsClass, "setIpAddress",
-            "(Ljava/lang/String;I)Z");
-    dhcpResultsFieldIds.setGateway = GetMethodIDOrDie(env, dhcpResultsClass, "setGateway",
-            "(Ljava/lang/String;)Z");
-    dhcpResultsFieldIds.addDns = GetMethodIDOrDie(env, dhcpResultsClass, "addDns",
-            "(Ljava/lang/String;)Z");
-    dhcpResultsFieldIds.setDomains = GetMethodIDOrDie(env, dhcpResultsClass, "setDomains",
-            "(Ljava/lang/String;)V");
-    dhcpResultsFieldIds.setServerAddress = GetMethodIDOrDie(env, dhcpResultsClass,
-            "setServerAddress", "(Ljava/lang/String;)Z");
-    dhcpResultsFieldIds.setLeaseDuration = GetMethodIDOrDie(env, dhcpResultsClass,
-            "setLeaseDuration", "(I)V");
-    dhcpResultsFieldIds.setVendorInfo = GetMethodIDOrDie(env, dhcpResultsClass, "setVendorInfo",
-            "(Ljava/lang/String;)V");
-
     return RegisterMethodsOrDie(env, NETUTILS_PKG_NAME, gNetworkUtilMethods,
                                 NELEM(gNetworkUtilMethods));
 }
diff --git a/core/tests/coretests/src/android/net/LinkPropertiesTest.java b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
index ea444a4..d5f6321 100644
--- a/core/tests/coretests/src/android/net/LinkPropertiesTest.java
+++ b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
@@ -23,6 +23,7 @@
 import android.net.RouteInfo;
 import android.system.OsConstants;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
 import junit.framework.TestCase;
 
 import java.net.InetAddress;
@@ -47,6 +48,11 @@
     private static LinkAddress LINKADDRV6 = new LinkAddress(ADDRV6, 128);
     private static LinkAddress LINKADDRV6LINKLOCAL = new LinkAddress("fe80::1/64");
 
+    // TODO: replace all calls to NetworkUtils.numericToInetAddress with calls to this method.
+    private InetAddress Address(String addrString) {
+        return NetworkUtils.numericToInetAddress(addrString);
+    }
+
     public void assertLinkPropertiesEqual(LinkProperties source, LinkProperties target) {
         // Check implementation of equals(), element by element.
         assertTrue(source.isIdenticalInterfaceName(target));
@@ -555,9 +561,13 @@
         assertTrue(v46lp.isProvisioned());
 
         assertEquals(ProvisioningChange.STILL_PROVISIONED,
+                LinkProperties.compareProvisioning(v4lp, v46lp));
+        assertEquals(ProvisioningChange.STILL_PROVISIONED,
                 LinkProperties.compareProvisioning(v6lp, v46lp));
         assertEquals(ProvisioningChange.LOST_PROVISIONING,
                 LinkProperties.compareProvisioning(v46lp, v6lp));
+        assertEquals(ProvisioningChange.LOST_PROVISIONING,
+                LinkProperties.compareProvisioning(v46lp, v4lp));
 
         // Check that losing and gaining a secondary router does not change
         // the provisioning status.
@@ -572,6 +582,7 @@
     }
 
     @SmallTest
+    @Suppress  // Failing.
     public void testIsReachable() {
         final LinkProperties v4lp = new LinkProperties();
         assertFalse(v4lp.isReachable(DNS1));
@@ -645,5 +656,26 @@
         assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope));
         assertTrue(v6lp.isReachable(kOnLinkDns));
         assertTrue(v6lp.isReachable(DNS6));
+
+        // Check isReachable on stacked links. This requires that the source IP address be assigned
+        // on the interface returned by the route lookup.
+        LinkProperties stacked = new LinkProperties();
+
+        // Can't add a stacked link without an interface name.
+        stacked.setInterfaceName("v4-test0");
+        v6lp.addStackedLink(stacked);
+
+        InetAddress stackedAddress = Address("192.0.0.4");
+        LinkAddress stackedLinkAddress = new LinkAddress(stackedAddress, 32);
+        assertFalse(v6lp.isReachable(stackedAddress));
+        stacked.addLinkAddress(stackedLinkAddress);
+        assertFalse(v6lp.isReachable(stackedAddress));
+        stacked.addRoute(new RouteInfo(stackedLinkAddress));
+        assertTrue(stacked.isReachable(stackedAddress));
+        assertTrue(v6lp.isReachable(stackedAddress));
+
+        assertFalse(v6lp.isReachable(DNS1));
+        stacked.addRoute(new RouteInfo((IpPrefix) null, stackedAddress));
+        assertTrue(v6lp.isReachable(DNS1));
     }
 }
diff --git a/core/tests/coretests/src/android/net/NetworkStatsTest.java b/core/tests/coretests/src/android/net/NetworkStatsTest.java
index a470de1..9074f8a 100644
--- a/core/tests/coretests/src/android/net/NetworkStatsTest.java
+++ b/core/tests/coretests/src/android/net/NetworkStatsTest.java
@@ -16,6 +16,9 @@
 
 package android.net;
 
+import static android.net.NetworkStats.ROAMING_ALL;
+import static android.net.NetworkStats.ROAMING_NO;
+import static android.net.NetworkStats.ROAMING_YES;
 import static android.net.NetworkStats.SET_DEFAULT;
 import static android.net.NetworkStats.SET_FOREGROUND;
 import static android.net.NetworkStats.SET_DBG_VPN_IN;
@@ -42,62 +45,103 @@
     private static final long TEST_START = 1194220800000L;
 
     public void testFindIndex() throws Exception {
-        final NetworkStats stats = new NetworkStats(TEST_START, 3)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 10)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 11)
-                .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 12);
+        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);
 
-        assertEquals(2, stats.findIndex(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE));
-        assertEquals(2, stats.findIndex(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE));
-        assertEquals(0, stats.findIndex(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE));
-        assertEquals(-1, stats.findIndex(TEST_IFACE, 6, SET_DEFAULT, TAG_NONE));
+        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));
     }
 
     public void testFindIndexHinted() {
         final NetworkStats stats = new NetworkStats(TEST_START, 3)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 10)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 11)
-                .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 12)
-                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 1024L, 8L, 0L, 0L, 10)
-                .addValues(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, 0L, 0L, 1024L, 8L, 11)
-                .addValues(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 12);
+                .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);
 
         // 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, hint));
-            assertEquals(1, stats.findIndexHinted(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, hint));
-            assertEquals(2, stats.findIndexHinted(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, hint));
-            assertEquals(3, stats.findIndexHinted(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, hint));
-            assertEquals(4, stats.findIndexHinted(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, hint));
-            assertEquals(5, stats.findIndexHinted(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, hint));
-            assertEquals(-1, stats.findIndexHinted(TEST_IFACE, 6, SET_DEFAULT, TAG_NONE, hint));
+            assertEquals(0, stats.findIndexHinted(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE,
+                    ROAMING_NO, hint));
+            assertEquals(1, stats.findIndexHinted(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE,
+                    ROAMING_NO, hint));
+            assertEquals(2, stats.findIndexHinted(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE,
+                    ROAMING_NO, hint));
+            assertEquals(3, stats.findIndexHinted(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE,
+                    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));
+            assertEquals(6, stats.findIndexHinted(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE,
+                    ROAMING_YES, hint));
+            assertEquals(-1, stats.findIndexHinted(TEST_IFACE, 6, SET_DEFAULT, TAG_NONE,
+                    ROAMING_NO, hint));
         }
     }
 
     public void testAddEntryGrow() throws Exception {
-        final NetworkStats stats = new NetworkStats(TEST_START, 2);
+        final NetworkStats stats = new NetworkStats(TEST_START, 3);
 
         assertEquals(0, stats.size());
-        assertEquals(2, stats.internalSize());
+        assertEquals(3, stats.internalSize());
 
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 1L, 1L, 2L, 2L, 3);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 2L, 2L, 2L, 2L, 4);
+        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(2, stats.size());
-        assertEquals(2, stats.internalSize());
+        assertEquals(3, stats.size());
+        assertEquals(3, stats.internalSize());
 
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 3L, 30L, 4L, 40L, 7);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 4L, 40L, 4L, 40L, 8);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 5L, 50L, 5L, 50L, 10);
+        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(5, stats.size());
-        assertTrue(stats.internalSize() >= 5);
+        assertEquals(7, stats.size());
+        assertTrue(stats.internalSize() >= 7);
 
-        assertValues(stats, 0, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 1L, 1L, 2L, 2L, 3);
-        assertValues(stats, 1, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 2L, 2L, 2L, 2L, 4);
-        assertValues(stats, 2, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 3L, 30L, 4L, 40L, 7);
-        assertValues(stats, 3, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 4L, 40L, 4L, 40L, 8);
-        assertValues(stats, 4, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 5L, 50L, 5L, 50L, 10);
+        assertValues(stats, 0, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1L, 1L,
+                2L, 2L, 3);
+        assertValues(stats, 1, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, 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,
+                40L, 4L, 40L, 7);
+        assertValues(stats, 4, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_NO, 5L,
+                50L, 4L, 40L, 8);
+        assertValues(stats, 5, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_NO, 6L,
+                60L, 5L, 50L, 10);
+        assertValues(stats, 6, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, ROAMING_YES, 7L,
+                70L, 5L, 50L, 11);
     }
 
     public void testCombineExisting() throws Exception {
@@ -105,16 +149,23 @@
 
         stats.addValues(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 10);
         stats.addValues(TEST_IFACE, 1001, SET_DEFAULT, 0xff, 128L, 1L, 128L, 1L, 2);
-        stats.combineValues(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, -128L, -1L, -128L, -1L, -1);
+        stats.combineValues(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, -128L, -1L,
+                -128L, -1L, -1);
 
-        assertValues(stats, 0, TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, 384L, 3L, 128L, 1L, 9);
-        assertValues(stats, 1, TEST_IFACE, 1001, SET_DEFAULT, 0xff, 128L, 1L, 128L, 1L, 2);
+        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);
 
         // 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, 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, 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, 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);
     }
 
     public void testSubtractIdenticalData() throws Exception {
@@ -129,8 +180,10 @@
         final NetworkStats result = after.subtract(before);
 
         // identical data should result in zero delta
-        assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0);
-        assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0);
+        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);
     }
 
     public void testSubtractIdenticalRows() throws Exception {
@@ -145,8 +198,10 @@
         final NetworkStats result = after.subtract(before);
 
         // expect delta between measurements
-        assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1L, 1L, 2L, 1L, 4);
-        assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 3L, 1L, 4L, 1L, 8);
+        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);
     }
 
     public void testSubtractNewRows() throws Exception {
@@ -162,9 +217,12 @@
         final NetworkStats result = after.subtract(before);
 
         // its okay to have new rows
-        assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0);
-        assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0);
-        assertValues(result, 2, TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 20);
+        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);
     }
 
     public void testSubtractMissingRows() throws Exception {
@@ -179,7 +237,8 @@
 
         // should silently drop omitted rows
         assertEquals(1, result.size());
-        assertValues(result, 0, TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 1L, 2L, 3L, 4L, 0);
+        assertValues(result, 0, TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1L,
+                2L, 3L, 4L, 0);
         assertEquals(4L, result.getTotalBytes());
     }
 
@@ -195,7 +254,7 @@
                 .addValues(TEST_IFACE, 101, SET_FOREGROUND, TAG_NONE, 32L, 0L, 0L, 0L, 0L);
         assertEquals(96L, uidSet.getTotalBytes());
 
-        final NetworkStats uidTag = new NetworkStats(TEST_START, 3)
+        final NetworkStats uidTag = new NetworkStats(TEST_START, 6)
                 .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
                 .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
                 .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L)
@@ -203,6 +262,15 @@
                 .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
                 .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L);
         assertEquals(64L, uidTag.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);
+        assertEquals(96L, uidRoaming.getTotalBytes());
     }
 
     public void testGroupedByIfaceEmpty() throws Exception {
@@ -215,62 +283,98 @@
 
     public void testGroupedByIfaceAll() throws Exception {
         final NetworkStats uidStats = new NetworkStats(TEST_START, 3)
-                .addValues(IFACE_ALL, 100, SET_ALL, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
-                .addValues(IFACE_ALL, 101, SET_FOREGROUND, TAG_NONE, 128L, 8L, 0L, 2L, 20L);
+                .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,
+                        2L, 20L)
+                .addValues(IFACE_ALL, 101, SET_ALL, TAG_NONE, ROAMING_YES, 128L, 8L, 0L, 2L,
+                        20L);
         final NetworkStats grouped = uidStats.groupedByIface();
 
-        assertEquals(2, uidStats.size());
+        assertEquals(3, uidStats.size());
         assertEquals(1, grouped.size());
 
-        assertValues(grouped, 0, IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE, 256L, 16L, 0L, 4L, 0L);
+        assertValues(grouped, 0, IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE, ROAMING_ALL, 384L, 24L, 0L,
+                6L, 0L);
     }
 
     public void testGroupedByIface() throws Exception {
-        final NetworkStats uidStats = new NetworkStats(TEST_START, 3)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 64L, 4L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 512L, 32L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 128L, 8L, 0L, 0L, 0L);
+        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,
+                        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);
 
         final NetworkStats grouped = uidStats.groupedByIface();
 
-        assertEquals(6, uidStats.size());
+        assertEquals(7, uidStats.size());
 
         assertEquals(2, grouped.size());
-        assertValues(grouped, 0, TEST_IFACE, UID_ALL, SET_ALL, TAG_NONE, 256L, 16L, 0L, 2L, 0L);
-        assertValues(grouped, 1, TEST_IFACE2, UID_ALL, SET_ALL, TAG_NONE, 1024L, 64L, 0L, 0L, 0L);
+        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);
     }
 
     public void testAddAllValues() {
         final NetworkStats first = new NetworkStats(TEST_START, 5)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, 32L, 0L, 0L, 0L, 0L);
+                .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);
 
         final NetworkStats second = new NetworkStats(TEST_START, 2)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L);
+                .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,
+                        0L, 0L, 0L)
+                .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, ROAMING_YES, 32L, 0L, 0L,
+                        0L, 0L);
 
         first.combineAllValues(second);
 
-        assertEquals(3, first.size());
-        assertValues(first, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 64L, 0L, 0L, 0L, 0L);
-        assertValues(first, 1, TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, 32L, 0L, 0L, 0L, 0L);
-        assertValues(first, 2, TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L);
+        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,
+                0L, 0L, 0L, 0L);
     }
 
     public void testGetTotal() {
-        final NetworkStats stats = new NetworkStats(TEST_START, 3)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 64L, 4L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 512L, 32L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 128L, 8L, 0L, 0L, 0L);
+        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,
+                        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);
 
-        assertValues(stats.getTotal(null), 1280L, 80L, 0L, 2L, 20L);
-        assertValues(stats.getTotal(null, 100), 1152L, 72L, 0L, 2L, 20L);
+        assertValues(stats.getTotal(null), 1408L, 88L, 0L, 2L, 20L);
+        assertValues(stats.getTotal(null, 100), 1280L, 80L, 0L, 2L, 20L);
         assertValues(stats.getTotal(null, 101), 128L, 8L, 0L, 0L, 0L);
 
         final HashSet<String> ifaces = Sets.newHashSet();
@@ -292,8 +396,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, 128L, 8L, 0L, 0L, 0L);
-        assertValues(after, 1, TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 128L, 8L, 0L, 0L, 0L);
+        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);
     }
 
     public void testClone() throws Exception {
@@ -351,76 +457,83 @@
         assertEquals(21, delta.size());
 
         // tunIface and TEST_IFACE entries are not changed.
-        assertValues(delta, 0, tunIface, 10100, SET_DEFAULT, TAG_NONE,
+        assertValues(delta, 0, tunIface, 10100, SET_DEFAULT, TAG_NONE, ROAMING_NO,
                 39605L, 46L, 12259L, 55L, 0L);
-        assertValues(delta, 1, tunIface, 10100, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
-        assertValues(delta, 2, tunIface, 10120, SET_DEFAULT, TAG_NONE,
+        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,
                 72667L, 197L, 43909L, 241L, 0L);
-        assertValues(delta, 3, tunIface, 10120, SET_FOREGROUND, TAG_NONE,
+        assertValues(delta, 3, tunIface, 10120, SET_FOREGROUND, TAG_NONE, ROAMING_NO,
                 9297L, 17L, 4128L, 21L, 0L);
-        assertValues(delta, 4, tunIface, tunUid, SET_DEFAULT, TAG_NONE,
+        assertValues(delta, 4, tunIface, tunUid, SET_DEFAULT, TAG_NONE, ROAMING_NO,
                 4983L, 10L, 1801L, 12L, 0L);
-        assertValues(delta, 5, tunIface, tunUid, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
-        assertValues(delta, 6, tunIface, 10120, SET_DEFAULT, testTag1,
+        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,
                 21691L, 41L, 13820L, 51L, 0L);
-        assertValues(delta, 7, tunIface, 10120, SET_FOREGROUND, testTag1, 1281L, 2L, 665L, 2L, 0L);
-        assertValues(delta, 8, TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, 1685L, 5L, 2070L, 6L, 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);
 
         // Existing underlying Iface entries are updated
-        assertValues(delta, 9, underlyingIface, 10100, SET_DEFAULT, TAG_NONE,
+        assertValues(delta, 9, underlyingIface, 10100, SET_DEFAULT, TAG_NONE, ROAMING_NO,
                 44783L, 54L, 13829L, 60L, 0L);
-        assertValues(delta, 10, underlyingIface, 10100, SET_FOREGROUND, TAG_NONE,
+        assertValues(delta, 10, underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, ROAMING_NO,
                 0L, 0L, 0L, 0L, 0L);
 
         // VPN underlying Iface entries are updated
-        assertValues(delta, 11, underlyingIface, tunUid, SET_DEFAULT, TAG_NONE,
+        assertValues(delta, 11, underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, ROAMING_NO,
                 28304L, 27L, 1719L, 12L, 0L);
-        assertValues(delta, 12, underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE,
+        assertValues(delta, 12, underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, 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,
-                72667L, 197L, 41872l, 219L, 0L);
-        assertContains(delta, underlyingIface, 10120, SET_FOREGROUND, TAG_NONE,
+        assertContains(delta, underlyingIface, 10120, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+                72667L, 197L, 41872L, 219L, 0L);
+        assertContains(delta, underlyingIface, 10120, SET_FOREGROUND, TAG_NONE, ROAMING_NO,
                 9297L, 17L, 3936, 19L, 0L);
-        assertContains(delta, underlyingIface, 10120, SET_DEFAULT, testTag1,
+        assertContains(delta, underlyingIface, 10120, SET_DEFAULT, testTag1, ROAMING_NO,
                 21691L, 41L, 13179L, 46L, 0L);
-        assertContains(delta, underlyingIface, 10120, SET_FOREGROUND, testTag1,
+        assertContains(delta, underlyingIface, 10120, SET_FOREGROUND, testTag1, ROAMING_NO,
                 1281L, 2L, 634L, 1L, 0L);
 
         // New entries are added for debug purpose
-        assertContains(delta, underlyingIface, 10100, SET_DBG_VPN_IN, TAG_NONE,
+        assertContains(delta, underlyingIface, 10100, SET_DBG_VPN_IN, TAG_NONE, ROAMING_NO,
                 39605L, 46L, 11690, 49, 0);
-        assertContains(delta, underlyingIface, 10120, SET_DBG_VPN_IN, TAG_NONE,
+        assertContains(delta, underlyingIface, 10120, SET_DBG_VPN_IN, TAG_NONE, ROAMING_NO,
                 81964, 214, 45808, 238, 0);
-        assertContains(delta, underlyingIface, tunUid, SET_DBG_VPN_IN, TAG_NONE,
+        assertContains(delta, underlyingIface, tunUid, SET_DBG_VPN_IN, TAG_NONE, ROAMING_NO,
                 4983, 10, 1717, 10, 0);
-        assertContains(delta, underlyingIface, tunUid, SET_DBG_VPN_OUT, TAG_NONE,
+        assertContains(delta, underlyingIface, tunUid, SET_DBG_VPN_OUT, TAG_NONE, ROAMING_ALL,
                 126552, 270, 59215, 297, 0);
 
     }
 
     private static void assertContains(NetworkStats stats,  String iface, int uid, int set,
-            int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
-        int index = stats.findIndex(iface, uid, set, tag);
+            int tag, int roaming, long rxBytes, long rxPackets, long txBytes, long txPackets,
+            long operations) {
+        int index = stats.findIndex(iface, uid, set, tag, roaming);
         assertTrue(index != -1);
-        assertValues(stats, index, iface, uid, set, tag,
+        assertValues(stats, index, iface, uid, set, tag, roaming,
                 rxBytes, rxPackets, txBytes, txPackets, operations);
     }
 
     private static void assertValues(NetworkStats stats, int index, String iface, int uid, int set,
-            int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
+            int tag, 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);
+        assertValues(entry, iface, uid, set, tag, roaming);
         assertValues(entry, rxBytes, rxPackets, txBytes, txPackets, operations);
     }
 
     private static void assertValues(
-            NetworkStats.Entry entry, String iface, int uid, int set, int tag) {
+            NetworkStats.Entry entry, String iface, int uid, int set, int tag, int roaming) {
         assertEquals(iface, entry.iface);
         assertEquals(uid, entry.uid);
         assertEquals(set, entry.set);
         assertEquals(tag, entry.tag);
+        assertEquals(roaming, entry.roaming);
     }
 
     private static void assertValues(NetworkStats.Entry entry, long rxBytes, long rxPackets,
diff --git a/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java b/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
index d3dd01a..327f3fd 100644
--- a/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
+++ b/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
@@ -16,6 +16,7 @@
 
 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;
@@ -156,7 +157,7 @@
 
     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);
+        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);
@@ -164,7 +165,7 @@
 
     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);
+        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);
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 327fb8a..428e192 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -33,7 +33,7 @@
 import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
 
 import android.annotation.Nullable;
-import android.app.AlarmManager;
+import android.app.BroadcastOptions;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -71,7 +71,9 @@
 import android.net.RouteInfo;
 import android.net.UidRange;
 import android.net.Uri;
+import android.net.metrics.ConnectivityServiceChangeEvent;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.FileUtils;
 import android.os.Handler;
@@ -85,7 +87,7 @@
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.SystemClock;
+import android.os.ResultReceiver;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -112,9 +114,9 @@
 import com.android.internal.net.VpnConfig;
 import com.android.internal.net.VpnInfo;
 import com.android.internal.net.VpnProfile;
-import com.android.internal.telephony.DctConstants;
 import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.MessageUtils;
 import com.android.internal.util.XmlUtils;
 import com.android.server.am.BatteryStatsService;
 import com.android.server.connectivity.DataConnectionStats;
@@ -152,11 +154,9 @@
 import java.util.HashSet;
 import java.util.SortedSet;
 import java.util.TreeSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * @hide
@@ -169,6 +169,7 @@
     private static final boolean VDBG = false;
 
     private static final boolean LOGD_RULES = false;
+    private static final boolean LOGD_BLOCKED_NETWORKINFO = true;
 
     // TODO: create better separation between radio types and network types
 
@@ -224,6 +225,9 @@
     private static final int ENABLED  = 1;
     private static final int DISABLED = 0;
 
+    private static final SparseArray<String> sMagicDecoderRing = MessageUtils.findMessageNames(
+            new Class[] { AsyncChannel.class, ConnectivityService.class, NetworkAgent.class });
+
     private enum ReapUnvalidatedNetworks {
         // Tear down networks that have no chance (e.g. even if validated) of becoming
         // the highest scoring network satisfying a NetworkRequest.  This should be passed when
@@ -253,11 +257,6 @@
     private static final int EVENT_APPLY_GLOBAL_HTTP_PROXY = 9;
 
     /**
-     * used internally to send a sticky broadcast delayed.
-     */
-    private static final int EVENT_SEND_STICKY_BROADCAST_INTENT = 11;
-
-    /**
      * PAC manager has received new port.
      */
     private static final int EVENT_PROXY_HAS_CHANGED = 16;
@@ -376,8 +375,6 @@
     private int mNetTransitionWakeLockTimeout;
     private final PowerManager.WakeLock mPendingIntentWakeLock;
 
-    private InetAddress mDefaultDns;
-
     // used in DBG mode to track inet condition reports
     private static final int INET_CONDITION_LOG_MAX_SIZE = 15;
     private ArrayList mInetLog;
@@ -455,7 +452,6 @@
 
         private static final boolean DBG = true;
         private static final boolean VDBG = false;
-        private static final String TAG = "CSLegacyTypeTracker";
 
         /**
          * Array of lists, one per legacy network type (e.g., TYPE_MOBILE_MMS).
@@ -608,12 +604,6 @@
             pw.decreaseIndent();
             pw.println();
         }
-
-        // This class needs its own log method because it has a different TAG.
-        private void log(String s) {
-            Slog.d(TAG, s);
-        }
-
     }
     private LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker();
 
@@ -628,7 +618,7 @@
 
         mDefaultRequest = createInternetRequestForTransport(-1);
         NetworkRequestInfo defaultNRI = new NetworkRequestInfo(null, mDefaultRequest,
-                new Binder(), NetworkRequestInfo.REQUEST);
+                new Binder(), NetworkRequestType.REQUEST);
         mNetworkRequests.put(mDefaultRequest, defaultNRI);
         mNetworkRequestInfoLogs.log("REGISTER " + defaultNRI);
 
@@ -650,19 +640,6 @@
             }
         }
 
-        // read our default dns server ip
-        String dns = Settings.Global.getString(context.getContentResolver(),
-                Settings.Global.DEFAULT_DNS_SERVER);
-        if (dns == null || dns.length() == 0) {
-            dns = context.getResources().getString(
-                    com.android.internal.R.string.config_default_dns_server);
-        }
-        try {
-            mDefaultDns = NetworkUtils.numericToInetAddress(dns);
-        } catch (IllegalArgumentException e) {
-            loge("Error setting defaultDns using " + dns);
-        }
-
         mReleasePendingIntentDelayMs = Settings.Secure.getInt(context.getContentResolver(),
                 Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, 5_000);
 
@@ -746,7 +723,7 @@
         mTestMode = SystemProperties.get("cm.test.mode").equals("true")
                 && SystemProperties.get("ro.build.type").equals("eng");
 
-        mTethering = new Tethering(mContext, mNetd, statsService, mHandler.getLooper());
+        mTethering = new Tethering(mContext, mNetd, statsService);
 
         mPermissionMonitor = new PermissionMonitor(mContext, mNetd);
 
@@ -754,6 +731,9 @@
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_USER_STARTING);
         intentFilter.addAction(Intent.ACTION_USER_STOPPING);
+        intentFilter.addAction(Intent.ACTION_USER_ADDED);
+        intentFilter.addAction(Intent.ACTION_USER_REMOVED);
+        intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
         mContext.registerReceiverAsUser(
                 mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
 
@@ -801,7 +781,7 @@
 
         if (enable) {
             handleRegisterNetworkRequest(new NetworkRequestInfo(
-                    null, mDefaultMobileDataRequest, new Binder(), NetworkRequestInfo.REQUEST));
+                    null, mDefaultMobileDataRequest, new Binder(), NetworkRequestType.REQUEST));
         } else {
             handleReleaseNetworkRequest(mDefaultMobileDataRequest, Process.SYSTEM_UID);
         }
@@ -955,6 +935,21 @@
         }
     }
 
+    private void maybeLogBlockedNetworkInfo(NetworkInfo ni, int uid) {
+        if (ni == null || !LOGD_BLOCKED_NETWORKINFO) return;
+        boolean removed = false;
+        boolean added = false;
+        synchronized (mBlockedAppUids) {
+            if (ni.getDetailedState() == DetailedState.BLOCKED && mBlockedAppUids.add(uid)) {
+                added = true;
+            } else if (ni.isConnected() && mBlockedAppUids.remove(uid)) {
+                removed = true;
+            }
+        }
+        if (added) log("Returning blocked NetworkInfo to uid=" + uid);
+        else if (removed) log("Returning unblocked NetworkInfo to uid=" + uid);
+    }
+
     /**
      * Return a filtered {@link NetworkInfo}, potentially marked
      * {@link DetailedState#BLOCKED} based on
@@ -965,10 +960,6 @@
             // network is blocked; clone and override state
             info = new NetworkInfo(info);
             info.setDetailedState(DetailedState.BLOCKED, null, null);
-            if (VDBG) {
-                log("returning Blocked NetworkInfo for ifname=" +
-                        lp.getInterfaceName() + ", uid=" + uid);
-            }
         }
         if (info != null && mLockdownTracker != null) {
             info = mLockdownTracker.augmentNetworkInfo(info);
@@ -989,13 +980,24 @@
         enforceAccessPermission();
         final int uid = Binder.getCallingUid();
         NetworkState state = getUnfilteredActiveNetworkState(uid);
-        return getFilteredNetworkInfo(state.networkInfo, state.linkProperties, uid);
+        NetworkInfo ni = getFilteredNetworkInfo(state.networkInfo, state.linkProperties, uid);
+        maybeLogBlockedNetworkInfo(ni, uid);
+        return ni;
     }
 
     @Override
     public Network getActiveNetwork() {
         enforceAccessPermission();
-        final int uid = Binder.getCallingUid();
+        return getActiveNetworkForUidInternal(Binder.getCallingUid());
+    }
+
+    @Override
+    public Network getActiveNetworkForUid(int uid) {
+        enforceConnectivityInternalPermission();
+        return getActiveNetworkForUidInternal(uid);
+    }
+
+    private Network getActiveNetworkForUidInternal(final int uid) {
         final int user = UserHandle.getUserId(uid);
         int vpnNetId = NETID_UNSET;
         synchronized (mVpns) {
@@ -1364,7 +1366,8 @@
                 bestRoute = RouteInfo.makeHostRoute(addr, bestRoute.getGateway(), iface);
             }
         }
-        if (DBG) log("Adding " + bestRoute + " for interface " + bestRoute.getInterface());
+        if (DBG) log("Adding legacy route " + bestRoute +
+                " for UID/PID " + uid + "/" + Binder.getCallingPid());
         try {
             mNetd.addLegacyRouteForNetId(netId, bestRoute, uid);
         } catch (Exception e) {
@@ -1522,10 +1525,11 @@
                 mInitialBroadcast = new Intent(intent);
             }
             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-            if (DBG) {
+            if (VDBG) {
                 log("sendStickyBroadcast: action=" + intent.getAction());
             }
 
+            Bundle options = null;
             final long ident = Binder.clearCallingIdentity();
             if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
                 final NetworkInfo ni = intent.getParcelableExtra(
@@ -1533,6 +1537,10 @@
                 if (ni.getType() == ConnectivityManager.TYPE_MOBILE_SUPL) {
                     intent.setAction(ConnectivityManager.CONNECTIVITY_ACTION_SUPL);
                     intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                } else {
+                    BroadcastOptions opts = BroadcastOptions.makeBasic();
+                    opts.setMaxManifestReceiverApiLevel(Build.VERSION_CODES.M);
+                    options = opts.toBundle();
                 }
                 final IBatteryStats bs = BatteryStatsService.getService();
                 try {
@@ -1543,7 +1551,7 @@
                 }
             }
             try {
-                mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+                mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL, options);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -1563,12 +1571,9 @@
         // load the global proxy at startup
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_APPLY_GLOBAL_HTTP_PROXY));
 
-        // Try bringing up tracker, but if KeyStore isn't ready yet, wait
-        // for user to unlock device.
-        if (!updateLockdownVpn()) {
-            final IntentFilter filter = new IntentFilter(Intent.ACTION_USER_PRESENT);
-            mContext.registerReceiver(mUserPresentReceiver, filter);
-        }
+        // Try bringing up tracker, but KeyStore won't be ready yet for secondary users so wait
+        // for user to unlock device too.
+        updateLockdownVpn();
 
         // Configure whether mobile data is always on.
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON));
@@ -1578,17 +1583,6 @@
         mPermissionMonitor.startMonitoring();
     }
 
-    private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            // Try creating lockdown tracker, since user present usually means
-            // unlocked keystore.
-            if (updateLockdownVpn()) {
-                mContext.unregisterReceiver(this);
-            }
-        }
-    };
-
     /**
      * Setup data activity tracking for the given network.
      *
@@ -1659,7 +1653,7 @@
         }
 
         if (LinkProperties.isValidMtu(mtu, newLp.hasGlobalIPv6Address()) == false) {
-            loge("Unexpected mtu value: " + mtu + ", " + iface);
+            if (mtu != 0) loge("Unexpected mtu value: " + mtu + ", " + iface);
             return;
         }
 
@@ -1670,7 +1664,7 @@
         }
 
         try {
-            if (DBG) log("Setting MTU size: " + iface + ", " + mtu);
+            if (VDBG) log("Setting MTU size: " + iface + ", " + mtu);
             mNetd.setMtu(iface, mtu);
         } catch (Exception e) {
             Slog.e(TAG, "exception in setMtu()" + e);
@@ -1706,7 +1700,7 @@
         if (tcpBufferSizes.equals(mCurrentTcpBufferSizes)) return;
 
         try {
-            if (DBG) Slog.d(TAG, "Setting tx/rx TCP buffers to " + tcpBufferSizes);
+            if (VDBG) Slog.d(TAG, "Setting tx/rx TCP buffers to " + tcpBufferSizes);
 
             final String prefix = "/sys/kernel/ipv4/tcp_";
             FileUtils.stringToFile(prefix + "rmem_min", values[0]);
@@ -1774,6 +1768,24 @@
         return false;
     }
 
+    private void dumpNetworkDiagnostics(IndentingPrintWriter pw) {
+        final List<NetworkDiagnostics> netDiags = new ArrayList<NetworkDiagnostics>();
+        final long DIAG_TIME_MS = 5000;
+        for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
+            // Start gathering diagnostic information.
+            netDiags.add(new NetworkDiagnostics(
+                    nai.network,
+                    new LinkProperties(nai.linkProperties),  // Must be a copy.
+                    DIAG_TIME_MS));
+        }
+
+        for (NetworkDiagnostics netDiag : netDiags) {
+            pw.println();
+            netDiag.waitForMeasurements();
+            netDiag.dump(pw);
+        }
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
@@ -1786,23 +1798,8 @@
             return;
         }
 
-        final List<NetworkDiagnostics> netDiags = new ArrayList<NetworkDiagnostics>();
         if (argsContain(args, "--diag")) {
-            final long DIAG_TIME_MS = 5000;
-            for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
-                // Start gathering diagnostic information.
-                netDiags.add(new NetworkDiagnostics(
-                        nai.network,
-                        new LinkProperties(nai.linkProperties),  // Must be a copy.
-                        DIAG_TIME_MS));
-            }
-
-            for (NetworkDiagnostics netDiag : netDiags) {
-                pw.println();
-                netDiag.waitForMeasurements();
-                netDiag.dump(pw);
-            }
-
+            dumpNetworkDiagnostics(pw);
             return;
         }
 
@@ -1868,6 +1865,8 @@
         pw.println();
         mKeepaliveTracker.dump(pw);
 
+        pw.println();
+
         if (mInetLog != null && mInetLog.size() > 0) {
             pw.println();
             pw.println("Inet condition reports:");
@@ -1898,11 +1897,12 @@
         }
     }
 
-    private boolean isLiveNetworkAgent(NetworkAgentInfo nai, String msg) {
+    private boolean isLiveNetworkAgent(NetworkAgentInfo nai, int what) {
         if (nai.network == null) return false;
         final NetworkAgentInfo officialNai = getNetworkAgentInfoForNetwork(nai.network);
         if (officialNai != null && officialNai.equals(nai)) return true;
         if (officialNai != null || VDBG) {
+            final String msg = sMagicDecoderRing.get(what, Integer.toString(what));
             loge(msg + " - isLiveNetworkAgent found mismatched netId: " + officialNai +
                 " - " + nai);
         }
@@ -1910,7 +1910,7 @@
     }
 
     private boolean isRequest(NetworkRequest request) {
-        return mNetworkRequests.get(request).isRequest;
+        return mNetworkRequests.get(request).isRequest();
     }
 
     // must be stateless - things change under us.
@@ -1919,10 +1919,10 @@
             super(looper);
         }
 
-        @Override
-        public void handleMessage(Message msg) {
-            NetworkInfo info;
+        private boolean maybeHandleAsyncChannelMessage(Message msg) {
             switch (msg.what) {
+                default:
+                    return false;
                 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
                     handleAsyncChannelHalfConnect(msg);
                     break;
@@ -1936,69 +1936,58 @@
                     handleAsyncChannelDisconnected(msg);
                     break;
                 }
+            }
+            return true;
+        }
+
+        private void maybeHandleNetworkAgentMessage(Message msg) {
+            NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
+            if (nai == null) {
+                if (VDBG) {
+                    final String what = sMagicDecoderRing.get(msg.what, Integer.toString(msg.what));
+                    log(String.format("%s from unknown NetworkAgent", what));
+                }
+                return;
+            }
+
+            switch (msg.what) {
                 case NetworkAgent.EVENT_NETWORK_CAPABILITIES_CHANGED: {
-                    NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
-                    if (nai == null) {
-                        loge("EVENT_NETWORK_CAPABILITIES_CHANGED from unknown NetworkAgent");
-                    } else {
-                        final NetworkCapabilities networkCapabilities =
-                                (NetworkCapabilities)msg.obj;
-                        if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL) ||
-                                networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
-                            Slog.wtf(TAG, "BUG: " + nai + " has CS-managed capability.");
-                        }
-                        if (nai.created && !nai.networkCapabilities.equalImmutableCapabilities(
-                                networkCapabilities)) {
-                            Slog.wtf(TAG, "BUG: " + nai + " changed immutable capabilities: "
-                                    + nai.networkCapabilities + " -> " + networkCapabilities);
-                        }
-                        updateCapabilities(nai, networkCapabilities);
+                    final NetworkCapabilities networkCapabilities = (NetworkCapabilities) msg.obj;
+                    if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL) ||
+                            networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
+                        Slog.wtf(TAG, "BUG: " + nai + " has CS-managed capability.");
                     }
+                    if (nai.created && !nai.networkCapabilities.equalImmutableCapabilities(
+                            networkCapabilities)) {
+                        Slog.wtf(TAG, "BUG: " + nai + " changed immutable capabilities: "
+                                + nai.networkCapabilities + " -> " + networkCapabilities);
+                    }
+                    updateCapabilities(nai, networkCapabilities);
                     break;
                 }
                 case NetworkAgent.EVENT_NETWORK_PROPERTIES_CHANGED: {
-                    NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
-                    if (nai == null) {
-                        loge("NetworkAgent not found for EVENT_NETWORK_PROPERTIES_CHANGED");
-                    } else {
-                        if (VDBG) {
-                            log("Update of LinkProperties for " + nai.name() +
-                                    "; created=" + nai.created);
-                        }
-                        LinkProperties oldLp = nai.linkProperties;
-                        synchronized (nai) {
-                            nai.linkProperties = (LinkProperties)msg.obj;
-                        }
-                        if (nai.created) updateLinkProperties(nai, oldLp);
+                    if (VDBG) {
+                        log("Update of LinkProperties for " + nai.name() +
+                                "; created=" + nai.created);
                     }
+                    LinkProperties oldLp = nai.linkProperties;
+                    synchronized (nai) {
+                        nai.linkProperties = (LinkProperties)msg.obj;
+                    }
+                    if (nai.created) updateLinkProperties(nai, oldLp);
                     break;
                 }
                 case NetworkAgent.EVENT_NETWORK_INFO_CHANGED: {
-                    NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
-                    if (nai == null) {
-                        loge("EVENT_NETWORK_INFO_CHANGED from unknown NetworkAgent");
-                        break;
-                    }
-                    info = (NetworkInfo) msg.obj;
+                    NetworkInfo info = (NetworkInfo) msg.obj;
                     updateNetworkInfo(nai, info);
                     break;
                 }
                 case NetworkAgent.EVENT_NETWORK_SCORE_CHANGED: {
-                    NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
-                    if (nai == null) {
-                        loge("EVENT_NETWORK_SCORE_CHANGED from unknown NetworkAgent");
-                        break;
-                    }
                     Integer score = (Integer) msg.obj;
                     if (score != null) updateNetworkScore(nai, score.intValue());
                     break;
                 }
                 case NetworkAgent.EVENT_UID_RANGES_ADDED: {
-                    NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
-                    if (nai == null) {
-                        loge("EVENT_UID_RANGES_ADDED from unknown NetworkAgent");
-                        break;
-                    }
                     try {
                         mNetd.addVpnUidRanges(nai.network.netId, (UidRange[])msg.obj);
                     } catch (Exception e) {
@@ -2008,11 +1997,6 @@
                     break;
                 }
                 case NetworkAgent.EVENT_UID_RANGES_REMOVED: {
-                    NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
-                    if (nai == null) {
-                        loge("EVENT_UID_RANGES_REMOVED from unknown NetworkAgent");
-                        break;
-                    }
                     try {
                         mNetd.removeVpnUidRanges(nai.network.netId, (UidRange[])msg.obj);
                     } catch (Exception e) {
@@ -2022,11 +2006,6 @@
                     break;
                 }
                 case NetworkAgent.EVENT_SET_EXPLICITLY_SELECTED: {
-                    NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
-                    if (nai == null) {
-                        loge("EVENT_SET_EXPLICITLY_SELECTED from unknown NetworkAgent");
-                        break;
-                    }
                     if (nai.created && !nai.networkMisc.explicitlySelected) {
                         loge("ERROR: created network explicitly selected.");
                     }
@@ -2035,17 +2014,19 @@
                     break;
                 }
                 case NetworkAgent.EVENT_PACKET_KEEPALIVE: {
-                    NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
-                    if (nai == null) {
-                        loge("EVENT_PACKET_KEEPALIVE from unknown NetworkAgent");
-                        break;
-                    }
                     mKeepaliveTracker.handleEventPacketKeepalive(nai, msg);
                     break;
                 }
+            }
+        }
+
+        private boolean maybeHandleNetworkMonitorMessage(Message msg) {
+            switch (msg.what) {
+                default:
+                    return false;
                 case NetworkMonitor.EVENT_NETWORK_TESTED: {
                     NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj;
-                    if (isLiveNetworkAgent(nai, "EVENT_NETWORK_TESTED")) {
+                    if (isLiveNetworkAgent(nai, msg.what)) {
                         final boolean valid =
                                 (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID);
                         if (DBG) log(nai.name() + " validation " + (valid ? " passed" : "failed"));
@@ -2068,7 +2049,7 @@
                 }
                 case NetworkMonitor.EVENT_NETWORK_LINGER_COMPLETE: {
                     NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj;
-                    if (isLiveNetworkAgent(nai, "EVENT_NETWORK_LINGER_COMPLETE")) {
+                    if (isLiveNetworkAgent(nai, msg.what)) {
                         handleLingerComplete(nai);
                     }
                     break;
@@ -2100,6 +2081,14 @@
                     break;
                 }
             }
+            return true;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (!maybeHandleAsyncChannelMessage(msg) && !maybeHandleNetworkMonitorMessage(msg)) {
+                maybeHandleNetworkAgentMessage(msg);
+            }
         }
     }
 
@@ -2129,7 +2118,7 @@
                 if (VDBG) log("NetworkFactory connected");
                 // A network factory has connected.  Send it all current NetworkRequests.
                 for (NetworkRequestInfo nri : mNetworkRequests.values()) {
-                    if (nri.isRequest == false) continue;
+                    if (!nri.isRequest()) continue;
                     NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
                     ac.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK,
                             (nai != null ? nai.getCurrentScore() : 0), 0, nri.request);
@@ -2179,7 +2168,7 @@
             if (wasDefault) {
                 mDefaultInetConditionPublished = 0;
             }
-            notifyIfacesChanged();
+            notifyIfacesChangedForNetworkStats();
             // TODO - we shouldn't send CALLBACK_LOST to requests that can be satisfied
             // by other networks that are already connected. Perhaps that can be done by
             // sending all CALLBACK_LOST messages (for requests, not listens) at the end
@@ -2265,7 +2254,7 @@
     private void handleRegisterNetworkRequest(NetworkRequestInfo nri) {
         mNetworkRequests.put(nri.request, nri);
         mNetworkRequestInfoLogs.log("REGISTER " + nri);
-        if (!nri.isRequest) {
+        if (!nri.isRequest()) {
             for (NetworkAgentInfo network : mNetworkAgentInfos.values()) {
                 if (nri.request.networkCapabilities.hasSignalStrength() &&
                         network.satisfiesImmutableCapabilitiesOf(nri.request)) {
@@ -2274,7 +2263,7 @@
             }
         }
         rematchAllNetworksAndRequests(null, 0);
-        if (nri.isRequest && mNetworkForRequestId.get(nri.request.requestId) == null) {
+        if (nri.isRequest() && mNetworkForRequestId.get(nri.request.requestId) == null) {
             sendUpdatedScoreToFactories(nri.request, 0);
         }
     }
@@ -2295,7 +2284,7 @@
         for (NetworkRequestInfo nri : mNetworkRequests.values()) {
             // If this Network is already the highest scoring Network for a request, or if
             // there is hope for it to become one if it validated, then it is needed.
-            if (nri.isRequest && nai.satisfies(nri.request) &&
+            if (nri.isRequest() && nai.satisfies(nri.request) &&
                     (nai.networkRequests.get(nri.request.requestId) != null ||
                     // Note that this catches two important cases:
                     // 1. Unvalidated cellular will not be reaped when unvalidated WiFi
@@ -2319,11 +2308,11 @@
                 if (DBG) log("Attempt to release unowned NetworkRequest " + request);
                 return;
             }
-            if (DBG) log("releasing NetworkRequest " + request);
+            if (VDBG || (DBG && nri.isRequest())) log("releasing NetworkRequest " + request);
             nri.unlinkDeathRecipient();
             mNetworkRequests.remove(request);
             mNetworkRequestInfoLogs.log("RELEASE " + nri);
-            if (nri.isRequest) {
+            if (nri.isRequest()) {
                 // Find all networks that are satisfying this request and remove the request
                 // from their request lists.
                 // TODO - it's my understanding that for a request there is only a single
@@ -2332,7 +2321,7 @@
                 for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
                     if (nai.networkRequests.get(nri.request.requestId) != null) {
                         nai.networkRequests.remove(nri.request.requestId);
-                        if (DBG) {
+                        if (VDBG) {
                             log(" Removing from current network " + nai.name() +
                                     ", leaving " + nai.networkRequests.size() +
                                     " requests.");
@@ -2443,14 +2432,14 @@
     }
 
     private void scheduleUnvalidatedPrompt(NetworkAgentInfo nai) {
-        if (DBG) log("scheduleUnvalidatedPrompt " + nai.network);
+        if (VDBG) log("scheduleUnvalidatedPrompt " + nai.network);
         mHandler.sendMessageDelayed(
                 mHandler.obtainMessage(EVENT_PROMPT_UNVALIDATED, nai.network),
                 PROMPT_UNVALIDATED_DELAY_MS);
     }
 
     private void handlePromptUnvalidated(Network network) {
-        if (DBG) log("handlePromptUnvalidated " + network);
+        if (VDBG) log("handlePromptUnvalidated " + network);
         NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
 
         // Only prompt if the network is unvalidated and was explicitly selected by the user, and if
@@ -2493,11 +2482,14 @@
                             break;
                         }
                     }
-                    if (msg.what == EVENT_EXPIRE_NET_TRANSITION_WAKELOCK) {
-                        log("Failed to find a new network - expiring NetTransition Wakelock");
-                    } else {
-                        log("NetTransition Wakelock (" + (causedBy == null ? "unknown" : causedBy) +
-                                " cleared because we found a replacement network");
+                    if (VDBG) {
+                        if (msg.what == EVENT_EXPIRE_NET_TRANSITION_WAKELOCK) {
+                            log("Failed to find a new network - expiring NetTransition Wakelock");
+                        } else {
+                            log("NetTransition Wakelock (" +
+                                    (causedBy == null ? "unknown" : causedBy) +
+                                    " cleared because we found a replacement network");
+                        }
                     }
                     break;
                 }
@@ -2505,11 +2497,6 @@
                     handleDeprecatedGlobalHttpProxy();
                     break;
                 }
-                case EVENT_SEND_STICKY_BROADCAST_INTENT: {
-                    Intent intent = (Intent)msg.obj;
-                    sendStickyBroadcast(intent);
-                    break;
-                }
                 case EVENT_PROXY_HAS_CHANGED: {
                     handleApplyDefaultProxy((ProxyInfo)msg.obj);
                     break;
@@ -2673,18 +2660,37 @@
     // if ro.tether.denied = true we default to no tethering
     // gservices could set the secure setting to 1 though to enable it on a build where it
     // had previously been turned off.
+    @Override
     public boolean isTetheringSupported() {
         enforceTetherAccessPermission();
         int defaultVal = (SystemProperties.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 && ((mTethering.getTetherableUsbRegexs().length != 0 ||
+        return tetherEnabledInSettings && mUserManager.isAdminUser() &&
+                ((mTethering.getTetherableUsbRegexs().length != 0 ||
                 mTethering.getTetherableWifiRegexs().length != 0 ||
                 mTethering.getTetherableBluetoothRegexs().length != 0) &&
                 mTethering.getUpstreamIfaceTypes().length != 0);
     }
 
+    @Override
+    public void startTethering(int type, ResultReceiver receiver,
+            boolean showProvisioningUi) {
+        ConnectivityManager.enforceTetherChangePermission(mContext);
+        if (!isTetheringSupported()) {
+            receiver.send(ConnectivityManager.TETHER_ERROR_UNSUPPORTED, null);
+            return;
+        }
+        mTethering.startTethering(type, receiver, showProvisioningUi);
+    }
+
+    @Override
+    public void stopTethering(int type) {
+        ConnectivityManager.enforceTetherChangePermission(mContext);
+        mTethering.stopTethering(type);
+    }
+
     // Called when we lose the default network and have no replacement yet.
     // This will automatically be cleared after X seconds or a new default network
     // becomes CONNECTED, whichever happens first.  The timer is started by the
@@ -3201,11 +3207,6 @@
         // Tear down existing lockdown if profile was removed
         mLockdownEnabled = LockdownVpnTracker.isEnabled();
         if (mLockdownEnabled) {
-            if (!mKeyStore.isUnlocked()) {
-                Slog.w(TAG, "KeyStore locked; unable to create LockdownTracker");
-                return false;
-            }
-
             final String profileName = new String(mKeyStore.get(Credentials.LOCKDOWN_VPN));
             final VpnProfile profile = VpnProfile.decode(
                     profileName, mKeyStore.get(Credentials.VPN + profileName));
@@ -3216,8 +3217,12 @@
             }
             int user = UserHandle.getUserId(Binder.getCallingUid());
             synchronized(mVpns) {
-                setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, mVpns.get(user),
-                            profile));
+                Vpn vpn = mVpns.get(user);
+                if (vpn == null) {
+                    Slog.w(TAG, "VPN for user " + user + " not ready yet. Skipping lockdown");
+                    return false;
+                }
+                setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, vpn, profile));
             }
         } else {
             setLockdownTracker(null);
@@ -3258,6 +3263,76 @@
         }
     }
 
+    /**
+     * Sets up or tears down the always-on VPN for user {@param user} as appropriate.
+     *
+     * @return {@code false} in case of errors; {@code true} otherwise.
+     */
+    private boolean updateAlwaysOnVpn(int user) {
+        final String lockdownPackage = getAlwaysOnVpnPackage(user);
+        if (lockdownPackage == null) {
+            return true;
+        }
+
+        // Create an intent to start the VPN service declared in the app's manifest.
+        Intent serviceIntent = new Intent(VpnConfig.SERVICE_INTERFACE);
+        serviceIntent.setPackage(lockdownPackage);
+
+        try {
+            return mContext.startServiceAsUser(serviceIntent, UserHandle.of(user)) != null;
+        } catch (RuntimeException e) {
+            return false;
+        }
+    }
+
+    @Override
+    public boolean setAlwaysOnVpnPackage(int userId, String packageName) {
+        enforceConnectivityInternalPermission();
+        enforceCrossUserPermission(userId);
+
+        // Can't set always-on VPN if legacy VPN is already in lockdown mode.
+        if (LockdownVpnTracker.isEnabled()) {
+            return false;
+        }
+
+        // If the current VPN package is the same as the new one, this is a no-op
+        final String oldPackage = getAlwaysOnVpnPackage(userId);
+        if (TextUtils.equals(oldPackage, packageName)) {
+            return true;
+        }
+
+        synchronized (mVpns) {
+            Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                Slog.w(TAG, "User " + userId + " has no Vpn configuration");
+                return false;
+            }
+            if (!vpn.setAlwaysOnPackage(packageName)) {
+                return false;
+            }
+            if (!updateAlwaysOnVpn(userId)) {
+                vpn.setAlwaysOnPackage(null);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public String getAlwaysOnVpnPackage(int userId) {
+        enforceConnectivityInternalPermission();
+        enforceCrossUserPermission(userId);
+
+        synchronized (mVpns) {
+            Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                Slog.w(TAG, "User " + userId + " has no Vpn configuration");
+                return null;
+            }
+            return vpn.getAlwaysOnPackage();
+        }
+    }
+
     @Override
     public int checkMobileProvisioning(int suggestedTimeOutMs) {
         // TODO: Remove?  Any reason to trigger a provisioning check?
@@ -3268,10 +3343,6 @@
     private static enum NotificationType { SIGN_IN, NO_INTERNET; };
 
     private void setProvNotificationVisible(boolean visible, int networkType, String action) {
-        if (DBG) {
-            log("setProvNotificationVisible: E visible=" + visible + " networkType=" + networkType
-                + " action=" + action);
-        }
         Intent intent = new Intent(action);
         PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
         // Concatenate the range of types onto the range of NetIDs.
@@ -3298,7 +3369,7 @@
     private void setProvNotificationVisibleIntent(boolean visible, int id,
             NotificationType notifyType, int networkType, String extraInfo, PendingIntent intent,
             boolean highPriority) {
-        if (DBG) {
+        if (VDBG || (DBG && visible)) {
             log("setProvNotificationVisibleIntent " + notifyType + " visible=" + visible
                     + " networkType=" + getNetworkTypeName(networkType)
                     + " extraInfo=" + extraInfo + " highPriority=" + highPriority);
@@ -3514,6 +3585,11 @@
             userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, userId);
             mVpns.put(userId, userVpn);
         }
+        if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
+            updateLockdownVpn();
+        } else {
+            updateAlwaysOnVpn(userId);
+        }
     }
 
     private void onUserStop(int userId) {
@@ -3527,6 +3603,35 @@
         }
     }
 
+    private void onUserAdded(int userId) {
+        synchronized(mVpns) {
+            final int vpnsSize = mVpns.size();
+            for (int i = 0; i < vpnsSize; i++) {
+                Vpn vpn = mVpns.valueAt(i);
+                vpn.onUserAdded(userId);
+            }
+        }
+    }
+
+    private void onUserRemoved(int userId) {
+        synchronized(mVpns) {
+            final int vpnsSize = mVpns.size();
+            for (int i = 0; i < vpnsSize; i++) {
+                Vpn vpn = mVpns.valueAt(i);
+                vpn.onUserRemoved(userId);
+            }
+        }
+    }
+
+    private void onUserUnlocked(int userId) {
+        // User present may be sent because of an unlock, which might mean an unlocked keystore.
+        if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
+            updateLockdownVpn();
+        } else {
+            updateAlwaysOnVpn(userId);
+        }
+    }
+
     private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -3538,6 +3643,12 @@
                 onUserStart(userId);
             } else if (Intent.ACTION_USER_STOPPING.equals(action)) {
                 onUserStop(userId);
+            } else if (Intent.ACTION_USER_ADDED.equals(action)) {
+                onUserAdded(userId);
+            } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
+                onUserRemoved(userId);
+            } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
+                onUserUnlocked(userId);
             }
         }
     };
@@ -3560,13 +3671,36 @@
     }
 
     /**
+     * A NetworkRequest as registered by an application can be one of three
+     * types:
+     *
+     *     - "listen", for which the framework will issue callbacks about any
+     *       and all networks that match the specified NetworkCapabilities,
+     *
+     *     - "request", capable of causing a specific network to be created
+     *       first (e.g. a telephony DUN request), the framework will issue
+     *       callbacks about the single, highest scoring current network
+     *       (if any) that matches the specified NetworkCapabilities, or
+     *
+     *     - "track the default network", a hybrid of the two designed such
+     *       that the framework will issue callbacks for the single, highest
+     *       scoring current network (if any) that matches the capabilities of
+     *       the default Internet request (mDefaultRequest), but which cannot
+     *       cause the framework to either create or retain the existence of
+     *       any specific network.
+     *
+     */
+    private static enum NetworkRequestType {
+        LISTEN,
+        TRACK_DEFAULT,
+        REQUEST
+    };
+
+    /**
      * Tracks info about the requester.
      * Also used to notice when the calling process dies so we can self-expire
      */
     private class NetworkRequestInfo implements IBinder.DeathRecipient {
-        static final boolean REQUEST = true;
-        static final boolean LISTEN = false;
-
         final NetworkRequest request;
         final PendingIntent mPendingIntent;
         boolean mPendingIntentSent;
@@ -3574,26 +3708,26 @@
         final int mPid;
         final int mUid;
         final Messenger messenger;
-        final boolean isRequest;
+        private final NetworkRequestType mType;
 
-        NetworkRequestInfo(NetworkRequest r, PendingIntent pi, boolean isRequest) {
+        NetworkRequestInfo(NetworkRequest r, PendingIntent pi, NetworkRequestType type) {
             request = r;
             mPendingIntent = pi;
             messenger = null;
             mBinder = null;
             mPid = getCallingPid();
             mUid = getCallingUid();
-            this.isRequest = isRequest;
+            mType = type;
         }
 
-        NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder, boolean isRequest) {
+        NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder, NetworkRequestType type) {
             super();
             messenger = m;
             request = r;
             mBinder = binder;
             mPid = getCallingPid();
             mUid = getCallingUid();
-            this.isRequest = isRequest;
+            mType = type;
             mPendingIntent = null;
 
             try {
@@ -3603,6 +3737,16 @@
             }
         }
 
+        private String typeString() {
+            switch (mType) {
+                case LISTEN: return "Listen";
+                case REQUEST: return "Request";
+                case TRACK_DEFAULT: return "Track default";
+                default:
+                    return "unknown type";
+            }
+        }
+
         void unlinkDeathRecipient() {
             if (mBinder != null) {
                 mBinder.unlinkToDeath(this, 0);
@@ -3615,8 +3759,27 @@
             releaseNetworkRequest(request);
         }
 
+        /**
+         * Returns true iff. the contained NetworkRequest is one that:
+         *
+         *     - should be associated with at most one satisfying network
+         *       at a time;
+         *
+         *     - should cause a network to be kept up if it is the only network
+         *       which can satisfy the NetworkReqeust.
+         *
+         * For full detail of how isRequest() is used for pairing Networks with
+         * NetworkRequests read rematchNetworkAndRequests().
+         *
+         * TODO: Rename to something more properly descriptive.
+         */
+        public boolean isRequest() {
+            return (mType == NetworkRequestType.TRACK_DEFAULT) ||
+                   (mType == NetworkRequestType.REQUEST);
+        }
+
         public String toString() {
-            return (isRequest ? "Request" : "Listen") +
+            return typeString() +
                     " from uid/pid:" + mUid + "/" + mPid +
                     " for " + request +
                     (mPendingIntent == null ? "" : " to trigger " + mPendingIntent);
@@ -3649,8 +3812,7 @@
         Bundle thresholds = new Bundle();
         thresholds.putIntegerArrayList("thresholds", thresholdsArray);
 
-        // TODO: Switch to VDBG.
-        if (DBG) {
+        if (VDBG || (DBG && !"CONNECT".equals(reason))) {
             String detail;
             if (request != null && request.networkCapabilities.hasSignalStrength()) {
                 detail = reason + " " + request.networkCapabilities.getSignalStrength();
@@ -3669,8 +3831,19 @@
     @Override
     public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
             Messenger messenger, int timeoutMs, IBinder binder, int legacyType) {
-        networkCapabilities = new NetworkCapabilities(networkCapabilities);
-        enforceNetworkRequestPermissions(networkCapabilities);
+        final NetworkRequestType type = (networkCapabilities == null)
+                ? NetworkRequestType.TRACK_DEFAULT
+                : NetworkRequestType.REQUEST;
+        // If the requested networkCapabilities is null, take them instead from
+        // the default network request. This allows callers to keep track of
+        // the system default network.
+        if (type == NetworkRequestType.TRACK_DEFAULT) {
+            networkCapabilities = new NetworkCapabilities(mDefaultRequest.networkCapabilities);
+            enforceAccessPermission();
+        } else {
+            networkCapabilities = new NetworkCapabilities(networkCapabilities);
+            enforceNetworkRequestPermissions(networkCapabilities);
+        }
         enforceMeteredApnPolicy(networkCapabilities);
         ensureRequestableCapabilities(networkCapabilities);
 
@@ -3678,10 +3851,15 @@
             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 + "'");
+        }
+
         NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
                 nextNetworkRequestId());
-        NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder,
-                NetworkRequestInfo.REQUEST);
+        NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder, type);
         if (DBG) log("requestNetwork for " + nri);
 
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST, nri));
@@ -3746,7 +3924,7 @@
         NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
                 nextNetworkRequestId());
         NetworkRequestInfo nri = new NetworkRequestInfo(networkRequest, operation,
-                NetworkRequestInfo.REQUEST);
+                NetworkRequestType.REQUEST);
         if (DBG) log("pendingRequest for " + nri);
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT,
                 nri));
@@ -3798,8 +3976,8 @@
         NetworkRequest networkRequest = new NetworkRequest(
                 new NetworkCapabilities(networkCapabilities), TYPE_NONE, nextNetworkRequestId());
         NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder,
-                NetworkRequestInfo.LISTEN);
-        if (DBG) log("listenForNetwork for " + nri);
+                NetworkRequestType.LISTEN);
+        if (VDBG) log("listenForNetwork for " + nri);
 
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
         return networkRequest;
@@ -3816,8 +3994,8 @@
         NetworkRequest networkRequest = new NetworkRequest(
                 new NetworkCapabilities(networkCapabilities), TYPE_NONE, nextNetworkRequestId());
         NetworkRequestInfo nri = new NetworkRequestInfo(networkRequest, operation,
-                NetworkRequestInfo.LISTEN);
-        if (DBG) log("pendingListenForNetwork for " + nri);
+                NetworkRequestType.LISTEN);
+        if (VDBG) log("pendingListenForNetwork for " + nri);
 
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
     }
@@ -3882,6 +4060,9 @@
     private final HashMap<Messenger, NetworkAgentInfo> mNetworkAgentInfos =
             new HashMap<Messenger, NetworkAgentInfo>();
 
+    @GuardedBy("mBlockedAppUids")
+    private final HashSet<Integer> mBlockedAppUids = new HashSet();
+
     // Note: if mDefaultRequest is changed, NetworkMonitor needs to be updated.
     private final NetworkRequest mDefaultRequest;
 
@@ -3947,14 +4128,8 @@
 //        }
         updateTcpBufferSizes(networkAgent);
 
-        // TODO: deprecate and remove mDefaultDns when we can do so safely. See http://b/18327075
-        // In L, we used it only when the network had Internet access but provided no DNS servers.
-        // For now, just disable it, and if disabling it doesn't break things, remove it.
-        // final boolean useDefaultDns = networkAgent.networkCapabilities.hasCapability(
-        //        NET_CAPABILITY_INTERNET);
-        final boolean useDefaultDns = false;
-        final boolean flushDns = updateRoutes(newLp, oldLp, netId);
-        updateDnses(newLp, oldLp, netId, flushDns, useDefaultDns);
+        updateRoutes(newLp, oldLp, netId);
+        updateDnses(newLp, oldLp, netId);
 
         updateClat(newLp, oldLp, networkAgent);
         if (isDefaultNetwork(networkAgent)) {
@@ -3964,7 +4139,7 @@
         }
         // TODO - move this check to cover the whole function
         if (!Objects.equals(newLp, oldLp)) {
-            notifyIfacesChanged();
+            notifyIfacesChangedForNetworkStats();
             notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED);
         }
 
@@ -4025,7 +4200,7 @@
         // do this twice, adding non-nexthop routes first, then routes they are dependent on
         for (RouteInfo route : routeDiff.added) {
             if (route.hasGateway()) continue;
-            if (DBG) log("Adding Route [" + route + "] to network " + netId);
+            if (VDBG) log("Adding Route [" + route + "] to network " + netId);
             try {
                 mNetd.addRoute(netId, route);
             } catch (Exception e) {
@@ -4036,7 +4211,7 @@
         }
         for (RouteInfo route : routeDiff.added) {
             if (route.hasGateway() == false) continue;
-            if (DBG) log("Adding Route [" + route + "] to network " + netId);
+            if (VDBG) log("Adding Route [" + route + "] to network " + netId);
             try {
                 mNetd.addRoute(netId, route);
             } catch (Exception e) {
@@ -4047,7 +4222,7 @@
         }
 
         for (RouteInfo route : routeDiff.removed) {
-            if (DBG) log("Removing Route [" + route + "] from network " + netId);
+            if (VDBG) log("Removing Route [" + route + "] from network " + netId);
             try {
                 mNetd.removeRoute(netId, route);
             } catch (Exception e) {
@@ -4057,37 +4232,24 @@
         return !routeDiff.added.isEmpty() || !routeDiff.removed.isEmpty();
     }
 
-    private void updateDnses(LinkProperties newLp, LinkProperties oldLp, int netId,
-                             boolean flush, boolean useDefaultDns) {
-        if (oldLp == null || (newLp.isIdenticalDnses(oldLp) == false)) {
-            Collection<InetAddress> dnses = newLp.getDnsServers();
-            if (dnses.size() == 0 && mDefaultDns != null && useDefaultDns) {
-                dnses = new ArrayList();
-                dnses.add(mDefaultDns);
-                if (DBG) {
-                    loge("no dns provided for netId " + netId + ", so using defaults");
-                }
-            }
-            if (DBG) log("Setting Dns servers for network " + netId + " to " + dnses);
-            try {
-                mNetd.setDnsServersForNetwork(netId, NetworkUtils.makeStrings(dnses),
-                    newLp.getDomains());
-            } catch (Exception e) {
-                loge("Exception in setDnsServersForNetwork: " + e);
-            }
-            final NetworkAgentInfo defaultNai = getDefaultNetwork();
-            if (defaultNai != null && defaultNai.network.netId == netId) {
-                setDefaultDnsSystemProperties(dnses);
-            }
-            flushVmDnsCache();
-        } else if (flush) {
-            try {
-                mNetd.flushNetworkDnsCache(netId);
-            } catch (Exception e) {
-                loge("Exception in flushNetworkDnsCache: " + e);
-            }
-            flushVmDnsCache();
+    private void updateDnses(LinkProperties newLp, LinkProperties oldLp, int netId) {
+        if (oldLp != null && newLp.isIdenticalDnses(oldLp)) {
+            return;  // no updating necessary
         }
+
+        Collection<InetAddress> dnses = newLp.getDnsServers();
+        if (DBG) log("Setting DNS servers for network " + netId + " to " + dnses);
+        try {
+            mNetd.setDnsServersForNetwork(
+                    netId, NetworkUtils.makeStrings(dnses), newLp.getDomains());
+        } catch (Exception e) {
+            loge("Exception in setDnsServersForNetwork: " + e);
+        }
+        final NetworkAgentInfo defaultNai = getDefaultNetwork();
+        if (defaultNai != null && defaultNai.network.netId == netId) {
+            setDefaultDnsSystemProperties(dnses);
+        }
+        flushVmDnsCache();
     }
 
     private void setDefaultDnsSystemProperties(Collection<InetAddress> dnses) {
@@ -4261,6 +4423,7 @@
 
     private void makeDefault(NetworkAgentInfo newNetwork) {
         if (DBG) log("Switching to new default network: " + newNetwork);
+        ConnectivityServiceChangeEvent.logEvent(newNetwork.network.netId);
         setupDataActivityTracking(newNetwork);
         try {
             mNetd.setDefaultNetId(newNetwork.network.netId);
@@ -4327,7 +4490,7 @@
             // check if it satisfies the NetworkCapabilities
             if (VDBG) log("  checking if request is satisfied: " + nri.request);
             if (satisfies) {
-                if (!nri.isRequest) {
+                if (!nri.isRequest()) {
                     // This is not a request, it's a callback listener.
                     // Add it to newNetwork regardless of score.
                     if (newNetwork.addRequest(nri.request)) addedRequests.add(nri);
@@ -4343,14 +4506,14 @@
                 }
                 if (currentNetwork == null ||
                         currentNetwork.getCurrentScore() < newNetwork.getCurrentScore()) {
-                    if (DBG) log("rematch for " + newNetwork.name());
+                    if (VDBG) log("rematch for " + newNetwork.name());
                     if (currentNetwork != null) {
-                        if (DBG) log("   accepting network in place of " + currentNetwork.name());
+                        if (VDBG) log("   accepting network in place of " + currentNetwork.name());
                         currentNetwork.networkRequests.remove(nri.request.requestId);
                         currentNetwork.networkLingered.add(nri.request);
                         affectedNetworks.add(currentNetwork);
                     } else {
-                        if (DBG) log("   accepting network in place of null");
+                        if (VDBG) log("   accepting network in place of null");
                     }
                     unlinger(newNetwork);
                     mNetworkForRequestId.put(nri.request.requestId, newNetwork);
@@ -4387,7 +4550,7 @@
                     mNetworkForRequestId.remove(nri.request.requestId);
                     sendUpdatedScoreToFactories(nri.request, 0);
                 } else {
-                    if (nri.isRequest == true) {
+                    if (nri.isRequest()) {
                         Slog.wtf(TAG, "BUG: Removing request " + nri.request.requestId + " from " +
                                 newNetwork.name() +
                                 " without updating mNetworkForRequestId or factories!");
@@ -4588,7 +4751,11 @@
         notifyLockdownVpn(networkAgent);
 
         if (oldInfo != null && oldInfo.getState() == state) {
-            if (VDBG) log("ignoring duplicate network state non-change");
+            if (oldInfo.isRoaming() != newInfo.isRoaming()) {
+                if (VDBG) log("roaming status changed, notifying NetworkStatsService");
+                notifyIfacesChangedForNetworkStats();
+            } else if (VDBG) log("ignoring duplicate network state non-change");
+            // In either case, no further work should be needed.
             return;
         }
         if (DBG) {
@@ -4618,7 +4785,7 @@
             }
             networkAgent.created = true;
             updateLinkProperties(networkAgent, null);
-            notifyIfacesChanged();
+            notifyIfacesChangedForNetworkStats();
 
             networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
             scheduleUnvalidatedPrompt(networkAgent);
@@ -4676,7 +4843,7 @@
     }
 
     private void updateNetworkScore(NetworkAgentInfo nai, int score) {
-        if (DBG) log("updateNetworkScore for " + nai.name() + " to " + score);
+        if (VDBG) log("updateNetworkScore for " + nai.name() + " to " + score);
         if (score < 0) {
             loge("updateNetworkScore for " + nai.name() + " got a negative score (" + score +
                     ").  Bumping score to min of 0");
@@ -4754,7 +4921,7 @@
     }
 
     protected void notifyNetworkCallbacks(NetworkAgentInfo networkAgent, int notifyType) {
-        if (DBG) log("notifyType " + notifyTypeToName(notifyType) + " for " + networkAgent.name());
+        if (VDBG) log("notifyType " + notifyTypeToName(notifyType) + " for " + networkAgent.name());
         for (int i = 0; i < networkAgent.networkRequests.size(); i++) {
             NetworkRequest nr = networkAgent.networkRequests.valueAt(i);
             NetworkRequestInfo nri = mNetworkRequests.get(nr);
@@ -4782,9 +4949,10 @@
     }
 
     /**
-     * Notify other system services that set of active ifaces has changed.
+     * Notify NetworkStatsService that the set of active ifaces has changed, or that one of the
+     * properties tracked by NetworkStatsService on an active iface has changed.
      */
-    private void notifyIfacesChanged() {
+    private void notifyIfacesChangedForNetworkStats() {
         try {
             mStatsService.forceUpdateIfaces();
         } catch (Exception ignored) {
@@ -4818,12 +4986,17 @@
             success = mVpns.get(user).setUnderlyingNetworks(networks);
         }
         if (success) {
-            notifyIfacesChanged();
+            notifyIfacesChangedForNetworkStats();
         }
         return success;
     }
 
     @Override
+    public String getCaptivePortalServerUrl() {
+        return NetworkMonitor.getCaptivePortalServerUrl(mContext);
+    }
+
+    @Override
     public void startNattKeepalive(Network network, int intervalSeconds, Messenger messenger,
             IBinder binder, String srcAddr, int srcPort, String dstAddr) {
         enforceKeepalivePermission();
@@ -4881,5 +5054,4 @@
             NetworkAgentInfo nai, NetworkRequest defaultRequest) {
         return new NetworkMonitor(context, handler, nai, defaultRequest);
     }
-
 }
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index 90c9ddf..9e1f6b8 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -60,7 +60,7 @@
 public class KeepaliveTracker {
 
     private static final String TAG = "KeepaliveTracker";
-    private static final boolean DBG = true;
+    private static final boolean DBG = false;
 
     public static final String PERMISSION = android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD;
 
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index a9eaeee..b390884 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -16,13 +16,11 @@
 
 package com.android.server.connectivity;
 
-import static android.net.ConnectivityManager.TYPE_MOBILE;
-import static android.net.ConnectivityManager.TYPE_WIFI;
-
 import java.net.Inet4Address;
 
 import android.content.Context;
 import android.net.InterfaceConfiguration;
+import android.net.ConnectivityManager;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.NetworkAgent;
@@ -34,6 +32,7 @@
 import android.util.Slog;
 
 import com.android.server.net.BaseNetworkObserver;
+import com.android.internal.util.ArrayUtils;
 
 /**
  * @hide
@@ -46,6 +45,13 @@
     // This must match the interface prefix in clatd.c.
     private static final String CLAT_PREFIX = "v4-";
 
+    // The network types we will start clatd on.
+    private static final int[] NETWORK_TYPES = {
+            ConnectivityManager.TYPE_MOBILE,
+            ConnectivityManager.TYPE_WIFI,
+            ConnectivityManager.TYPE_ETHERNET,
+    };
+
     private final INetworkManagementService mNMService;
 
     // ConnectivityService Handler for LinkProperties updates.
@@ -90,7 +96,7 @@
                 (nai.linkProperties != null) ? nai.linkProperties.hasIPv4Address() : false;
         // Only support clat on mobile and wifi for now, because these are the only IPv6-only
         // networks we can connect to.
-        return connected && !hasIPv4Address && (netType == TYPE_MOBILE || netType == TYPE_WIFI);
+        return connected && !hasIPv4Address && ArrayUtils.contains(NETWORK_TYPES, netType);
     }
 
     /**
@@ -221,7 +227,7 @@
     }
 
     private void maybeSetIpv6NdOffload(String iface, boolean on) {
-        if (mNetwork.networkInfo.getType() != TYPE_WIFI) {
+        if (mNetwork.networkInfo.getType() != ConnectivityManager.TYPE_WIFI) {
             return;
         }
         try {
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 0029279..c5d38cb 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -248,10 +248,10 @@
     }
 
     public String toString() {
-        return "NetworkAgentInfo{ ni{" + networkInfo + "}  network{" +
-                network + "}  lp{" +
-                linkProperties + "}  nc{" +
-                networkCapabilities + "}  Score{" + getCurrentScore() + "}  " +
+        return "NetworkAgentInfo{ ni{" + networkInfo + "}  " +
+                "network{" + network + "}  nethandle{" + network.getNetworkHandle() + "}  " +
+                "lp{" + linkProperties + "}  " +
+                "nc{" + networkCapabilities + "}  Score{" + getCurrentScore() + "}  " +
                 "everValidated{" + everValidated + "}  lastValidated{" + lastValidated + "}  " +
                 "created{" + created + "} lingering{" + lingering + "} " +
                 "explicitlySelected{" + networkMisc.explicitlySelected + "} " +
diff --git a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
index 5fd39c0..dc62609 100644
--- a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
+++ b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
@@ -49,7 +49,9 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Random;
 
@@ -107,27 +109,33 @@
     // so callers can wait for completion.
     private final CountDownLatch mCountDownLatch;
 
-    private class Measurement {
+    public class Measurement {
         private static final String SUCCEEDED = "SUCCEEDED";
         private static final String FAILED = "FAILED";
 
-        // TODO: Refactor to make these private for better encapsulation.
-        public String description = "";
-        public long startTime;
-        public long finishTime;
-        public String result = "";
-        public Thread thread;
+        private boolean succeeded;
 
-        public void recordSuccess(String msg) {
+        // Package private.  TODO: investigate better encapsulation.
+        String description = "";
+        long startTime;
+        long finishTime;
+        String result = "";
+        Thread thread;
+
+        public boolean checkSucceeded() { return succeeded; }
+
+        void recordSuccess(String msg) {
             maybeFixupTimes();
+            succeeded = true;
             result = SUCCEEDED + ": " + msg;
             if (mCountDownLatch != null) {
                 mCountDownLatch.countDown();
             }
         }
 
-        public void recordFailure(String msg) {
+        void recordFailure(String msg) {
             maybeFixupTimes();
+            succeeded = false;
             result = FAILED + ": " + msg;
             if (mCountDownLatch != null) {
                 mCountDownLatch.countDown();
@@ -265,6 +273,51 @@
         } catch (InterruptedException ignored) {}
     }
 
+    public List<Measurement> getMeasurements() {
+        // TODO: Consider moving waitForMeasurements() in here to minimize the
+        // chance of caller errors.
+
+        ArrayList<Measurement> measurements = new ArrayList(totalMeasurementCount());
+
+        // Sort measurements IPv4 first.
+        for (Map.Entry<InetAddress, Measurement> entry : mIcmpChecks.entrySet()) {
+            if (entry.getKey() instanceof Inet4Address) {
+                measurements.add(entry.getValue());
+            }
+        }
+        for (Map.Entry<Pair<InetAddress, InetAddress>, Measurement> entry :
+                mExplicitSourceIcmpChecks.entrySet()) {
+            if (entry.getKey().first instanceof Inet4Address) {
+                measurements.add(entry.getValue());
+            }
+        }
+        for (Map.Entry<InetAddress, Measurement> entry : mDnsUdpChecks.entrySet()) {
+            if (entry.getKey() instanceof Inet4Address) {
+                measurements.add(entry.getValue());
+            }
+        }
+
+        // IPv6 measurements second.
+        for (Map.Entry<InetAddress, Measurement> entry : mIcmpChecks.entrySet()) {
+            if (entry.getKey() instanceof Inet6Address) {
+                measurements.add(entry.getValue());
+            }
+        }
+        for (Map.Entry<Pair<InetAddress, InetAddress>, Measurement> entry :
+                mExplicitSourceIcmpChecks.entrySet()) {
+            if (entry.getKey().first instanceof Inet6Address) {
+                measurements.add(entry.getValue());
+            }
+        }
+        for (Map.Entry<InetAddress, Measurement> entry : mDnsUdpChecks.entrySet()) {
+            if (entry.getKey() instanceof Inet6Address) {
+                measurements.add(entry.getValue());
+            }
+        }
+
+        return measurements;
+    }
+
     public void dump(IndentingPrintWriter pw) {
         pw.println(TAG + ":" + mDescription);
         final long unfinished = mCountDownLatch.getCount();
@@ -276,30 +329,13 @@
         }
 
         pw.increaseIndent();
-        for (Map.Entry<InetAddress, Measurement> entry : mIcmpChecks.entrySet()) {
-            if (entry.getKey() instanceof Inet4Address) {
-                pw.println(entry.getValue().toString());
-            }
+
+        String prefix;
+        for (Measurement m : getMeasurements()) {
+            prefix = m.checkSucceeded() ? "." : "F";
+            pw.println(prefix + "  " + m.toString());
         }
-        for (Map.Entry<InetAddress, Measurement> entry : mIcmpChecks.entrySet()) {
-            if (entry.getKey() instanceof Inet6Address) {
-                pw.println(entry.getValue().toString());
-            }
-        }
-        for (Map.Entry<Pair<InetAddress, InetAddress>, Measurement> entry :
-                mExplicitSourceIcmpChecks.entrySet()) {
-            pw.println(entry.getValue().toString());
-        }
-        for (Map.Entry<InetAddress, Measurement> entry : mDnsUdpChecks.entrySet()) {
-            if (entry.getKey() instanceof Inet4Address) {
-                pw.println(entry.getValue().toString());
-            }
-        }
-        for (Map.Entry<InetAddress, Measurement> entry : mDnsUdpChecks.entrySet()) {
-            if (entry.getKey() instanceof Inet6Address) {
-                pw.println(entry.getValue().toString());
-            }
-        }
+
         pw.decreaseIndent();
     }
 
diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
index debda14..22cefd1 100644
--- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java
+++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
@@ -54,7 +54,7 @@
  */
 public class PermissionMonitor {
     private static final String TAG = "PermissionMonitor";
-    private static final boolean DBG = true;
+    private static final boolean DBG = false;
     private static final boolean SYSTEM = true;
     private static final boolean NETWORK = false;
 
diff --git a/services/net/java/android/net/apf/ApfCapabilities.java b/services/net/java/android/net/apf/ApfCapabilities.java
new file mode 100644
index 0000000..0ec50c4
--- /dev/null
+++ b/services/net/java/android/net/apf/ApfCapabilities.java
@@ -0,0 +1,51 @@
+/*
+ * 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 android.net.apf;
+
+/**
+ * APF program support capabilities.
+ *
+ * @hide
+ */
+public class ApfCapabilities {
+    /**
+     * Version of APF instruction set supported for packet filtering. 0 indicates no support for
+     * packet filtering using APF programs.
+     */
+    public final int apfVersionSupported;
+
+    /**
+     * Maximum size of APF program allowed.
+     */
+    public final int maximumApfProgramSize;
+
+    /**
+     * Format of packets passed to APF filter. Should be one of ARPHRD_*
+     */
+    public final int apfPacketFormat;
+
+    ApfCapabilities(int apfVersionSupported, int maximumApfProgramSize, int apfPacketFormat) {
+        this.apfVersionSupported = apfVersionSupported;
+        this.maximumApfProgramSize = maximumApfProgramSize;
+        this.apfPacketFormat = apfPacketFormat;
+    }
+
+    public String toString() {
+        return String.format("%s{version: %d, maxSize: %d format: %d}", getClass().getSimpleName(),
+                apfVersionSupported, maximumApfProgramSize, apfPacketFormat);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
index 97e16da..2f20a4b 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -52,18 +52,25 @@
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.Message;
 import android.os.MessageQueue;
+import android.os.Messenger;
 import android.os.MessageQueue.IdleHandler;
+import android.os.Process;
+import android.os.SystemClock;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 import android.util.LogPrinter;
 
+import com.android.internal.util.WakeupMessage;
 import com.android.server.connectivity.NetworkAgentInfo;
 import com.android.server.connectivity.NetworkMonitor;
+import com.android.server.net.NetworkPinner;
 
 import java.net.InetAddress;
 import java.util.concurrent.CountDownLatch;
@@ -84,10 +91,30 @@
 
     private BroadcastInterceptingContext mServiceContext;
     private WrappedConnectivityService mService;
-    private ConnectivityManager mCm;
+    private WrappedConnectivityManager mCm;
     private MockNetworkAgent mWiFiNetworkAgent;
     private MockNetworkAgent mCellNetworkAgent;
 
+    // This class exists to test bindProcessToNetwork and getBoundNetworkForProcess. These methods
+    // do not go through ConnectivityService but talk to netd directly, so they don't automatically
+    // reflect the state of our test ConnectivityService.
+    private class WrappedConnectivityManager extends ConnectivityManager {
+        private Network mFakeBoundNetwork;
+
+        public synchronized boolean bindProcessToNetwork(Network network) {
+            mFakeBoundNetwork = network;
+            return true;
+        }
+
+        public synchronized Network getBoundNetworkForProcess() {
+            return mFakeBoundNetwork;
+        }
+
+        public WrappedConnectivityManager(Context context, ConnectivityService service) {
+            super(context, service);
+        }
+    }
+
     private class MockContext extends BroadcastInterceptingContext {
         MockContext(Context base) {
             super(base);
@@ -135,9 +162,11 @@
                 assertNull("BUG: only one idle handler allowed", mIdleHandler);
                 mIdleHandler = new IdleHandler() {
                     public boolean queueIdle() {
-                        cv.open();
-                        mIdleHandler = null;
-                        return false;  // Remove the handler.
+                        synchronized (queue) {
+                            cv.open();
+                            mIdleHandler = null;
+                            return false;  // Remove the handler.
+                        }
                     }
                 };
                 queue.addIdleHandler(mIdleHandler);
@@ -481,6 +510,35 @@
         }
     }
 
+    private class FakeWakeupMessage extends WakeupMessage {
+        private static final int UNREASONABLY_LONG_WAIT = 1000;
+
+        public FakeWakeupMessage(Context context, Handler handler, String cmdName, int cmd) {
+            super(context, handler, cmdName, cmd);
+        }
+
+        @Override
+        public void schedule(long when) {
+            long delayMs = when - SystemClock.elapsedRealtime();
+            if (delayMs < 0) delayMs = 0;
+            if (delayMs > UNREASONABLY_LONG_WAIT) {
+                fail("Attempting to send msg more than " + UNREASONABLY_LONG_WAIT +
+                        "ms into the future: " + delayMs);
+            }
+            mHandler.sendEmptyMessageDelayed(mCmd, delayMs);
+        }
+
+        @Override
+        public void cancel() {
+            mHandler.removeMessages(mCmd);
+        }
+
+        @Override
+        public void onAlarm() {
+            throw new AssertionError("Should never happen. Update this fake.");
+        }
+    }
+
     // NetworkMonitor implementation allowing overriding of Internet connectivity probe result.
     private class WrappedNetworkMonitor extends NetworkMonitor {
         // HTTP response code fed back to NetworkMonitor for Internet connectivity probe.
@@ -495,6 +553,12 @@
         protected int isCaptivePortal() {
             return gen204ProbeResult;
         }
+
+        @Override
+        protected WakeupMessage makeWakeupMessage(
+                Context context, Handler handler, String cmdName, int cmd) {
+            return new FakeWakeupMessage(context, handler, cmdName, cmd);
+        }
     }
 
     private class WrappedConnectivityService extends ConnectivityService {
@@ -572,10 +636,10 @@
         int delays = 0;
         while (!criteria.get()) {
             try {
-                Thread.sleep(100);
+                Thread.sleep(50);
             } catch (InterruptedException e) {
             }
-            if (++delays == 5) fail();
+            if (++delays == 10) fail();
         }
     }
 
@@ -591,6 +655,14 @@
     public void setUp() throws Exception {
         super.setUp();
 
+        NetworkMonitor.SetDefaultLingerTime(120);
+
+        // InstrumentationTestRunner prepares a looper, but AndroidJUnitRunner does not.
+        // http://b/25897652 .
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+
         mServiceContext = new MockContext(getContext());
         mService = new WrappedConnectivityService(mServiceContext,
                 mock(INetworkManagementService.class),
@@ -598,7 +670,8 @@
                 mock(INetworkPolicyManager.class));
 
         mService.systemReady();
-        mCm = new ConnectivityManager(getContext(), mService);
+        mCm = new WrappedConnectivityManager(getContext(), mService);
+        mCm.bindProcessToNetwork(null);
     }
 
     private int transportToLegacyType(int transport) {
@@ -618,6 +691,7 @@
         assertEquals(transportToLegacyType(transport), mCm.getActiveNetworkInfo().getType());
         // Test getActiveNetwork()
         assertNotNull(mCm.getActiveNetwork());
+        assertEquals(mCm.getActiveNetwork(), mCm.getActiveNetworkForUid(Process.myUid()));
         switch (transport) {
             case TRANSPORT_WIFI:
                 assertEquals(mCm.getActiveNetwork(), mWiFiNetworkAgent.getNetwork());
@@ -641,6 +715,7 @@
         assertNull(mCm.getActiveNetworkInfo());
         // Test getActiveNetwork()
         assertNull(mCm.getActiveNetwork());
+        assertNull(mCm.getActiveNetworkForUid(Process.myUid()));
         // Test getAllNetworks()
         assertEquals(0, mCm.getAllNetworks().length);
     }
@@ -665,8 +740,6 @@
 
     @LargeTest
     public void testLingering() throws Exception {
-        // Decrease linger timeout to the minimum allowed by AlarmManagerService.
-        NetworkMonitor.SetDefaultLingerTime(5000);
         verifyNoNetwork();
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
@@ -693,10 +766,8 @@
         assertTrue(mCm.getAllNetworks()[0].equals(mCellNetworkAgent.getNetwork()) ||
                 mCm.getAllNetworks()[1].equals(mCellNetworkAgent.getNetwork()));
         // Test cellular linger timeout.
-        try {
-            Thread.sleep(6000);
-        } catch (InterruptedException e) {
-        }
+        waitFor(new Criteria() {
+                public boolean get() { return mCm.getAllNetworks().length == 1; } });
         verifyActiveNetwork(TRANSPORT_WIFI);
         assertEquals(1, mCm.getAllNetworks().length);
         assertEquals(mCm.getAllNetworks()[0], mCm.getActiveNetwork());
@@ -940,29 +1011,41 @@
     private class TestNetworkCallback extends NetworkCallback {
         private final ConditionVariable mConditionVariable = new ConditionVariable();
         private CallbackState mLastCallback = CallbackState.NONE;
+        private Network mLastNetwork;
 
         public void onAvailable(Network network) {
             assertEquals(CallbackState.NONE, mLastCallback);
             mLastCallback = CallbackState.AVAILABLE;
+            mLastNetwork = network;
             mConditionVariable.open();
         }
 
         public void onLosing(Network network, int maxMsToLive) {
             assertEquals(CallbackState.NONE, mLastCallback);
             mLastCallback = CallbackState.LOSING;
+            mLastNetwork = network;
             mConditionVariable.open();
         }
 
         public void onLost(Network network) {
             assertEquals(CallbackState.NONE, mLastCallback);
             mLastCallback = CallbackState.LOST;
+            mLastNetwork = network;
             mConditionVariable.open();
         }
 
         void expectCallback(CallbackState state) {
+            expectCallback(state, null);
+        }
+
+        void expectCallback(CallbackState state, MockNetworkAgent mockAgent) {
             waitFor(mConditionVariable);
             assertEquals(state, mLastCallback);
+            if (mockAgent != null) {
+                assertEquals(mockAgent.getNetwork(), mLastNetwork);
+            }
             mLastCallback = CallbackState.NONE;
+            mLastNetwork = null;
             mConditionVariable.close();
         }
 
@@ -1291,6 +1374,85 @@
         validatedCallback.expectCallback(CallbackState.LOST);
     }
 
+    @SmallTest
+    public void testInvalidNetworkSpecifier() {
+        boolean execptionCalled = true;
+
+        try {
+            NetworkRequest.Builder builder = new NetworkRequest.Builder();
+            builder.setNetworkSpecifier(MATCH_ALL_REQUESTS_NETWORK_SPECIFIER);
+            execptionCalled = false;
+        } catch (IllegalArgumentException e) {
+            // do nothing - should get here
+        }
+
+        assertTrue("NetworkReqeuest builder with MATCH_ALL_REQUESTS_NETWORK_SPECIFIER",
+                execptionCalled);
+
+        try {
+            NetworkCapabilities networkCapabilities = new NetworkCapabilities();
+            networkCapabilities.addTransportType(TRANSPORT_WIFI)
+                    .setNetworkSpecifier(NetworkCapabilities.MATCH_ALL_REQUESTS_NETWORK_SPECIFIER);
+            mService.requestNetwork(networkCapabilities, null, 0, null,
+                    ConnectivityManager.TYPE_WIFI);
+            execptionCalled = false;
+        } catch (IllegalArgumentException e) {
+            // do nothing - should get here
+        }
+
+        assertTrue("ConnectivityService requestNetwork with MATCH_ALL_REQUESTS_NETWORK_SPECIFIER",
+                execptionCalled);
+    }
+
+    @LargeTest
+    public void testRegisterDefaultNetworkCallback() throws Exception {
+        final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback();
+        mCm.registerDefaultNetworkCallback(defaultNetworkCallback);
+        defaultNetworkCallback.assertNoCallback();
+
+        // Create a TRANSPORT_CELLULAR request to keep the mobile interface up
+        // whenever Wi-Fi is up. Without this, the mobile network agent is
+        // reaped before any other activity can take place.
+        final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
+        final NetworkRequest cellRequest = new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_CELLULAR).build();
+        mCm.requestNetwork(cellRequest, cellNetworkCallback);
+        cellNetworkCallback.assertNoCallback();
+
+        // 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);
+
+        // Bring up wifi and expect CALLBACK_AVAILABLE.
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(true);
+        cellNetworkCallback.assertNoCallback();
+        defaultNetworkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+
+        // Bring down cell. Expect no default network callback, since it wasn't the default.
+        mCellNetworkAgent.disconnect();
+        cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        defaultNetworkCallback.assertNoCallback();
+
+        // 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);
+        defaultNetworkCallback.assertNoCallback();
+
+        // Bring down wifi. Expect the default network callback to notified of LOST wifi
+        // followed by AVAILABLE cell.
+        mWiFiNetworkAgent.disconnect();
+        cellNetworkCallback.assertNoCallback();
+        defaultNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        defaultNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        mCellNetworkAgent.disconnect();
+        cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        defaultNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+    }
+
     private static class TestKeepaliveCallback extends PacketKeepaliveCallback {
 
         public static enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };
@@ -1498,4 +1660,109 @@
         ka3.stop();
         callback3.expectStopped();
     }
+
+    @SmallTest
+    public void testGetCaptivePortalServerUrl() throws Exception {
+        String url = mCm.getCaptivePortalServerUrl();
+        assertEquals("http://connectivitycheck.gstatic.com/generate_204", url);
+    }
+
+    private static class TestNetworkPinner extends NetworkPinner {
+        public static boolean awaitPin(int timeoutMs) {
+            synchronized(sLock) {
+                if (sNetwork == null) {
+                    try {
+                        sLock.wait(timeoutMs);
+                    } catch (InterruptedException e) {}
+                }
+                return sNetwork != null;
+            }
+        }
+
+        public static boolean awaitUnpin(int timeoutMs) {
+            synchronized(sLock) {
+                if (sNetwork != null) {
+                    try {
+                        sLock.wait(timeoutMs);
+                    } catch (InterruptedException e) {}
+                }
+                return sNetwork == null;
+            }
+        }
+    }
+
+    private void assertPinnedToWifiWithCellDefault() {
+        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getBoundNetworkForProcess());
+        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+    }
+
+    private void assertPinnedToWifiWithWifiDefault() {
+        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getBoundNetworkForProcess());
+        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+    }
+
+    private void assertNotPinnedToWifi() {
+        assertNull(mCm.getBoundNetworkForProcess());
+        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+    }
+
+    @SmallTest
+    public void testNetworkPinner() {
+        NetworkRequest wifiRequest = new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_WIFI)
+                .build();
+        assertNull(mCm.getBoundNetworkForProcess());
+
+        TestNetworkPinner.pin(mServiceContext, wifiRequest);
+        assertNull(mCm.getBoundNetworkForProcess());
+
+        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        mCellNetworkAgent.connect(true);
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(false);
+
+        // When wi-fi connects, expect to be pinned.
+        assertTrue(TestNetworkPinner.awaitPin(100));
+        assertPinnedToWifiWithCellDefault();
+
+        // Disconnect and expect the pin to drop.
+        mWiFiNetworkAgent.disconnect();
+        assertTrue(TestNetworkPinner.awaitUnpin(100));
+        assertNotPinnedToWifi();
+
+        // Reconnecting does not cause the pin to come back.
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(false);
+        assertFalse(TestNetworkPinner.awaitPin(100));
+        assertNotPinnedToWifi();
+
+        // Pinning while connected causes the pin to take effect immediately.
+        TestNetworkPinner.pin(mServiceContext, wifiRequest);
+        assertTrue(TestNetworkPinner.awaitPin(100));
+        assertPinnedToWifiWithCellDefault();
+
+        // Explicitly unpin and expect to use the default network again.
+        TestNetworkPinner.unpin();
+        assertNotPinnedToWifi();
+
+        // Disconnect cell and wifi.
+        ConditionVariable cv = waitForConnectivityBroadcasts(3);  // cell down, wifi up, wifi down.
+        mCellNetworkAgent.disconnect();
+        mWiFiNetworkAgent.disconnect();
+        waitFor(cv);
+
+        // Pinning takes effect even if the pinned network is the default when the pin is set...
+        TestNetworkPinner.pin(mServiceContext, wifiRequest);
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(false);
+        assertTrue(TestNetworkPinner.awaitPin(100));
+        assertPinnedToWifiWithWifiDefault();
+
+        // ... and is maintained even when that network is no longer the default.
+        cv = waitForConnectivityBroadcasts(1);
+        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mCellNetworkAgent.connect(true);
+        waitFor(cv);
+        assertPinnedToWifiWithCellDefault();
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsAccessTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkStatsAccessTest.java
new file mode 100644
index 0000000..bb8f9d1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkStatsAccessTest.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2015 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.net;
+
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.Manifest.permission;
+import android.app.AppOpsManager;
+import android.app.admin.DeviceAdminInfo;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.telephony.TelephonyManager;
+
+import com.android.server.LocalServices;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class NetworkStatsAccessTest extends TestCase {
+    private static final String TEST_PKG = "com.example.test";
+    private static final int TEST_UID = 12345;
+
+    @Mock private Context mContext;
+    @Mock private DevicePolicyManagerInternal mDpmi;
+    @Mock private TelephonyManager mTm;
+    @Mock private AppOpsManager mAppOps;
+
+    // Hold the real service so we can restore it when tearing down the test.
+    private DevicePolicyManagerInternal mSystemDpmi;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+
+        mSystemDpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
+        LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+        LocalServices.addService(DevicePolicyManagerInternal.class, mDpmi);
+
+        when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTm);
+        when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOps);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+        LocalServices.addService(DevicePolicyManagerInternal.class, mSystemDpmi);
+        super.tearDown();
+    }
+
+    public void testCheckAccessLevel_hasCarrierPrivileges() throws Exception {
+        setHasCarrierPrivileges(true);
+        setIsDeviceOwner(false);
+        setIsProfileOwner(false);
+        setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
+        setHasReadHistoryPermission(false);
+        assertEquals(NetworkStatsAccess.Level.DEVICE,
+                NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+    }
+
+    public void testCheckAccessLevel_isDeviceOwner() throws Exception {
+        setHasCarrierPrivileges(false);
+        setIsDeviceOwner(true);
+        setIsProfileOwner(false);
+        setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
+        setHasReadHistoryPermission(false);
+        assertEquals(NetworkStatsAccess.Level.DEVICE,
+                NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+    }
+
+    public void testCheckAccessLevel_isProfileOwner() throws Exception {
+        setHasCarrierPrivileges(false);
+        setIsDeviceOwner(false);
+        setIsProfileOwner(true);
+        setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
+        setHasReadHistoryPermission(false);
+        assertEquals(NetworkStatsAccess.Level.USER,
+                NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+    }
+
+    public void testCheckAccessLevel_hasAppOpsBitAllowed() throws Exception {
+        setHasCarrierPrivileges(false);
+        setIsDeviceOwner(false);
+        setIsProfileOwner(true);
+        setHasAppOpsPermission(AppOpsManager.MODE_ALLOWED, false);
+        setHasReadHistoryPermission(false);
+        assertEquals(NetworkStatsAccess.Level.USER,
+                NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+    }
+
+    public void testCheckAccessLevel_hasAppOpsBitDefault_grantedPermission() throws Exception {
+        setHasCarrierPrivileges(false);
+        setIsDeviceOwner(false);
+        setIsProfileOwner(true);
+        setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, true);
+        setHasReadHistoryPermission(false);
+        assertEquals(NetworkStatsAccess.Level.USER,
+                NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+    }
+
+    public void testCheckAccessLevel_hasReadHistoryPermission() throws Exception {
+        setHasCarrierPrivileges(false);
+        setIsDeviceOwner(false);
+        setIsProfileOwner(true);
+        setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
+        setHasReadHistoryPermission(true);
+        assertEquals(NetworkStatsAccess.Level.USER,
+                NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+    }
+
+    public void testCheckAccessLevel_deniedAppOpsBit() throws Exception {
+        setHasCarrierPrivileges(false);
+        setIsDeviceOwner(false);
+        setIsProfileOwner(false);
+        setHasAppOpsPermission(AppOpsManager.MODE_ERRORED, true);
+        setHasReadHistoryPermission(false);
+        assertEquals(NetworkStatsAccess.Level.DEFAULT,
+                NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+    }
+
+    public void testCheckAccessLevel_deniedAppOpsBit_deniedPermission() throws Exception {
+        setHasCarrierPrivileges(false);
+        setIsDeviceOwner(false);
+        setIsProfileOwner(false);
+        setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
+        setHasReadHistoryPermission(false);
+        assertEquals(NetworkStatsAccess.Level.DEFAULT,
+                NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+    }
+
+    private void setHasCarrierPrivileges(boolean hasPrivileges) {
+        when(mTm.checkCarrierPrivilegesForPackage(TEST_PKG)).thenReturn(
+                hasPrivileges ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
+                        : TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+    }
+
+    private void setIsDeviceOwner(boolean isOwner) {
+        when(mDpmi.isActiveAdminWithPolicy(TEST_UID, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER))
+                .thenReturn(isOwner);
+    }
+
+    private void setIsProfileOwner(boolean isOwner) {
+        when(mDpmi.isActiveAdminWithPolicy(TEST_UID, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER))
+                .thenReturn(isOwner);
+    }
+
+    private void setHasAppOpsPermission(int appOpsMode, boolean hasPermission) {
+        when(mAppOps.checkOp(AppOpsManager.OP_GET_USAGE_STATS, TEST_UID, TEST_PKG))
+                .thenReturn(appOpsMode);
+        when(mContext.checkCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS)).thenReturn(
+                hasPermission ? PackageManager.PERMISSION_GRANTED
+                        : PackageManager.PERMISSION_DENIED);
+    }
+
+    private void setHasReadHistoryPermission(boolean hasPermission) {
+        when(mContext.checkCallingOrSelfPermission(permission.READ_NETWORK_USAGE_HISTORY))
+                .thenReturn(hasPermission ? PackageManager.PERMISSION_GRANTED
+                        : PackageManager.PERMISSION_DENIED);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
index 1a6c289..9f53c87 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.net;
 
+import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.NetworkStats.SET_DEFAULT;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
@@ -24,9 +25,14 @@
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 
 import android.content.res.Resources;
+import android.net.NetworkIdentity;
 import android.net.NetworkStats;
 import android.net.NetworkTemplate;
+import android.os.Process;
+import android.os.UserHandle;
+import android.telephony.TelephonyManager;
 import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
 import android.test.suitebuilder.annotation.MediumTest;
 
 import com.android.frameworks.servicestests.R;
@@ -68,7 +74,7 @@
 
         // verify that history read correctly
         assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
-                636016770L, 709306L, 88038768L, 518836L);
+                636016770L, 709306L, 88038768L, 518836L, NetworkStatsAccess.Level.DEVICE);
 
         // now export into a unified format
         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
@@ -77,12 +83,12 @@
         // clear structure completely
         collection.reset();
         assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
-                0L, 0L, 0L, 0L);
+                0L, 0L, 0L, 0L, NetworkStatsAccess.Level.DEVICE);
 
         // and read back into structure, verifying that totals are same
         collection.read(new ByteArrayInputStream(bos.toByteArray()));
         assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
-                636016770L, 709306L, 88038768L, 518836L);
+                636016770L, 709306L, 88038768L, 518836L, NetworkStatsAccess.Level.DEVICE);
     }
 
     public void testReadLegacyUid() throws Exception {
@@ -94,7 +100,7 @@
 
         // verify that history read correctly
         assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
-                637076152L, 711413L, 88343717L, 521022L);
+                637076152L, 711413L, 88343717L, 521022L, NetworkStatsAccess.Level.DEVICE);
 
         // now export into a unified format
         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
@@ -103,12 +109,12 @@
         // clear structure completely
         collection.reset();
         assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
-                0L, 0L, 0L, 0L);
+                0L, 0L, 0L, 0L, NetworkStatsAccess.Level.DEVICE);
 
         // and read back into structure, verifying that totals are same
         collection.read(new ByteArrayInputStream(bos.toByteArray()));
         assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
-                637076152L, 711413L, 88343717L, 521022L);
+                637076152L, 711413L, 88343717L, 521022L, NetworkStatsAccess.Level.DEVICE);
     }
 
     public void testReadLegacyUidTags() throws Exception {
@@ -151,6 +157,66 @@
         assertEquals(2 * HOUR_IN_MILLIS, collection.getEndMillis());
     }
 
+    public void testAccessLevels() throws Exception {
+        final NetworkStatsCollection collection = new NetworkStatsCollection(HOUR_IN_MILLIS);
+        final NetworkStats.Entry entry = new NetworkStats.Entry();
+        final NetworkIdentitySet identSet = new NetworkIdentitySet();
+        identSet.add(new NetworkIdentity(TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                TEST_IMSI, null, false, true));
+
+        int myUid = Process.myUid();
+        int otherUidInSameUser = Process.myUid() + 1;
+        int uidInDifferentUser = Process.myUid() + UserHandle.PER_USER_RANGE;
+
+        // Record one entry for the current UID.
+        entry.rxBytes = 32;
+        collection.recordData(identSet, myUid, SET_DEFAULT, TAG_NONE, 0, 60 * MINUTE_IN_MILLIS,
+                entry);
+
+        // Record one entry for another UID in this user.
+        entry.rxBytes = 64;
+        collection.recordData(identSet, otherUidInSameUser, SET_DEFAULT, TAG_NONE, 0,
+                60 * MINUTE_IN_MILLIS, entry);
+
+        // Record one entry for the system UID.
+        entry.rxBytes = 128;
+        collection.recordData(identSet, Process.SYSTEM_UID, SET_DEFAULT, TAG_NONE, 0,
+                60 * MINUTE_IN_MILLIS, entry);
+
+        // Record one entry for a UID in a different user.
+        entry.rxBytes = 256;
+        collection.recordData(identSet, uidInDifferentUser, SET_DEFAULT, TAG_NONE, 0,
+                60 * MINUTE_IN_MILLIS, entry);
+
+        // Verify the set of relevant UIDs for each access level.
+        MoreAsserts.assertEquals(new int[] { myUid },
+                collection.getRelevantUids(NetworkStatsAccess.Level.DEFAULT));
+        MoreAsserts.assertEquals(new int[] { Process.SYSTEM_UID, myUid, otherUidInSameUser },
+                collection.getRelevantUids(NetworkStatsAccess.Level.USER));
+        MoreAsserts.assertEquals(
+                new int[] { Process.SYSTEM_UID, myUid, otherUidInSameUser, uidInDifferentUser },
+                collection.getRelevantUids(NetworkStatsAccess.Level.DEVICE));
+
+        // Verify security check in getHistory.
+        assertNotNull(collection.getHistory(buildTemplateMobileAll(TEST_IMSI), myUid, SET_DEFAULT,
+                TAG_NONE, 0, NetworkStatsAccess.Level.DEFAULT));
+        try {
+            collection.getHistory(buildTemplateMobileAll(TEST_IMSI), otherUidInSameUser,
+                    SET_DEFAULT, TAG_NONE, 0, NetworkStatsAccess.Level.DEFAULT);
+            fail("Should have thrown SecurityException for accessing different UID");
+        } catch (SecurityException e) {
+            // expected
+        }
+
+        // Verify appropriate aggregation in getSummary.
+        assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), 32, 0, 0, 0,
+                NetworkStatsAccess.Level.DEFAULT);
+        assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), 32 + 64 + 128, 0, 0, 0,
+                NetworkStatsAccess.Level.USER);
+        assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), 32 + 64 + 128 + 256, 0, 0,
+                0, NetworkStatsAccess.Level.DEVICE);
+    }
+
     /**
      * Copy a {@link Resources#openRawResource(int)} into {@link File} for
      * testing purposes.
@@ -170,16 +236,19 @@
     }
 
     private static void assertSummaryTotal(NetworkStatsCollection collection,
-            NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets) {
+            NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets,
+            @NetworkStatsAccess.Level int accessLevel) {
         final NetworkStats.Entry entry = collection.getSummary(
-                template, Long.MIN_VALUE, Long.MAX_VALUE).getTotal(null);
+                template, Long.MIN_VALUE, Long.MAX_VALUE, accessLevel)
+                .getTotal(null);
         assertEntry(entry, rxBytes, rxPackets, txBytes, txPackets);
     }
 
     private static void assertSummaryTotalIncludingTags(NetworkStatsCollection collection,
             NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets) {
         final NetworkStats.Entry entry = collection.getSummary(
-                template, Long.MIN_VALUE, Long.MAX_VALUE).getTotalIncludingTags(null);
+                template, Long.MIN_VALUE, Long.MAX_VALUE, NetworkStatsAccess.Level.DEVICE)
+                .getTotalIncludingTags(null);
         assertEntry(entry, rxBytes, rxPackets, txBytes, txPackets);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java
new file mode 100644
index 0000000..cff5876
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java
@@ -0,0 +1,634 @@
+/*
+ * 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.net;
+
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.when;
+
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.ROAMING_NO;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkTemplate.buildTemplateMobileAll;
+import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
+import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
+import android.app.usage.NetworkStatsManager;
+import android.net.DataUsageRequest;
+import android.net.NetworkIdentity;
+import android.net.NetworkStats;
+import android.net.NetworkTemplate;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Process;
+
+import android.os.ConditionVariable;
+import android.os.Looper;
+import android.os.Messenger;
+import android.os.Message;
+import android.os.UserHandle;
+import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
+
+import com.android.internal.net.VpnInfo;
+import com.android.server.net.NetworkStatsService;
+import com.android.server.net.NetworkStatsServiceTest.IdleableHandlerThread;
+import com.android.server.net.NetworkStatsServiceTest.LatchedHandler;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link NetworkStatsObservers}.
+ */
+public class NetworkStatsObserversTest extends TestCase {
+    private static final String TEST_IFACE = "test0";
+    private static final String TEST_IFACE2 = "test1";
+    private static final long TEST_START = 1194220800000L;
+
+    private static final String IMSI_1 = "310004";
+    private static final String IMSI_2 = "310260";
+    private static final String TEST_SSID = "AndroidAP";
+
+    private static NetworkTemplate sTemplateWifi = buildTemplateWifiWildcard();
+    private static NetworkTemplate sTemplateImsi1 = buildTemplateMobileAll(IMSI_1);
+    private static NetworkTemplate sTemplateImsi2 = buildTemplateMobileAll(IMSI_2);
+
+    private static final int UID_RED = UserHandle.PER_USER_RANGE + 1;
+    private static final int UID_BLUE = UserHandle.PER_USER_RANGE + 2;
+    private static final int UID_GREEN = UserHandle.PER_USER_RANGE + 3;
+    private static final int UID_ANOTHER_USER = 2 * UserHandle.PER_USER_RANGE + 4;
+
+    private static final long WAIT_TIMEOUT = 500;  // 1/2 sec
+    private static final long THRESHOLD_BYTES = 2 * MB_IN_BYTES;
+    private static final long BASE_BYTES = 7 * MB_IN_BYTES;
+    private static final int INVALID_TYPE = -1;
+
+    private static final int[] NO_UIDS = null;
+    private static final VpnInfo[] VPN_INFO = new VpnInfo[0];
+
+    private long mElapsedRealtime;
+
+    private IdleableHandlerThread mObserverHandlerThread;
+    private Handler mObserverNoopHandler;
+
+    private LatchedHandler mHandler;
+    private ConditionVariable mCv;
+
+    private NetworkStatsObservers mStatsObservers;
+    private Messenger mMessenger;
+    private ArrayMap<String, NetworkIdentitySet> mActiveIfaces;
+    private ArrayMap<String, NetworkIdentitySet> mActiveUidIfaces;
+
+    @Mock private IBinder mockBinder;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+
+        mObserverHandlerThread = new IdleableHandlerThread("HandlerThread");
+        mObserverHandlerThread.start();
+        final Looper observerLooper = mObserverHandlerThread.getLooper();
+        mStatsObservers = new NetworkStatsObservers() {
+            @Override
+            protected Looper getHandlerLooperLocked() {
+                return observerLooper;
+            }
+        };
+
+        mCv = new ConditionVariable();
+        mHandler = new LatchedHandler(Looper.getMainLooper(), mCv);
+        mMessenger = new Messenger(mHandler);
+
+        mActiveIfaces = new ArrayMap<>();
+        mActiveUidIfaces = new ArrayMap<>();
+    }
+
+    public void testRegister_thresholdTooLow_setsDefaultThreshold() throws Exception {
+        long thresholdTooLowBytes = 1L;
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateWifi };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, thresholdTooLowBytes);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+    }
+
+    public void testRegister_highThreshold_accepted() throws Exception {
+        long highThresholdBytes = 2 * THRESHOLD_BYTES;
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateWifi };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, highThresholdBytes);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        assertEquals(highThresholdBytes, request.thresholdInBytes);
+    }
+
+    public void testRegister_twoRequests_twoIds() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateWifi };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, THRESHOLD_BYTES);
+
+        DataUsageRequest request1 = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+        assertTrue(request1.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request1.templates));
+        assertNull(request1.uids);
+        assertEquals(THRESHOLD_BYTES, request1.thresholdInBytes);
+
+        DataUsageRequest request2 = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+        assertTrue(request2.requestId > request1.requestId);
+        assertTrue(Arrays.deepEquals(templates, request2.templates));
+        assertNull(request2.uids);
+        assertEquals(THRESHOLD_BYTES, request2.thresholdInBytes);
+    }
+
+    public void testRegister_defaultAccess_otherUids_securityException() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        int[] uids = new int[] { UID_RED, UID_BLUE, UID_GREEN };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, uids, THRESHOLD_BYTES);
+
+        try {
+            mStatsObservers.register(inputRequest, mMessenger, mockBinder, UID_RED,
+                    NetworkStatsAccess.Level.DEFAULT);
+            fail("Should have denied access");
+        } catch (SecurityException expected) {}
+    }
+
+    public void testRegister_userAccess_otherUidsSameUser()
+            throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        int[] uids = new int[] { UID_RED, UID_BLUE, UID_GREEN };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, uids, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                UID_RED, NetworkStatsAccess.Level.USER);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertTrue(Arrays.equals(uids, request.uids));
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+    }
+
+    public void testRegister_defaultAccess_sameUid() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        int[] uids = new int[] { UID_RED };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, uids, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                UID_RED, NetworkStatsAccess.Level.DEFAULT);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertTrue(Arrays.equals(uids, request.uids));
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+    }
+
+    public void testUnregister_unknownRequest_noop() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateWifi };
+        DataUsageRequest unknownRequest = new DataUsageRequest(
+                123456 /* id */, templates, NO_UIDS, THRESHOLD_BYTES);
+
+        mStatsObservers.unregister(unknownRequest, UID_RED);
+    }
+
+    public void testUnregister_knownRequest_releasesCaller() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+        Mockito.verify(mockBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+
+        mStatsObservers.unregister(request, Process.SYSTEM_UID);
+        waitForObserverToIdle();
+
+        Mockito.verify(mockBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+    }
+
+    public void testUnregister_knownRequest_invalidUid_doesNotUnregister() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                UID_RED, NetworkStatsAccess.Level.DEVICE);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+        Mockito.verify(mockBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+
+        mStatsObservers.unregister(request, UID_BLUE);
+        waitForObserverToIdle();
+
+        Mockito.verifyZeroInteractions(mockBinder);
+    }
+
+    public void testUpdateStats_initialSample_doesNotNotify() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+
+        NetworkIdentitySet identSet = new NetworkIdentitySet();
+        identSet.add(new NetworkIdentity(
+                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                IMSI_1, null /* networkId */, false /* roaming */, true /* metered */));
+        mActiveIfaces.put(TEST_IFACE, identSet);
+
+        // Baseline
+        NetworkStats xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */)
+                .addIfaceValues(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L);
+        NetworkStats uidSnapshot = null;
+
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+        waitForObserverToIdle();
+
+        assertTrue(mCv.block(WAIT_TIMEOUT));
+        assertEquals(INVALID_TYPE, mHandler.mLastMessageType);
+    }
+
+    public void testUpdateStats_belowThreshold_doesNotNotify() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+
+        NetworkIdentitySet identSet = new NetworkIdentitySet();
+        identSet.add(new NetworkIdentity(
+                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                IMSI_1, null /* networkId */, false /* roaming */, true /* metered */));
+        mActiveIfaces.put(TEST_IFACE, identSet);
+
+        // Baseline
+        NetworkStats xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */)
+                .addIfaceValues(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L);
+        NetworkStats uidSnapshot = null;
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+
+        // Delta
+        xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */)
+                .addIfaceValues(TEST_IFACE, BASE_BYTES + 1024L, 10L, BASE_BYTES + 2048L, 20L);
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+        waitForObserverToIdle();
+
+        assertTrue(mCv.block(WAIT_TIMEOUT));
+        mCv.block(WAIT_TIMEOUT);
+        assertEquals(INVALID_TYPE, mHandler.mLastMessageType);
+    }
+
+    public void testUpdateStats_aboveThresholdNetwork_notifies() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+
+        NetworkIdentitySet identSet = new NetworkIdentitySet();
+        identSet.add(new NetworkIdentity(
+                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                IMSI_1, null /* networkId */, false /* roaming */, true /* metered */));
+        mActiveIfaces.put(TEST_IFACE, identSet);
+
+        // Baseline
+        NetworkStats xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */)
+                .addIfaceValues(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L);
+        NetworkStats uidSnapshot = null;
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+
+        // Delta
+        xtSnapshot = new NetworkStats(TEST_START + MINUTE_IN_MILLIS, 1 /* initialSize */)
+                .addIfaceValues(TEST_IFACE, BASE_BYTES + THRESHOLD_BYTES, 12L,
+                        BASE_BYTES + THRESHOLD_BYTES, 22L);
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+        waitForObserverToIdle();
+
+        assertTrue(mCv.block(WAIT_TIMEOUT));
+        assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
+    }
+
+    public void testUpdateStats_aboveThresholdMultipleNetwork_notifies() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1, sTemplateImsi2 };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                UID_RED, NetworkStatsAccess.Level.DEVICESUMMARY);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+
+        NetworkIdentitySet identSet1 = new NetworkIdentitySet();
+        identSet1.add(new NetworkIdentity(
+                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                IMSI_1, null /* networkId */, false /* roaming */, true /* metered */));
+        mActiveIfaces.put(TEST_IFACE, identSet1);
+
+        NetworkIdentitySet identSet2 = new NetworkIdentitySet();
+        identSet2.add(new NetworkIdentity(
+                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                IMSI_2, null /* networkId */, false /* roaming */, true /* metered */));
+        mActiveIfaces.put(TEST_IFACE2, identSet2);
+
+        // Baseline
+        NetworkStats xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */)
+                .addIfaceValues(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L)
+                .addIfaceValues(TEST_IFACE2, BASE_BYTES + 1234L, 18L, BASE_BYTES, 12L);
+        NetworkStats uidSnapshot = null;
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+
+        // Delta - traffic on IMSI2
+        xtSnapshot = new NetworkStats(TEST_START + MINUTE_IN_MILLIS, 1 /* initialSize */)
+                .addIfaceValues(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L)
+                .addIfaceValues(TEST_IFACE2, BASE_BYTES + THRESHOLD_BYTES, 22L,
+                        BASE_BYTES + THRESHOLD_BYTES, 24L);
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+        waitForObserverToIdle();
+
+        assertTrue(mCv.block(WAIT_TIMEOUT));
+        assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
+    }
+
+    public void testUpdateStats_aboveThresholdUid_notifies() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        int[] uids = new int[] { UID_RED, UID_BLUE, UID_GREEN };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, uids, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertTrue(Arrays.equals(uids,request.uids));
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+
+        NetworkIdentitySet identSet = new NetworkIdentitySet();
+        identSet.add(new NetworkIdentity(
+                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                IMSI_1, null /* networkId */, false /* roaming */, true /* metered */));
+        mActiveUidIfaces.put(TEST_IFACE, identSet);
+
+        // Baseline
+        NetworkStats xtSnapshot = null;
+        NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 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_RED, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+                        BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+        waitForObserverToIdle();
+
+        assertTrue(mCv.block(WAIT_TIMEOUT));
+        assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
+    }
+
+    public void testUpdateStats_defaultAccess_noUid_notifiesSameUid() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                UID_RED, NetworkStatsAccess.Level.DEFAULT);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+
+        NetworkIdentitySet identSet = new NetworkIdentitySet();
+        identSet.add(new NetworkIdentity(
+                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                IMSI_1, null /* networkId */, false /* roaming */, true /* metered */));
+        mActiveUidIfaces.put(TEST_IFACE, identSet);
+
+        // Baseline
+        NetworkStats xtSnapshot = null;
+        NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 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_RED, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+                        BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+        waitForObserverToIdle();
+
+        assertTrue(mCv.block(WAIT_TIMEOUT));
+        assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
+    }
+
+    public void testUpdateStats_defaultAccess_noUid_usageOtherUid_doesNotNotify() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                UID_BLUE, NetworkStatsAccess.Level.DEFAULT);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+
+        NetworkIdentitySet identSet = new NetworkIdentitySet();
+        identSet.add(new NetworkIdentity(
+                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                IMSI_1, null /* networkId */, false /* roaming */, true /* metered */));
+        mActiveUidIfaces.put(TEST_IFACE, identSet);
+
+        // Baseline
+        NetworkStats xtSnapshot = null;
+        NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 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_RED, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+                        BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+        waitForObserverToIdle();
+
+        assertTrue(mCv.block(WAIT_TIMEOUT));
+        assertEquals(INVALID_TYPE, mHandler.mLastMessageType);
+    }
+
+    public void testUpdateStats_userAccess_usageSameUser_notifies() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                UID_BLUE, NetworkStatsAccess.Level.USER);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+
+        NetworkIdentitySet identSet = new NetworkIdentitySet();
+        identSet.add(new NetworkIdentity(
+                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                IMSI_1, null /* networkId */, false /* roaming */, true /* metered */));
+        mActiveUidIfaces.put(TEST_IFACE, identSet);
+
+        // Baseline
+        NetworkStats xtSnapshot = null;
+        NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 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_RED, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+                        BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+        waitForObserverToIdle();
+
+        assertTrue(mCv.block(WAIT_TIMEOUT));
+        assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
+    }
+
+    public void testUpdateStats_userAccess_usageAnotherUser_doesNotNotify() throws Exception {
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1 };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, NO_UIDS, THRESHOLD_BYTES);
+
+        DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+                UID_RED, NetworkStatsAccess.Level.USER);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+
+        NetworkIdentitySet identSet = new NetworkIdentitySet();
+        identSet.add(new NetworkIdentity(
+                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                IMSI_1, null /* networkId */, false /* roaming */, true /* metered */));
+        mActiveUidIfaces.put(TEST_IFACE, identSet);
+
+        // 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);
+        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);
+        mStatsObservers.updateStats(
+                xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+                VPN_INFO, TEST_START);
+        waitForObserverToIdle();
+
+        assertTrue(mCv.block(WAIT_TIMEOUT));
+        assertEquals(INVALID_TYPE, mHandler.mLastMessageType);
+    }
+
+    private void waitForObserverToIdle() {
+        // Send dummy message to make sure that any previous message has been handled
+        mHandler.sendMessage(mHandler.obtainMessage(-1));
+        mObserverHandlerThread.waitForIdle(WAIT_TIMEOUT);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java
similarity index 65%
rename from services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
rename to services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java
index 90b4f43..74c1984 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java
@@ -14,15 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package com.android.server.net;
 
 import static android.content.Intent.ACTION_UID_REMOVED;
 import static android.content.Intent.EXTRA_UID;
-import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 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.ROAMING_ALL;
+import static android.net.NetworkStats.ROAMING_NO;
+import static android.net.NetworkStats.ROAMING_YES;
 import static android.net.NetworkStats.SET_ALL;
 import static android.net.NetworkStats.SET_DEFAULT;
 import static android.net.NetworkStats.SET_FOREGROUND;
@@ -41,6 +43,7 @@
 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;
@@ -49,9 +52,13 @@
 import static org.easymock.EasyMock.isA;
 
 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;
+import android.net.DataUsageRequest;
 import android.net.IConnectivityManager;
 import android.net.INetworkManagementEventObserver;
 import android.net.INetworkStatsSession;
@@ -62,7 +69,17 @@
 import android.net.NetworkStats;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.INetworkManagementService;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Messenger;
+import android.os.MessageQueue;
+import android.os.MessageQueue.IdleHandler;
+import android.os.Message;
+import android.os.PowerManager;
 import android.os.WorkSource;
 import android.telephony.TelephonyManager;
 import android.test.AndroidTestCase;
@@ -70,6 +87,8 @@
 import android.test.suitebuilder.annotation.Suppress;
 import android.util.TrustedTime;
 
+import com.android.internal.net.VpnInfo;
+import com.android.server.BroadcastInterceptingContext;
 import com.android.server.net.NetworkStatsService;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
@@ -80,9 +99,15 @@
 import org.easymock.EasyMock;
 
 import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 
 /**
  * Tests for {@link NetworkStatsService}.
+ *
+ * TODO: This test is really brittle, largely due to overly-strict use of Easymock.
+ * Rewrite w/ Mockito.
  */
 @LargeTest
 public class NetworkStatsServiceTest extends AndroidTestCase {
@@ -104,16 +129,20 @@
     private static final int UID_BLUE = 1002;
     private static final int UID_GREEN = 1003;
 
+    private static final long WAIT_TIMEOUT = 2 * 1000;  // 2 secs
+    private static final int INVALID_TYPE = -1;
+
     private long mElapsedRealtime;
 
     private BroadcastInterceptingContext mServiceContext;
     private File mStatsDir;
 
     private INetworkManagementService mNetManager;
-    private IAlarmManager mAlarmManager;
     private TrustedTime mTime;
     private NetworkStatsSettings mSettings;
     private IConnectivityManager mConnManager;
+    private IdleableHandlerThread mHandlerThread;
+    private Handler mHandler;
 
     private NetworkStatsService mService;
     private INetworkStatsSession mSession;
@@ -130,20 +159,34 @@
         }
 
         mNetManager = createMock(INetworkManagementService.class);
-        mAlarmManager = createMock(IAlarmManager.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, mAlarmManager, mTime, mStatsDir, mSettings);
+                mServiceContext, mNetManager, alarmManager, wakeLock, mTime,
+                TelephonyManager.getDefault(), mSettings, new NetworkStatsObservers(),
+                mStatsDir, getBaseDir(mStatsDir));
+        mHandlerThread = new IdleableHandlerThread("HandlerThread");
+        mHandlerThread.start();
+        Handler.Callback callback = new NetworkStatsService.HandlerCallback(mService);
+        mHandler = new Handler(mHandlerThread.getLooper(), callback);
+        mService.setHandler(mHandler, callback);
         mService.bindConnectivityManager(mConnManager);
 
         mElapsedRealtime = 0L;
 
         expectCurrentTime();
         expectDefaultSettings();
-        expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectSystemReady();
 
@@ -170,7 +213,6 @@
         mStatsDir = null;
 
         mNetManager = null;
-        mAlarmManager = null;
         mTime = null;
         mSettings = null;
         mConnManager = null;
@@ -190,9 +232,10 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectNetworkStatsPoll();
+        expectBandwidthControlCheck();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        mService.forceUpdateIfaces();
 
         // verify service has empty history for wifi
         assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
@@ -208,7 +251,7 @@
         expectNetworkStatsPoll();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertNetworkTotal(sTemplateWifi, 1024L, 1L, 2048L, 2L, 0);
@@ -225,7 +268,7 @@
         expectNetworkStatsPoll();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertNetworkTotal(sTemplateWifi, 4096L, 4L, 8192L, 8L, 0);
@@ -244,9 +287,10 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectNetworkStatsPoll();
+        expectBandwidthControlCheck();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        mService.forceUpdateIfaces();
 
         // verify service has empty history for wifi
         assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
@@ -272,13 +316,14 @@
         mService.incrementOperationCount(UID_RED, 0xFAAD, 6);
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        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, 512L, 4L, 256L, 2L, 4);
-        assertUidTotal(sTemplateWifi, UID_RED, SET_FOREGROUND, 512L, 4L, 256L, 2L, 6);
+        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_BLUE, 128L, 1L, 128L, 1L, 0);
         verifyAndReset();
 
@@ -295,7 +340,6 @@
         // boot through serviceReady() again
         expectCurrentTime();
         expectDefaultSettings();
-        expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectSystemReady();
 
@@ -313,8 +357,9 @@
         // 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, 512L, 4L, 256L, 2L, 4);
-        assertUidTotal(sTemplateWifi, UID_RED, SET_FOREGROUND, 512L, 4L, 256L, 2L, 6);
+        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_BLUE, 128L, 1L, 128L, 1L, 0);
         verifyAndReset();
 
@@ -335,9 +380,10 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectNetworkStatsPoll();
+        expectBandwidthControlCheck();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        mService.forceUpdateIfaces();
         verifyAndReset();
 
         // modify some number on wifi, and trigger poll event
@@ -350,7 +396,7 @@
         expectNetworkStatsPoll();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify service recorded history
         history = mSession.getHistoryForNetwork(sTemplateWifi, FIELD_ALL);
@@ -368,7 +414,7 @@
         expectNetworkStatsPoll();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify identical stats, but spread across 4 buckets now
         history = mSession.getHistoryForNetwork(sTemplateWifi, FIELD_ALL);
@@ -387,9 +433,10 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectNetworkStatsPoll();
+        expectBandwidthControlCheck();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        mService.forceUpdateIfaces();
         verifyAndReset();
 
         // create some traffic on first network
@@ -407,7 +454,7 @@
         mService.incrementOperationCount(UID_RED, 0xF00D, 10);
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertNetworkTotal(sTemplateImsi1, 2048L, 16L, 512L, 4L, 0);
@@ -429,10 +476,11 @@
                 .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();
-        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        mService.forceUpdateIfaces();
+        forcePollAndWaitForIdle();
         verifyAndReset();
 
         // create traffic on second network
@@ -451,7 +499,7 @@
         mService.incrementOperationCount(UID_BLUE, 0xFAAD, 10);
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify original history still intact
         assertNetworkTotal(sTemplateImsi1, 2048L, 16L, 512L, 4L, 0);
@@ -475,9 +523,10 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectNetworkStatsPoll();
+        expectBandwidthControlCheck();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        mService.forceUpdateIfaces();
         verifyAndReset();
 
         // create some traffic
@@ -496,7 +545,7 @@
         mService.incrementOperationCount(UID_RED, 0xFAAD, 10);
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertNetworkTotal(sTemplateWifi, 4128L, 258L, 544L, 34L, 0);
@@ -544,9 +593,10 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectNetworkStatsPoll();
+        expectBandwidthControlCheck();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        mService.forceUpdateIfaces();
         verifyAndReset();
 
         // create some traffic
@@ -562,7 +612,7 @@
         mService.incrementOperationCount(UID_RED, 0xF00D, 5);
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertUidTotal(sTemplateImsi1, UID_RED, 1024L, 8L, 1024L, 8L, 5);
@@ -578,10 +628,11 @@
                 .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();
-        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        mService.forceUpdateIfaces();
+        forcePollAndWaitForIdle();
         verifyAndReset();
 
         // create traffic on second network
@@ -599,7 +650,7 @@
         mService.incrementOperationCount(UID_RED, 0xFAAD, 5);
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify that ALL_MOBILE template combines both
         assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 1280L, 10L, 10);
@@ -615,9 +666,10 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectNetworkStatsPoll();
+        expectBandwidthControlCheck();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        mService.forceUpdateIfaces();
         verifyAndReset();
 
         // create some traffic for two apps
@@ -634,7 +686,7 @@
         mService.incrementOperationCount(UID_RED, 0xF00D, 1);
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertUidTotal(sTemplateWifi, UID_RED, 50L, 5L, 50L, 5L, 1);
@@ -653,22 +705,26 @@
         expectNetworkStatsPoll();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        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, 50L, 5L, 50L, 5L, 1);
-        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 1);
-        assertValues(stats, IFACE_ALL, UID_BLUE, SET_DEFAULT, TAG_NONE, 2048L, 16L, 1024L, 8L, 0);
+        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);
 
         // 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, 1024L, 8L, 512L, 4L, 0);
+        assertValues(stats, IFACE_ALL, UID_BLUE, SET_DEFAULT, TAG_NONE, ROAMING_NO, 1024L, 8L,
+                512L, 4L, 0);
 
         verifyAndReset();
     }
@@ -681,9 +737,10 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectNetworkStatsPoll();
+        expectBandwidthControlCheck();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        mService.forceUpdateIfaces();
         verifyAndReset();
 
         // create some initial traffic
@@ -699,7 +756,7 @@
         mService.incrementOperationCount(UID_RED, 0xF00D, 1);
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertUidTotal(sTemplateWifi, UID_RED, 128L, 2L, 128L, 2L, 1);
@@ -721,7 +778,7 @@
         mService.incrementOperationCount(UID_RED, 0xFAAD, 1);
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // test that we combined correctly
         assertUidTotal(sTemplateWifi, UID_RED, 160L, 4L, 160L, 4L, 2);
@@ -730,10 +787,61 @@
         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, 128L, 2L, 128L, 2L, 1);
-        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 1);
-        assertValues(stats, IFACE_ALL, UID_RED, SET_FOREGROUND, TAG_NONE, 32L, 2L, 32L, 2L, 1);
-        assertValues(stats, IFACE_ALL, UID_RED, SET_FOREGROUND, 0xFAAD, 1L, 1L, 1L, 1L, 1);
+        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();
+    }
+
+    public void testRoaming() throws Exception {
+        // pretend that network comes online
+        expectCurrentTime();
+        expectDefaultSettings();
+        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.
+        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();
+        forcePollAndWaitForIdle();
+
+        // verify service recorded history
+        assertUidTotal(sTemplateImsi1, UID_RED, 128L, 2L, 128L, 2L, 0);
+
+        // verify entire history present
+        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();
     }
@@ -746,9 +854,10 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectNetworkStatsPoll();
+        expectBandwidthControlCheck();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        mService.forceUpdateIfaces();
         verifyAndReset();
 
         // create some tethering traffic
@@ -768,7 +877,7 @@
         expectNetworkStatsPoll();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
         // verify service recorded history
         assertNetworkTotal(sTemplateImsi1, 2048L, 16L, 512L, 4L, 0);
@@ -778,63 +887,283 @@
 
     }
 
-    public void testReportXtOverDev() throws Exception {
-        // bring mobile network online
+    public void testRegisterDataUsageCallback_network() throws Exception {
+        // pretend that wifi network comes online; service should ask about full
+        // network state, and poll any existing interfaces before updating.
         expectCurrentTime();
         expectDefaultSettings();
-        expectNetworkState(buildMobile3gState(IMSI_1));
+        expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectNetworkStatsPoll();
+        expectBandwidthControlCheck();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        mService.forceUpdateIfaces();
+
+        // verify service has empty history for wifi
+        assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
         verifyAndReset();
 
-        // create some traffic, but only for DEV, and across 1.5 buckets
-        incrementCurrentTime(90 * MINUTE_IN_MILLIS);
+        String callingPackage = "the.calling.package";
+        long thresholdInBytes = 1L;  // very small; should be overriden by framework
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateWifi };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, null /* uids */, thresholdInBytes);
+
+        // Create a messenger that waits for callback activity
+        ConditionVariable cv = new ConditionVariable(false);
+        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();
-        expectNetworkStatsSummaryDev(new NetworkStats(getElapsedRealtime(), 1)
-                .addIfaceValues(TEST_IFACE, 6000L, 60L, 3000L, 30L));
-        expectNetworkStatsSummaryXt(buildEmptyStats());
+        expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(buildEmptyStats());
+        expectNetworkStatsPoll();
+        replay();
+
+        // Register and verify request and that binder was called
+        DataUsageRequest request =
+                mService.registerDataUsageCallback(callingPackage, inputRequest,
+                        messenger, mockBinder);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertNull(request.uids);
+        long minThresholdInBytes = 2 * 1024 * 1024; // 2 MB
+        assertEquals(minThresholdInBytes, request.thresholdInBytes);
+
+        // Send dummy message to make sure that any previous message has been handled
+        mHandler.sendMessage(mHandler.obtainMessage(-1));
+        mHandlerThread.waitForIdle(WAIT_TIMEOUT);
+
+        verifyAndReset();
+
+        // Make sure that the caller binder gets connected
+        EasyMock.verify(mockBinder);
+        EasyMock.reset(mockBinder);
+
+        // modify some number on wifi, and trigger poll event
+        // not enough traffic to call data usage callback
+        incrementCurrentTime(HOUR_IN_MILLIS);
+        expectCurrentTime();
+        expectDefaultSettings();
+        expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+                .addIfaceValues(TEST_IFACE, 1024L, 1L, 2048L, 2L));
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectNetworkStatsPoll();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
-        // verify service recorded history:
-        // 4000(dev) + 2000(dev)
-        assertNetworkTotal(sTemplateImsi1, 6000L, 60L, 3000L, 30L, 0);
+        // verify service recorded history
         verifyAndReset();
+        assertNetworkTotal(sTemplateWifi, 1024L, 1L, 2048L, 2L, 0);
 
-        // create traffic on both DEV and XT, across two buckets
-        incrementCurrentTime(2 * HOUR_IN_MILLIS);
+        // make sure callback has not being called
+        assertEquals(INVALID_TYPE, latchedHandler.mLastMessageType);
+
+        // and bump forward again, with counters going higher. this is
+        // important, since it will trigger the data usage callback
+        incrementCurrentTime(DAY_IN_MILLIS);
         expectCurrentTime();
         expectDefaultSettings();
-        expectNetworkStatsSummaryDev(new NetworkStats(getElapsedRealtime(), 1)
-                .addIfaceValues(TEST_IFACE, 6004L, 64L, 3004L, 34L));
-        expectNetworkStatsSummaryXt(new NetworkStats(getElapsedRealtime(), 1)
-                .addIfaceValues(TEST_IFACE, 10240L, 0L, 0L, 0L));
+        expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+                .addIfaceValues(TEST_IFACE, 4096000L, 4L, 8192000L, 8L));
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectNetworkStatsPoll();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        forcePollAndWaitForIdle();
 
-        // verify that we switching reporting at the first atomic XT bucket,
-        // which should give us:
-        // 4000(dev) + 2000(dev) + 1(dev) + 5120(xt) + 2560(xt)
-        assertNetworkTotal(sTemplateImsi1, 13681L, 61L, 3001L, 31L, 0);
+        // verify service recorded history
+        assertNetworkTotal(sTemplateWifi, 4096000L, 4L, 8192000L, 8L, 0);
+        verifyAndReset();
 
-        // also test pure-DEV and pure-XT ranges
-        assertNetworkTotal(sTemplateImsi1, startTimeMillis(),
-                startTimeMillis() + 2 * HOUR_IN_MILLIS, 6001L, 61L, 3001L, 31L, 0);
-        assertNetworkTotal(sTemplateImsi1, startTimeMillis() + 2 * HOUR_IN_MILLIS,
-                startTimeMillis() + 4 * HOUR_IN_MILLIS, 7680L, 0L, 0L, 0L, 0);
+        // Wait for the caller to ack receipt of CALLBACK_LIMIT_REACHED
+        assertTrue(cv.block(WAIT_TIMEOUT));
+        assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, latchedHandler.mLastMessageType);
+        cv.close();
+
+        // Allow binder to disconnect
+        expect(mockBinder.unlinkToDeath((IBinder.DeathRecipient) anyObject(), anyInt()))
+                .andReturn(true);
+        EasyMock.replay(mockBinder);
+
+        // Unregister request
+        mService.unregisterDataUsageRequest(request);
+
+        // Wait for the caller to ack receipt of CALLBACK_RELEASED
+        assertTrue(cv.block(WAIT_TIMEOUT));
+        assertEquals(NetworkStatsManager.CALLBACK_RELEASED, latchedHandler.mLastMessageType);
+
+        // Make sure that the caller binder gets disconnected
+        EasyMock.verify(mockBinder);
+    }
+
+    public void testRegisterDataUsageCallback_uids() throws Exception {
+        // pretend that network comes online
+        expectCurrentTime();
+        expectDefaultSettings();
+        expectNetworkState(buildMobile3gState(IMSI_1, true /* isRoaming */));
+        expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(buildEmptyStats());
+        expectNetworkStatsPoll();
+        expectBandwidthControlCheck();
+
+        replay();
+        mService.forceUpdateIfaces();
+        verifyAndReset();
+
+        String callingPackage = "the.calling.package";
+        long thresholdInBytes = 10 * 1024 * 1024;  // 10 MB
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1, sTemplateImsi2 };
+        int[] uids = new int[] { UID_RED };
+        DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, templates, uids, thresholdInBytes);
+
+        // Create a messenger that waits for callback activity
+        ConditionVariable cv = new ConditionVariable(false);
+        cv.close();
+        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.registerDataUsageCallback(callingPackage, inputRequest,
+                        messenger, mockBinder);
+        assertTrue(request.requestId > 0);
+        assertTrue(Arrays.deepEquals(templates, request.templates));
+        assertTrue(Arrays.equals(uids, request.uids));
+        assertEquals(thresholdInBytes, request.thresholdInBytes);
+
+        // Wait for service to handle internal MSG_REGISTER_DATA_USAGE_LISTENER
+        mHandler.sendMessage(mHandler.obtainMessage(-1));
+        mHandlerThread.waitForIdle(WAIT_TIMEOUT);
 
         verifyAndReset();
+
+        // Make sure that the caller binder gets connected
+        EasyMock.verify(mockBinder);
+        EasyMock.reset(mockBinder);
+
+        // modify some number on mobile interface, and trigger poll event
+        // not enough traffic to call data usage callback
+        incrementCurrentTime(HOUR_IN_MILLIS);
+        expectCurrentTime();
+        expectDefaultSettings();
+        expectNetworkStatsSummary(buildEmptyStats());
+        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();
+        forcePollAndWaitForIdle();
+
+        // verify service recorded history
+        assertUidTotal(sTemplateImsi1, UID_RED, 128L, 2L, 128L, 2L, 0);
+
+        // verify entire history present
+        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();
+
+        // make sure callback has not being called
+        assertEquals(INVALID_TYPE, latchedHandler.mLastMessageType);
+
+        // and bump forward again, with counters going higher. this is
+        // important, since it will trigger the data usage callback
+        incrementCurrentTime(DAY_IN_MILLIS);
+        expectCurrentTime();
+        expectDefaultSettings();
+        expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, ROAMING_NO,
+                        128000000L, 2L, 128000000L, 2L, 0L)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, ROAMING_NO,
+                        64000000L, 1L, 64000000L, 1L, 0L));
+        expectNetworkStatsPoll();
+
+        replay();
+        forcePollAndWaitForIdle();
+
+        // verify service recorded history
+        assertUidTotal(sTemplateImsi1, UID_RED, 128000000L, 2L, 128000000L, 2L, 0);
+
+        // verify entire history present
+        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,
+                128000000L, 2L, 128000000L, 2L, 0);
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, ROAMING_YES,
+                64000000L, 1L, 64000000L, 1L, 0);
+
+        verifyAndReset();
+
+        // Wait for the caller to ack receipt of CALLBACK_LIMIT_REACHED
+        assertTrue(cv.block(WAIT_TIMEOUT));
+        assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, latchedHandler.mLastMessageType);
+        cv.close();
+
+        // Allow binder to disconnect
+        expect(mockBinder.unlinkToDeath((IBinder.DeathRecipient) anyObject(), anyInt()))
+                .andReturn(true);
+        EasyMock.replay(mockBinder);
+
+        // Unregister request
+        mService.unregisterDataUsageRequest(request);
+
+        // Wait for the caller to ack receipt of CALLBACK_RELEASED
+        assertTrue(cv.block(WAIT_TIMEOUT));
+        assertEquals(NetworkStatsManager.CALLBACK_RELEASED, latchedHandler.mLastMessageType);
+
+        // Make sure that the caller binder gets disconnected
+        EasyMock.verify(mockBinder);
+    }
+
+    public void testUnregisterDataUsageCallback_unknown_noop() throws Exception {
+        String callingPackage = "the.calling.package";
+        long thresholdInBytes = 10 * 1024 * 1024;  // 10 MB
+        NetworkTemplate[] templates = new NetworkTemplate[] { sTemplateImsi1, sTemplateImsi2 };
+        DataUsageRequest unknownRequest = new DataUsageRequest(
+                2, templates, null /* uids */, thresholdInBytes);
+
+        mService.unregisterDataUsageRequest(unknownRequest);
+    }
+
+    private static File getBaseDir(File statsDir) {
+        File baseDir = new File(statsDir, "netstats");
+        baseDir.mkdirs();
+        return baseDir;
     }
 
     private void assertNetworkTotal(NetworkTemplate template, long rxBytes, long rxPackets,
@@ -851,17 +1180,19 @@
 
         // verify summary API
         final NetworkStats stats = mSession.getSummaryForNetwork(template, start, end);
-        assertValues(stats, IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes,
-                txPackets, operations);
+        assertValues(stats, IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 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, rxBytes, rxPackets, txBytes, txPackets, operations);
+        assertUidTotal(template, uid, SET_ALL, ROAMING_ALL, rxBytes, rxPackets, txBytes, txPackets,
+                operations);
     }
 
-    private void assertUidTotal(NetworkTemplate template, int uid, int set, long rxBytes,
-            long rxPackets, long txBytes, long txPackets, int operations) throws Exception {
+    private void assertUidTotal(NetworkTemplate template, int uid, int set, int roaming,
+            long rxBytes, long rxPackets, long txBytes, long txPackets, int operations)
+            throws Exception {
         // verify history API
         final NetworkStatsHistory history = mSession.getHistoryForUid(
                 template, uid, set, TAG_NONE, FIELD_ALL);
@@ -871,23 +1202,16 @@
         // verify summary API
         final NetworkStats stats = mSession.getSummaryForAllUid(
                 template, Long.MIN_VALUE, Long.MAX_VALUE, false);
-        assertValues(stats, IFACE_ALL, uid, set, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets,
-                operations);
+        assertValues(stats, IFACE_ALL, uid, set, TAG_NONE, roaming, rxBytes, rxPackets, txBytes,
+                txPackets, operations);
     }
 
     private void expectSystemReady() throws Exception {
-        mAlarmManager.remove(isA(PendingIntent.class));
-        expectLastCall().anyTimes();
-
-        mAlarmManager.set(eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), anyLong(),
-                anyInt(), isA(PendingIntent.class), isA(WorkSource.class),
-                isA(AlarmManager.AlarmClockInfo.class));
-        expectLastCall().atLeastOnce();
-
         mNetManager.setGlobalAlert(anyLong());
         expectLastCall().atLeastOnce();
 
-        expect(mNetManager.isBandwidthControlEnabled()).andReturn(true).atLeastOnce();
+        expectNetworkStatsSummary(buildEmptyStats());
+        expectBandwidthControlCheck();
     }
 
     private void expectNetworkState(NetworkState... state) throws Exception {
@@ -898,6 +1222,8 @@
     }
 
     private void expectNetworkStatsSummary(NetworkStats summary) throws Exception {
+        expect(mConnManager.getAllVpnInfo()).andReturn(new VpnInfo[0]).atLeastOnce();
+
         expectNetworkStatsSummaryDev(summary);
         expectNetworkStatsSummaryXt(summary);
     }
@@ -960,6 +1286,10 @@
         expectLastCall().anyTimes();
     }
 
+    private void expectBandwidthControlCheck() throws Exception {
+        expect(mNetManager.isBandwidthControlEnabled()).andReturn(true).atLeastOnce();
+    }
+
     private void assertStatsFilesExist(boolean exist) {
         final File basePath = new File(mStatsDir, "netstats");
         if (exist) {
@@ -970,18 +1300,31 @@
     }
 
     private static void assertValues(NetworkStats stats, String iface, int uid, int set,
-            int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, int operations) {
+            int tag, 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) {
-            final int i = stats.findIndex(iface, uid, SET_DEFAULT, tag);
-            if (i != -1) {
-                entry.add(stats.getValues(i, null));
-            }
+            sets.add(SET_DEFAULT);
         }
         if (set == SET_FOREGROUND || set == SET_ALL) {
-            final int i = stats.findIndex(iface, uid, SET_FOREGROUND, tag);
-            if (i != -1) {
-                entry.add(stats.getValues(i, null));
+            sets.add(SET_FOREGROUND);
+        }
+
+        List<Integer> roamings = new ArrayList<>();
+        if (roaming == ROAMING_NO || roaming == ROAMING_ALL) {
+            roamings.add(ROAMING_NO);
+        }
+        if (roaming == ROAMING_YES || roaming == ROAMING_ALL) {
+            roamings.add(ROAMING_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));
+                }
             }
         }
 
@@ -1011,9 +1354,14 @@
     }
 
     private static NetworkState buildMobile3gState(String subscriberId) {
+        return buildMobile3gState(subscriberId, false /* isRoaming */);
+    }
+
+    private static NetworkState buildMobile3gState(String subscriberId, boolean isRoaming) {
         final NetworkInfo info = new NetworkInfo(
                 TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UMTS, null, null);
         info.setDetailedState(DetailedState.CONNECTED, null, null);
+        info.setRoaming(isRoaming);
         final LinkProperties prop = new LinkProperties();
         prop.setInterfaceName(TEST_IFACE);
         return new NetworkState(info, prop, null, null, subscriberId, null);
@@ -1048,11 +1396,75 @@
     }
 
     private void replay() {
-        EasyMock.replay(mNetManager, mAlarmManager, mTime, mSettings, mConnManager);
+        EasyMock.replay(mNetManager, mTime, mSettings, mConnManager);
     }
 
     private void verifyAndReset() {
-        EasyMock.verify(mNetManager, mAlarmManager, mTime, mSettings, mConnManager);
-        EasyMock.reset(mNetManager, mAlarmManager, mTime, mSettings, mConnManager);
+        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
+        mHandler.sendMessage(mHandler.obtainMessage(-1));
+        mHandlerThread.waitForIdle(WAIT_TIMEOUT);
+    }
+
+    static class LatchedHandler extends Handler {
+        private final ConditionVariable mCv;
+        int mLastMessageType = INVALID_TYPE;
+
+        LatchedHandler(Looper looper, ConditionVariable cv) {
+            super(looper);
+            mCv = cv;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            mLastMessageType = msg.what;
+            mCv.open();
+            super.handleMessage(msg);
+        }
+    }
+
+    /**
+     * A subclass of HandlerThread that allows callers to wait for it to become idle. waitForIdle
+     * will return immediately if the handler is already idle.
+     */
+    static class IdleableHandlerThread extends HandlerThread {
+        private IdleHandler mIdleHandler;
+
+        public IdleableHandlerThread(String name) {
+            super(name);
+        }
+
+        public void waitForIdle(long 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() {
+                        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);
+            }
+        }
+    }
+
 }