Use "merge -s ours origin/mirror-partner-mm-wireless-dev" as described in b/26154276
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index ad9058f..515e9a2 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -1028,13 +1028,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) {
@@ -1046,6 +1058,14 @@
             return TYPE_NONE;
         }
 
+        // Do this only for SUPL, until GpsLocationProvider 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;
 
@@ -1062,7 +1082,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)
@@ -2484,6 +2504,23 @@
     }
 
     /**
+     * Helper function to requests 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
@@ -2513,8 +2550,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));
     }
 
     /**
@@ -2524,7 +2561,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
@@ -2537,12 +2574,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));
     }
 
     /**
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/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/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 327fb8a..65a27c8 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -34,6 +34,7 @@
 
 import android.annotation.Nullable;
 import android.app.AlarmManager;
+import android.app.BroadcastOptions;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -72,6 +73,7 @@
 import android.net.UidRange;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.FileUtils;
 import android.os.Handler;
@@ -253,11 +255,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;
@@ -754,6 +751,8 @@
         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);
         mContext.registerReceiverAsUser(
                 mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
 
@@ -1364,7 +1363,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) {
@@ -1526,6 +1526,7 @@
                 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 +1534,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 +1548,7 @@
                 }
             }
             try {
-                mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+                mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL, options);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -2505,11 +2510,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;
@@ -3527,6 +3527,26 @@
         }
     }
 
+    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 BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -3538,6 +3558,10 @@
                 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);
             }
         }
     };
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
index 97e16da..27deb72 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -591,6 +591,12 @@
     public void setUp() throws Exception {
         super.setUp();
 
+        // 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),
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index c12f978..204bc2e 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -18,7 +18,6 @@
 
 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;
@@ -49,6 +48,7 @@
 import static org.easymock.EasyMock.isA;
 
 import android.app.AlarmManager;
+import android.app.IAlarmListener;
 import android.app.IAlarmManager;
 import android.app.PendingIntent;
 import android.content.Intent;
@@ -70,6 +70,7 @@
 import android.test.suitebuilder.annotation.Suppress;
 import android.util.TrustedTime;
 
+import com.android.internal.net.VpnInfo;
 import com.android.server.net.NetworkStatsService;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
@@ -83,6 +84,9 @@
 
 /**
  * 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 {
@@ -143,7 +147,6 @@
 
         expectCurrentTime();
         expectDefaultSettings();
-        expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectSystemReady();
 
@@ -190,9 +193,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);
@@ -244,9 +248,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);
@@ -295,7 +300,6 @@
         // boot through serviceReady() again
         expectCurrentTime();
         expectDefaultSettings();
-        expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectSystemReady();
 
@@ -335,9 +339,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
@@ -387,9 +392,10 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectNetworkStatsPoll();
+        expectBandwidthControlCheck();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        mService.forceUpdateIfaces();
         verifyAndReset();
 
         // create some traffic on first network
@@ -429,9 +435,10 @@
                 .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));
+        mService.forceUpdateIfaces();
         mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
         verifyAndReset();
 
@@ -475,9 +482,10 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectNetworkStatsPoll();
+        expectBandwidthControlCheck();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        mService.forceUpdateIfaces();
         verifyAndReset();
 
         // create some traffic
@@ -544,9 +552,10 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectNetworkStatsPoll();
+        expectBandwidthControlCheck();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        mService.forceUpdateIfaces();
         verifyAndReset();
 
         // create some traffic
@@ -578,9 +587,10 @@
                 .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));
+        mService.forceUpdateIfaces();
         mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
         verifyAndReset();
 
@@ -615,9 +625,10 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectNetworkStatsPoll();
+        expectBandwidthControlCheck();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        mService.forceUpdateIfaces();
         verifyAndReset();
 
         // create some traffic for two apps
@@ -681,9 +692,10 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectNetworkStatsPoll();
+        expectBandwidthControlCheck();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        mService.forceUpdateIfaces();
         verifyAndReset();
 
         // create some initial traffic
@@ -746,9 +758,10 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectNetworkStatsPoll();
+        expectBandwidthControlCheck();
 
         replay();
-        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        mService.forceUpdateIfaces();
         verifyAndReset();
 
         // create some tethering traffic
@@ -778,65 +791,6 @@
 
     }
 
-    public void testReportXtOverDev() throws Exception {
-        // bring mobile network online
-        expectCurrentTime();
-        expectDefaultSettings();
-        expectNetworkState(buildMobile3gState(IMSI_1));
-        expectNetworkStatsSummary(buildEmptyStats());
-        expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
-
-        replay();
-        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
-        verifyAndReset();
-
-        // create some traffic, but only for DEV, and across 1.5 buckets
-        incrementCurrentTime(90 * MINUTE_IN_MILLIS);
-        expectCurrentTime();
-        expectDefaultSettings();
-        expectNetworkStatsSummaryDev(new NetworkStats(getElapsedRealtime(), 1)
-                .addIfaceValues(TEST_IFACE, 6000L, 60L, 3000L, 30L));
-        expectNetworkStatsSummaryXt(buildEmptyStats());
-        expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
-
-        replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
-
-        // verify service recorded history:
-        // 4000(dev) + 2000(dev)
-        assertNetworkTotal(sTemplateImsi1, 6000L, 60L, 3000L, 30L, 0);
-        verifyAndReset();
-
-        // create traffic on both DEV and XT, across two buckets
-        incrementCurrentTime(2 * HOUR_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));
-        expectNetworkStatsUidDetail(buildEmptyStats());
-        expectNetworkStatsPoll();
-
-        replay();
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
-
-        // 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);
-
-        // 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);
-
-        verifyAndReset();
-    }
-
     private void assertNetworkTotal(NetworkTemplate template, long rxBytes, long rxPackets,
             long txBytes, long txPackets, int operations) throws Exception {
         assertNetworkTotal(template, Long.MIN_VALUE, Long.MAX_VALUE, rxBytes, rxPackets, txBytes,
@@ -876,19 +830,21 @@
     }
 
     private void expectSystemReady() throws Exception {
-        mAlarmManager.remove(isA(PendingIntent.class), null);
+        mAlarmManager.remove(isA(PendingIntent.class), EasyMock.<IAlarmListener>isNull());
         expectLastCall().anyTimes();
 
-        mAlarmManager.set(getContext().getPackageName(),
+        mAlarmManager.set(eq(getContext().getPackageName()),
                 eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), anyLong(),
-                anyInt(), isA(PendingIntent.class), null, null, isA(WorkSource.class),
-                isA(AlarmManager.AlarmClockInfo.class));
-        expectLastCall().atLeastOnce();
+                anyInt(), isA(PendingIntent.class), EasyMock.<IAlarmListener>isNull(),
+                EasyMock.<String>isNull(), EasyMock.<WorkSource>isNull(),
+                EasyMock.<AlarmManager.AlarmClockInfo>isNull());
+        expectLastCall().anyTimes();
 
         mNetManager.setGlobalAlert(anyLong());
         expectLastCall().atLeastOnce();
 
-        expect(mNetManager.isBandwidthControlEnabled()).andReturn(true).atLeastOnce();
+        expectNetworkStatsSummary(buildEmptyStats());
+        expectBandwidthControlCheck();
     }
 
     private void expectNetworkState(NetworkState... state) throws Exception {
@@ -899,6 +855,8 @@
     }
 
     private void expectNetworkStatsSummary(NetworkStats summary) throws Exception {
+        expect(mConnManager.getAllVpnInfo()).andReturn(new VpnInfo[0]).atLeastOnce();
+
         expectNetworkStatsSummaryDev(summary);
         expectNetworkStatsSummaryXt(summary);
     }
@@ -961,6 +919,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) {
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..6026644 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));
+
+        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);
     }