Name specific app for rapid data usage.

When a single app is responsible for more than half of the data usage
that caused us to trigger a "rapid usage" alert, name that app in the
notification.  Tests to verify.

Move NPMS->NSS direct calls to "Internal" pattern, following
best-practices to avoid unnecessary AIDL exposure.

Remove 3G/4G split mobile plan support, which has been deprecated for
years and was never supported in a shipping product.

Move MultipathPolicyTracker in tree to reflect its package name.

Test: bit FrameworksNetTests:*
Test: bit FrameworksServicesTests:com.android.server.NetworkPolicyManagerServiceTest
Bug: 69263587, 64221505, 73431080, 72746951
Exempt-From-Owner-Approval: approved in previous PS
Change-Id: I3e4ec1ae2222d51b232f76f32faca93d4f8cd272
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl
index 90e3ffd..381cfb6 100644
--- a/core/java/android/net/INetworkStatsService.aidl
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -39,9 +39,6 @@
      */
     INetworkStatsSession openSessionForUsageStats(int flags, String callingPackage);
 
-    /** Return network layer usage total for traffic that matches template. */
-    long getNetworkTotalBytes(in NetworkTemplate template, long start, long end);
-
     /** Return data layer snapshot of UID network usage. */
     NetworkStats getDataLayerSnapshotForUid(int uid);
     /** Return set of any ifaces associated with mobile networks since boot. */
@@ -50,17 +47,11 @@
     /** Increment data layer count of operations performed for UID and tag. */
     void incrementOperationCount(int uid, int tag, int operationCount);
 
-    /** Mark given UID as being in foreground for stats purposes. */
-    void setUidForeground(int uid, boolean uidForeground);
-
     /** Force update of ifaces. */
     void forceUpdateIfaces(in Network[] defaultNetworks);
     /** Force update of statistics. */
     void forceUpdate();
 
-    /** Advise persistance threshold; may be overridden internally. */
-    void advisePersistThreshold(long thresholdBytes);
-
     /** Registers a callback on data usage. */
     DataUsageRequest registerUsageCallback(String callingPackage,
             in DataUsageRequest request, in Messenger messenger, in IBinder binder);
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 01b2b39..16fb858 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -234,7 +234,7 @@
     public NetworkStats(long elapsedRealtime, int initialSize) {
         this.elapsedRealtime = elapsedRealtime;
         this.size = 0;
-        if (initialSize >= 0) {
+        if (initialSize > 0) {
             this.capacity = initialSize;
             this.iface = new String[initialSize];
             this.uid = new int[initialSize];
@@ -250,19 +250,7 @@
             this.operations = new long[initialSize];
         } else {
             // Special case for use by NetworkStatsFactory to start out *really* empty.
-            this.capacity = 0;
-            this.iface = EmptyArray.STRING;
-            this.uid = EmptyArray.INT;
-            this.set = EmptyArray.INT;
-            this.tag = EmptyArray.INT;
-            this.metered = EmptyArray.INT;
-            this.roaming = EmptyArray.INT;
-            this.defaultNetwork = EmptyArray.INT;
-            this.rxBytes = EmptyArray.LONG;
-            this.rxPackets = EmptyArray.LONG;
-            this.txBytes = EmptyArray.LONG;
-            this.txPackets = EmptyArray.LONG;
-            this.operations = EmptyArray.LONG;
+            clear();
         }
     }
 
@@ -314,6 +302,25 @@
         return clone;
     }
 
+    /**
+     * Clear all data stored in this object.
+     */
+    public void clear() {
+        this.capacity = 0;
+        this.iface = EmptyArray.STRING;
+        this.uid = EmptyArray.INT;
+        this.set = EmptyArray.INT;
+        this.tag = EmptyArray.INT;
+        this.metered = EmptyArray.INT;
+        this.roaming = EmptyArray.INT;
+        this.defaultNetwork = EmptyArray.INT;
+        this.rxBytes = EmptyArray.LONG;
+        this.rxPackets = EmptyArray.LONG;
+        this.txBytes = EmptyArray.LONG;
+        this.txPackets = EmptyArray.LONG;
+        this.operations = EmptyArray.LONG;
+    }
+
     @VisibleForTesting
     public NetworkStats addIfaceValues(
             String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) {
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index 433f941..a13ad65 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -39,6 +39,8 @@
 
 import com.android.internal.util.IndentingPrintWriter;
 
+import libcore.util.EmptyArray;
+
 import java.io.CharArrayWriter;
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
@@ -459,6 +461,21 @@
     }
 
     /**
+     * Clear all data stored in this object.
+     */
+    public void clear() {
+        bucketStart = EmptyArray.LONG;
+        if (activeTime != null) activeTime = EmptyArray.LONG;
+        if (rxBytes != null) rxBytes = EmptyArray.LONG;
+        if (rxPackets != null) rxPackets = EmptyArray.LONG;
+        if (txBytes != null) txBytes = EmptyArray.LONG;
+        if (txPackets != null) txPackets = EmptyArray.LONG;
+        if (operations != null) operations = EmptyArray.LONG;
+        bucketCount = 0;
+        totalBytes = 0;
+    }
+
+    /**
      * Remove buckets older than requested cutoff.
      */
     @Deprecated
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index 8efd39a..74233fd 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -23,7 +23,6 @@
 import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
 import static android.net.ConnectivityManager.TYPE_WIMAX;
-import static android.net.NetworkIdentity.COMBINE_SUBTYPE_ENABLED;
 import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
 import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
 import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
@@ -34,11 +33,6 @@
 import static android.net.NetworkStats.ROAMING_NO;
 import static android.net.NetworkStats.ROAMING_YES;
 import static android.net.wifi.WifiInfo.removeDoubleQuotes;
-import static android.telephony.TelephonyManager.NETWORK_CLASS_2_G;
-import static android.telephony.TelephonyManager.NETWORK_CLASS_3_G;
-import static android.telephony.TelephonyManager.NETWORK_CLASS_4_G;
-import static android.telephony.TelephonyManager.NETWORK_CLASS_UNKNOWN;
-import static android.telephony.TelephonyManager.getNetworkClass;
 
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -55,8 +49,8 @@
 import java.util.Objects;
 
 /**
- * Template definition used to generically match {@link NetworkIdentity},
- * usually when collecting statistics.
+ * Predicate used to match {@link NetworkIdentity}, usually when collecting
+ * statistics. (It should probably have been named {@code NetworkPredicate}.)
  *
  * @hide
  */
@@ -68,13 +62,7 @@
      */
     private static final int BACKUP_VERSION = 1;
 
-    public static final int MATCH_MOBILE_ALL = 1;
-    /** @deprecated don't use this any more */
-    @Deprecated
-    public static final int MATCH_MOBILE_3G_LOWER = 2;
-    /** @deprecated don't use this any more */
-    @Deprecated
-    public static final int MATCH_MOBILE_4G = 3;
+    public static final int MATCH_MOBILE = 1;
     public static final int MATCH_WIFI = 4;
     public static final int MATCH_ETHERNET = 5;
     public static final int MATCH_MOBILE_WILDCARD = 6;
@@ -84,9 +72,7 @@
 
     private static boolean isKnownMatchRule(final int rule) {
         switch (rule) {
-            case MATCH_MOBILE_ALL:
-            case MATCH_MOBILE_3G_LOWER:
-            case MATCH_MOBILE_4G:
+            case MATCH_MOBILE:
             case MATCH_WIFI:
             case MATCH_ETHERNET:
             case MATCH_MOBILE_WILDCARD:
@@ -111,25 +97,7 @@
      * the given IMSI.
      */
     public static NetworkTemplate buildTemplateMobileAll(String subscriberId) {
-        return new NetworkTemplate(MATCH_MOBILE_ALL, subscriberId, null);
-    }
-
-    /**
-     * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks with
-     * the given IMSI that roughly meet a "3G" definition, or lower.
-     */
-    @Deprecated
-    public static NetworkTemplate buildTemplateMobile3gLower(String subscriberId) {
-        return new NetworkTemplate(MATCH_MOBILE_3G_LOWER, subscriberId, null);
-    }
-
-    /**
-     * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks with
-     * the given IMSI that roughly meet a "4G" definition.
-     */
-    @Deprecated
-    public static NetworkTemplate buildTemplateMobile4g(String subscriberId) {
-        return new NetworkTemplate(MATCH_MOBILE_4G, subscriberId, null);
+        return new NetworkTemplate(MATCH_MOBILE, subscriberId, null);
     }
 
     /**
@@ -307,9 +275,7 @@
 
     public boolean isMatchRuleMobile() {
         switch (mMatchRule) {
-            case MATCH_MOBILE_3G_LOWER:
-            case MATCH_MOBILE_4G:
-            case MATCH_MOBILE_ALL:
+            case MATCH_MOBILE:
             case MATCH_MOBILE_WILDCARD:
                 return true;
             default:
@@ -348,12 +314,8 @@
         if (!matchesDefaultNetwork(ident)) return false;
 
         switch (mMatchRule) {
-            case MATCH_MOBILE_ALL:
+            case MATCH_MOBILE:
                 return matchesMobile(ident);
-            case MATCH_MOBILE_3G_LOWER:
-                return matchesMobile3gLower(ident);
-            case MATCH_MOBILE_4G:
-                return matchesMobile4g(ident);
             case MATCH_WIFI:
                 return matchesWifi(ident);
             case MATCH_ETHERNET:
@@ -410,43 +372,6 @@
     }
 
     /**
-     * Check if mobile network classified 3G or lower with matching IMSI.
-     */
-    @Deprecated
-    private boolean matchesMobile3gLower(NetworkIdentity ident) {
-        ensureSubtypeAvailable();
-        if (ident.mType == TYPE_WIMAX) {
-            return false;
-        } else if (matchesMobile(ident)) {
-            switch (getNetworkClass(ident.mSubType)) {
-                case NETWORK_CLASS_UNKNOWN:
-                case NETWORK_CLASS_2_G:
-                case NETWORK_CLASS_3_G:
-                    return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Check if mobile network classified 4G with matching IMSI.
-     */
-    @Deprecated
-    private boolean matchesMobile4g(NetworkIdentity ident) {
-        ensureSubtypeAvailable();
-        if (ident.mType == TYPE_WIMAX) {
-            // TODO: consider matching against WiMAX subscriber identity
-            return true;
-        } else if (matchesMobile(ident)) {
-            switch (getNetworkClass(ident.mSubType)) {
-                case NETWORK_CLASS_4_G:
-                    return true;
-            }
-        }
-        return false;
-    }
-
-    /**
      * Check if matches Wi-Fi network template.
      */
     private boolean matchesWifi(NetworkIdentity ident) {
@@ -506,12 +431,8 @@
 
     private static String getMatchRuleName(int matchRule) {
         switch (matchRule) {
-            case MATCH_MOBILE_3G_LOWER:
-                return "MOBILE_3G_LOWER";
-            case MATCH_MOBILE_4G:
-                return "MOBILE_4G";
-            case MATCH_MOBILE_ALL:
-                return "MOBILE_ALL";
+            case MATCH_MOBILE:
+                return "MOBILE";
             case MATCH_WIFI:
                 return "WIFI";
             case MATCH_ETHERNET:
@@ -529,13 +450,6 @@
         }
     }
 
-    private static void ensureSubtypeAvailable() {
-        if (COMBINE_SUBTYPE_ENABLED) {
-            throw new IllegalArgumentException(
-                    "Unable to enforce 3G_LOWER template on combined data.");
-        }
-    }
-
     /**
      * Examine the given template and normalize if it refers to a "merged"
      * mobile subscriber. We pick the "lowest" merged subscriber as the primary
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e6186023..bf28414 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3768,31 +3768,23 @@
     <string name="extract_edit_menu_button">Edit</string>
 
     <!-- Notification title when data usage has exceeded warning threshold. [CHAR LIMIT=50] -->
-    <string name="data_usage_warning_title">Data usage alert</string>
+    <string name="data_usage_warning_title">Data warning</string>
     <!-- Notification body when data usage has exceeded warning threshold. [CHAR LIMIT=32] -->
-    <string name="data_usage_warning_body">Tap to view usage and settings.</string>
+    <string name="data_usage_warning_body">You've used <xliff:g id="app" example="3.8GB">%s</xliff:g> of data</string>
 
-    <!-- Notification title when 2G-3G data usage has exceeded limit threshold, and has been disabled. [CHAR LIMIT=32] -->
-    <string name="data_usage_3g_limit_title">2G-3G data limit reached</string>
-    <!-- Notification title when 4G data usage has exceeded limit threshold, and has been disabled. [CHAR LIMIT=32] -->
-    <string name="data_usage_4g_limit_title">4G data limit reached</string>
     <!-- Notification title when mobile data usage has exceeded limit threshold, and has been disabled. [CHAR LIMIT=50] -->
     <string name="data_usage_mobile_limit_title">Mobile data limit reached</string>
     <!-- Notification title when Wi-Fi data usage has exceeded limit threshold, and has been disabled. [CHAR LIMIT=32] -->
     <string name="data_usage_wifi_limit_title">Wi-Fi data limit reached</string>
     <!-- Notification body when data usage has exceeded limit threshold, and has been disabled. -->
-    <string name="data_usage_limit_body">Data paused for rest of cycle</string>
+    <string name="data_usage_limit_body">Data paused for the rest of your cycle</string>
 
-    <!-- Notification title when 2G-3G data usage has exceeded limit threshold. [CHAR LIMIT=32] -->
-    <string name="data_usage_3g_limit_snoozed_title">2G-3G data limit exceeded</string>
-    <!-- Notification title when 4G data usage has exceeded limit threshold. [CHAR LIMIT=32] -->
-    <string name="data_usage_4g_limit_snoozed_title">4G data limit exceeded</string>
     <!-- Notification title when mobile data usage has exceeded limit threshold. [CHAR LIMIT=32] -->
-    <string name="data_usage_mobile_limit_snoozed_title">Mobile data limit exceeded</string>
+    <string name="data_usage_mobile_limit_snoozed_title">Over your mobile data limit</string>
     <!-- Notification title when Wi-Fi data usage has exceeded limit threshold. [CHAR LIMIT=32] -->
-    <string name="data_usage_wifi_limit_snoozed_title">Wi-Fi data limit exceeded</string>
+    <string name="data_usage_wifi_limit_snoozed_title">Over your Wi-Fi data limit</string>
     <!-- Notification body when data usage has exceeded limit threshold. -->
-    <string name="data_usage_limit_snoozed_body"><xliff:g id="size" example="3.8GB">%s</xliff:g> over specified limit.</string>
+    <string name="data_usage_limit_snoozed_body">You've gone <xliff:g id="size" example="3.8GB">%s</xliff:g> over your set limit</string>
 
     <!-- Notification title when background data usage is limited. [CHAR LIMIT=32] -->
     <string name="data_usage_restricted_title">Background data restricted</string>
@@ -3800,9 +3792,11 @@
     <string name="data_usage_restricted_body">Tap to remove restriction.</string>
 
     <!-- Notification title when there has been recent excessive data usage. [CHAR LIMIT=32] -->
-    <string name="data_usage_rapid_title">Large data usage</string>
+    <string name="data_usage_rapid_title">High mobile data usage</string>
     <!-- Notification body when there has been recent excessive data usage. [CHAR LIMIT=128] -->
-    <string name="data_usage_rapid_body">Your data usage over the last few days is larger than normal. Tap to view usage and settings.</string>
+    <string name="data_usage_rapid_body">Your apps have used more data than usual</string>
+    <!-- Notification body when there has been recent excessive data usage by a specific app. [CHAR LIMIT=128] -->
+    <string name="data_usage_rapid_app_body"><xliff:g id="app" example="Calculator">%s</xliff:g> has used more data than usual</string>
 
     <!-- SSL Certificate dialogs -->
     <!-- Title for an SSL Certificate dialog -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0493c2b..81af8c5 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1970,10 +1970,6 @@
   <java-symbol type="string" name="config_wimaxServiceClassname" />
   <java-symbol type="string" name="config_wimaxServiceJarLocation" />
   <java-symbol type="string" name="config_wimaxStateTrackerClassname" />
-  <java-symbol type="string" name="data_usage_3g_limit_snoozed_title" />
-  <java-symbol type="string" name="data_usage_3g_limit_title" />
-  <java-symbol type="string" name="data_usage_4g_limit_snoozed_title" />
-  <java-symbol type="string" name="data_usage_4g_limit_title" />
   <java-symbol type="string" name="data_usage_limit_body" />
   <java-symbol type="string" name="data_usage_limit_snoozed_body" />
   <java-symbol type="string" name="data_usage_mobile_limit_snoozed_title" />
@@ -1986,6 +1982,7 @@
   <java-symbol type="string" name="data_usage_wifi_limit_title" />
   <java-symbol type="string" name="data_usage_rapid_title" />
   <java-symbol type="string" name="data_usage_rapid_body" />
+  <java-symbol type="string" name="data_usage_rapid_app_body" />
   <java-symbol type="string" name="default_wallpaper_component" />
   <java-symbol type="string" name="device_storage_monitor_notification_channel" />
   <java-symbol type="string" name="dlg_ok" />
diff --git a/packages/SystemUI/src/com/android/systemui/net/NetworkOverLimitActivity.java b/packages/SystemUI/src/com/android/systemui/net/NetworkOverLimitActivity.java
index f201165..be3168c 100644
--- a/packages/SystemUI/src/com/android/systemui/net/NetworkOverLimitActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/net/NetworkOverLimitActivity.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.net;
 
+import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
+import static android.net.NetworkTemplate.MATCH_MOBILE;
+
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
@@ -32,11 +35,6 @@
 
 import com.android.systemui.R;
 
-import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
-import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
-import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
-import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
-
 /**
  * Notify user that a {@link NetworkTemplate} is over its
  * {@link NetworkPolicy#limitBytes}, giving them the choice of acknowledging or
@@ -85,11 +83,7 @@
 
     private static int getLimitedDialogTitleForTemplate(NetworkTemplate template) {
         switch (template.getMatchRule()) {
-            case MATCH_MOBILE_3G_LOWER:
-                return R.string.data_usage_disabled_dialog_3g_title;
-            case MATCH_MOBILE_4G:
-                return R.string.data_usage_disabled_dialog_4g_title;
-            case MATCH_MOBILE_ALL:
+            case MATCH_MOBILE:
                 return R.string.data_usage_disabled_dialog_mobile_title;
             default:
                 return R.string.data_usage_disabled_dialog_title;
diff --git a/services/core/java/com/android/server/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
similarity index 85%
rename from services/core/java/com/android/server/MultipathPolicyTracker.java
rename to services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
index 9e0a230..296b9ac 100644
--- a/services/core/java/com/android/server/MultipathPolicyTracker.java
+++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
@@ -16,11 +16,17 @@
 
 package com.android.server.connectivity;
 
+import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER;
+import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+
+import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
+
 import android.app.usage.NetworkStatsManager;
 import android.app.usage.NetworkStatsManager.UsageCallback;
 import android.content.Context;
-import android.net.INetworkStatsService;
-import android.net.INetworkPolicyManager;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.Network;
@@ -31,29 +37,17 @@
 import android.net.NetworkTemplate;
 import android.net.StringNetworkSpecifier;
 import android.os.Handler;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.telephony.TelephonyManager;
 import android.util.DebugUtils;
 import android.util.Slog;
 
-import java.util.Calendar;
-import java.util.concurrent.ConcurrentHashMap;
-
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.server.net.NetworkStatsManagerInternal;
 
-import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER;
-import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY;
-import static android.net.ConnectivityManager.TYPE_MOBILE;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI;
-import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
-import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
+import java.util.Calendar;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Manages multipath data budgets.
@@ -76,10 +70,8 @@
     private final Handler mHandler;
 
     private ConnectivityManager mCM;
-    private NetworkStatsManager mStatsManager;
     private NetworkPolicyManager mNPM;
-    private TelephonyManager mTelephonyManager;
-    private INetworkStatsService mStatsService;
+    private NetworkStatsManager mStatsManager;
 
     private NetworkCallback mMobileNetworkCallback;
     private NetworkPolicyManager.Listener mPolicyListener;
@@ -87,8 +79,6 @@
     // STOPSHIP: replace this with a configurable mechanism.
     private static final long DEFAULT_DAILY_MULTIPATH_QUOTA = 2_500_000;
 
-    private volatile int mMeteredMultipathPreference;
-
     public MultipathPolicyTracker(Context ctx, Handler handler) {
         mContext = ctx;
         mHandler = handler;
@@ -97,12 +87,9 @@
     }
 
     public void start() {
-        mCM = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
-        mNPM = (NetworkPolicyManager) mContext.getSystemService(Context.NETWORK_POLICY_SERVICE);
-        mStatsManager = (NetworkStatsManager) mContext.getSystemService(
-                Context.NETWORK_STATS_SERVICE);
-        mStatsService = INetworkStatsService.Stub.asInterface(
-                ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
+        mCM = mContext.getSystemService(ConnectivityManager.class);
+        mNPM = mContext.getSystemService(NetworkPolicyManager.class);
+        mStatsManager = mContext.getSystemService(NetworkStatsManager.class);
 
         registerTrackMobileCallback();
         registerNetworkPolicyListener();
@@ -119,6 +106,9 @@
 
     // Called on an arbitrary binder thread.
     public Integer getMultipathPreference(Network network) {
+        if (network == null) {
+            return null;
+        }
         MultipathTracker t = mMultipathTrackers.get(network);
         if (t != null) {
             return t.getMultipathPreference();
@@ -149,8 +139,7 @@
                         network, nc, e.getMessage()));
             }
 
-            TelephonyManager tele = (TelephonyManager) mContext.getSystemService(
-                    Context.TELEPHONY_SERVICE);
+            TelephonyManager tele = mContext.getSystemService(TelephonyManager.class);
             if (tele == null) {
                 throw new IllegalStateException(String.format("Missing TelephonyManager"));
             }
@@ -162,7 +151,7 @@
 
             subscriberId = tele.getSubscriberId();
             mNetworkTemplate = new NetworkTemplate(
-                    NetworkTemplate.MATCH_MOBILE_ALL, subscriberId, new String[] { subscriberId },
+                    NetworkTemplate.MATCH_MOBILE, subscriberId, new String[] { subscriberId },
                     null, NetworkStats.METERED_ALL, NetworkStats.ROAMING_ALL,
                     NetworkStats.DEFAULT_NETWORK_NO);
             mUsageCallback = new UsageCallback() {
@@ -185,26 +174,21 @@
             start.set(Calendar.SECOND, 0);
             start.set(Calendar.MILLISECOND, 0);
 
-            long bytes;
             try {
-                // TODO: Consider using NetworkStatsManager.getSummaryForDevice instead.
-                bytes = mStatsService.getNetworkTotalBytes(mNetworkTemplate,
-                        start.getTimeInMillis(), end.getTimeInMillis());
-                if (DBG) Slog.w(TAG, "Non-default data usage: " + bytes);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Can't fetch daily data usage: " + e);
-                bytes = -1;
-            } catch (IllegalStateException e) {
-                // Bandwidth control disabled?
-                bytes = -1;
+                final long bytes = LocalServices.getService(NetworkStatsManagerInternal.class)
+                        .getNetworkTotalBytes(mNetworkTemplate, start.getTimeInMillis(),
+                                end.getTimeInMillis());
+                if (DBG) Slog.d(TAG, "Non-default data usage: " + bytes);
+                return bytes;
+            } catch (RuntimeException e) {
+                Slog.w(TAG, "Failed to get data usage: " + e);
+                return -1;
             }
-            return bytes;
         }
 
         void updateMultipathBudget() {
-            NetworkPolicyManagerInternal npms = LocalServices.getService(
-                    NetworkPolicyManagerInternal.class);
-            long quota = npms.getSubscriptionOpportunisticQuota(this.network, QUOTA_TYPE_MULTIPATH);
+            long quota = LocalServices.getService(NetworkPolicyManagerInternal.class)
+                    .getSubscriptionOpportunisticQuota(this.network, QUOTA_TYPE_MULTIPATH);
             if (DBG) Slog.d(TAG, "Opportunistic quota from data plan: " + quota + " bytes");
 
             if (quota == 0) {
@@ -223,8 +207,10 @@
             }
             mQuota = quota;
 
-            long usage = getDailyNonDefaultDataUsage();
-            long budget = Math.max(0, quota - usage);
+            // If we can't get current usage, assume the worst and don't give
+            // ourselves any budget to work with.
+            final long usage = getDailyNonDefaultDataUsage();
+            final long budget = (usage == -1) ? 0 : Math.max(0, quota - usage);
             if (budget > 0) {
                 if (DBG) Slog.d(TAG, "Setting callback for " + budget +
                         " bytes on network " + network);
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index bd9ec55..f29e0bb 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -28,6 +28,11 @@
 import static android.content.Intent.ACTION_USER_ADDED;
 import static android.content.Intent.ACTION_USER_REMOVED;
 import static android.content.Intent.EXTRA_UID;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
+import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
@@ -61,9 +66,7 @@
 import static android.net.NetworkPolicyManager.resolveNetworkId;
 import static android.net.NetworkPolicyManager.uidPoliciesToString;
 import static android.net.NetworkPolicyManager.uidRulesToString;
-import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
-import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
-import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
+import static android.net.NetworkTemplate.MATCH_MOBILE;
 import static android.net.NetworkTemplate.MATCH_WIFI;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
 import static android.net.TrafficStats.MB_IN_BYTES;
@@ -71,7 +74,6 @@
 import static android.telephony.CarrierConfigManager.DATA_CYCLE_THRESHOLD_DISABLED;
 import static android.telephony.CarrierConfigManager.DATA_CYCLE_USE_PLATFORM_DEFAULT;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-import static android.text.format.DateUtils.DAY_IN_MILLIS;
 
 import static com.android.internal.util.ArrayUtils.appendInt;
 import static com.android.internal.util.Preconditions.checkNotNull;
@@ -139,15 +141,16 @@
 import android.net.NetworkRequest;
 import android.net.NetworkSpecifier;
 import android.net.NetworkState;
+import android.net.NetworkStats;
 import android.net.NetworkTemplate;
 import android.net.StringNetworkSpecifier;
 import android.net.TrafficStats;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
+import android.os.BestClock;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Environment;
-import android.os.BestClock;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IDeviceIdleController;
@@ -359,7 +362,7 @@
 
     private final Context mContext;
     private final IActivityManager mActivityManager;
-    private final INetworkStatsService mNetworkStats;
+    private NetworkStatsManagerInternal mNetworkStats;
     private final INetworkManagementService mNetworkManager;
     private UsageStatsManagerInternal mUsageStats;
     private final Clock mClock;
@@ -516,10 +519,9 @@
     // TODO: migrate notifications to SystemUI
 
     public NetworkPolicyManagerService(Context context, IActivityManager activityManager,
-            INetworkStatsService networkStats, INetworkManagementService networkManagement) {
-        this(context, activityManager, networkStats, networkManagement,
-                AppGlobals.getPackageManager(), getDefaultClock(), getDefaultSystemDir(),
-                false);
+            INetworkManagementService networkManagement) {
+        this(context, activityManager, networkManagement, AppGlobals.getPackageManager(),
+                getDefaultClock(), getDefaultSystemDir(), false);
     }
 
     private static @NonNull File getDefaultSystemDir() {
@@ -532,11 +534,10 @@
     }
 
     public NetworkPolicyManagerService(Context context, IActivityManager activityManager,
-            INetworkStatsService networkStats, INetworkManagementService networkManagement,
-            IPackageManager pm, Clock clock, File systemDir, boolean suppressDefaultPolicy) {
+            INetworkManagementService networkManagement, IPackageManager pm, Clock clock,
+            File systemDir, boolean suppressDefaultPolicy) {
         mContext = checkNotNull(context, "missing context");
         mActivityManager = checkNotNull(activityManager, "missing activityManager");
-        mNetworkStats = checkNotNull(networkStats, "missing networkStats");
         mNetworkManager = checkNotNull(networkManagement, "missing networkManagement");
         mDeviceIdleController = IDeviceIdleController.Stub.asInterface(ServiceManager.getService(
                 Context.DEVICE_IDLE_CONTROLLER));
@@ -660,6 +661,7 @@
             }
 
             mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
+            mNetworkStats = LocalServices.getService(NetworkStatsManagerInternal.class);
 
             synchronized (mUidRulesFirstLock) {
                 synchronized (mNetworkPoliciesSecondLock) {
@@ -1063,9 +1065,9 @@
             if (policy.isOverLimit(totalBytes)) {
                 final boolean snoozedThisCycle = policy.lastLimitSnooze >= cycleStart;
                 if (snoozedThisCycle) {
-                    enqueueNotification(policy, TYPE_LIMIT_SNOOZED, totalBytes);
+                    enqueueNotification(policy, TYPE_LIMIT_SNOOZED, totalBytes, null);
                 } else {
-                    enqueueNotification(policy, TYPE_LIMIT, totalBytes);
+                    enqueueNotification(policy, TYPE_LIMIT, totalBytes, null);
                     notifyOverLimitNL(policy.template);
                 }
 
@@ -1074,7 +1076,7 @@
 
                 final boolean snoozedThisCycle = policy.lastWarningSnooze >= cycleStart;
                 if (policy.isOverWarning(totalBytes) && !snoozedThisCycle) {
-                    enqueueNotification(policy, TYPE_WARNING, totalBytes);
+                    enqueueNotification(policy, TYPE_WARNING, totalBytes, null);
                 }
             }
 
@@ -1082,7 +1084,9 @@
             // far past the plan limits.
             if (policy.limitBytes != LIMIT_DISABLED) {
                 final long recentDuration = TimeUnit.DAYS.toMillis(4);
-                final long recentBytes = getTotalBytes(policy.template, now - recentDuration, now);
+                final long recentStart = now - recentDuration;
+                final long recentEnd = now;
+                final long recentBytes = getTotalBytes(policy.template, recentStart, recentEnd);
 
                 final long cycleDuration = cycleEnd - cycleStart;
                 final long projectedBytes = (recentBytes * cycleDuration) / recentDuration;
@@ -1096,7 +1100,8 @@
                 final boolean snoozedRecently = policy.lastRapidSnooze >= now
                         - DateUtils.DAY_IN_MILLIS;
                 if (projectedBytes > alertBytes && !snoozedRecently) {
-                    enqueueNotification(policy, TYPE_RAPID, 0);
+                    enqueueNotification(policy, TYPE_RAPID, 0,
+                            findRapidBlame(policy.template, recentStart, recentEnd));
                 }
             }
         }
@@ -1111,6 +1116,45 @@
     }
 
     /**
+     * Attempt to find a specific app to blame for rapid data usage during the
+     * given time period.
+     */
+    private @Nullable ApplicationInfo findRapidBlame(NetworkTemplate template,
+            long start, long end) {
+        long totalBytes = 0;
+        long maxBytes = 0;
+        int maxUid = 0;
+
+        final NetworkStats stats = getNetworkUidBytes(template, start, end);
+        NetworkStats.Entry entry = null;
+        for (int i = 0; i < stats.size(); i++) {
+            entry = stats.getValues(i, entry);
+            final long bytes = entry.rxBytes + entry.txBytes;
+            totalBytes += bytes;
+            if (bytes > maxBytes) {
+                maxBytes = bytes;
+                maxUid = entry.uid;
+            }
+        }
+
+        // Only point blame if the majority of usage was done by a single app.
+        // TODO: support shared UIDs
+        if (maxBytes > 0 && maxBytes > totalBytes / 2) {
+            final String[] packageNames = mContext.getPackageManager().getPackagesForUid(maxUid);
+            if (packageNames.length == 1) {
+                try {
+                    return mContext.getPackageManager().getApplicationInfo(packageNames[0],
+                            MATCH_ANY_USER | MATCH_DISABLED_COMPONENTS | MATCH_DIRECT_BOOT_AWARE
+                                    | MATCH_DIRECT_BOOT_UNAWARE | MATCH_UNINSTALLED_PACKAGES);
+                } catch (NameNotFoundException ignored) {
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
      * Test if given {@link NetworkTemplate} is relevant to user based on
      * current device state, such as when
      * {@link TelephonyManager#getSubscriberId()} matches. This is regardless of
@@ -1157,7 +1201,8 @@
      * Show notification for combined {@link NetworkPolicy} and specific type,
      * like {@link #TYPE_LIMIT}. Okay to call multiple times.
      */
-    private void enqueueNotification(NetworkPolicy policy, int type, long totalBytes) {
+    private void enqueueNotification(NetworkPolicy policy, int type, long totalBytes,
+            ApplicationInfo rapidBlame) {
         final NotificationId notificationId = new NotificationId(policy, type);
         final Notification.Builder builder =
                 new Notification.Builder(mContext, SystemNotificationChannels.NETWORK_ALERTS);
@@ -1167,16 +1212,15 @@
                 com.android.internal.R.color.system_notification_accent_color));
 
         final Resources res = mContext.getResources();
-        CharSequence body = null;
+        final CharSequence title;
+        final CharSequence body;
         switch (type) {
             case TYPE_WARNING: {
-                final CharSequence title = res.getText(R.string.data_usage_warning_title);
-                body = res.getString(R.string.data_usage_warning_body);
+                title = res.getText(R.string.data_usage_warning_title);
+                body = res.getString(R.string.data_usage_warning_body,
+                        Formatter.formatFileSize(mContext, totalBytes));
 
                 builder.setSmallIcon(R.drawable.stat_notify_error);
-                builder.setTicker(title);
-                builder.setContentTitle(title);
-                builder.setContentText(body);
 
                 final Intent snoozeIntent = buildSnoozeWarningIntent(policy.template);
                 builder.setDeleteIntent(PendingIntent.getBroadcast(
@@ -1189,34 +1233,20 @@
                 break;
             }
             case TYPE_LIMIT: {
-                body = res.getText(R.string.data_usage_limit_body);
-
-                final CharSequence title;
-                int icon = R.drawable.stat_notify_disabled_data;
                 switch (policy.template.getMatchRule()) {
-                    case MATCH_MOBILE_3G_LOWER:
-                        title = res.getText(R.string.data_usage_3g_limit_title);
-                        break;
-                    case MATCH_MOBILE_4G:
-                        title = res.getText(R.string.data_usage_4g_limit_title);
-                        break;
-                    case MATCH_MOBILE_ALL:
+                    case MATCH_MOBILE:
                         title = res.getText(R.string.data_usage_mobile_limit_title);
                         break;
                     case MATCH_WIFI:
                         title = res.getText(R.string.data_usage_wifi_limit_title);
-                        icon = R.drawable.stat_notify_error;
                         break;
                     default:
-                        title = null;
-                        break;
+                        return;
                 }
+                body = res.getText(R.string.data_usage_limit_body);
 
                 builder.setOngoing(true);
-                builder.setSmallIcon(icon);
-                builder.setTicker(title);
-                builder.setContentTitle(title);
-                builder.setContentText(body);
+                builder.setSmallIcon(R.drawable.stat_notify_disabled_data);
 
                 final Intent intent = buildNetworkOverLimitIntent(res, policy.template);
                 builder.setContentIntent(PendingIntent.getActivity(
@@ -1224,34 +1254,22 @@
                 break;
             }
             case TYPE_LIMIT_SNOOZED: {
-                final long overBytes = totalBytes - policy.limitBytes;
-                body = res.getString(R.string.data_usage_limit_snoozed_body,
-                        Formatter.formatFileSize(mContext, overBytes));
-
-                final CharSequence title;
                 switch (policy.template.getMatchRule()) {
-                    case MATCH_MOBILE_3G_LOWER:
-                        title = res.getText(R.string.data_usage_3g_limit_snoozed_title);
-                        break;
-                    case MATCH_MOBILE_4G:
-                        title = res.getText(R.string.data_usage_4g_limit_snoozed_title);
-                        break;
-                    case MATCH_MOBILE_ALL:
+                    case MATCH_MOBILE:
                         title = res.getText(R.string.data_usage_mobile_limit_snoozed_title);
                         break;
                     case MATCH_WIFI:
                         title = res.getText(R.string.data_usage_wifi_limit_snoozed_title);
                         break;
                     default:
-                        title = null;
-                        break;
+                        return;
                 }
+                final long overBytes = totalBytes - policy.limitBytes;
+                body = res.getString(R.string.data_usage_limit_snoozed_body,
+                        Formatter.formatFileSize(mContext, overBytes));
 
                 builder.setOngoing(true);
                 builder.setSmallIcon(R.drawable.stat_notify_error);
-                builder.setTicker(title);
-                builder.setContentTitle(title);
-                builder.setContentText(body);
                 builder.setChannelId(SystemNotificationChannels.NETWORK_STATUS);
 
                 final Intent intent = buildViewDataUsageIntent(res, policy.template);
@@ -1260,13 +1278,15 @@
                 break;
             }
             case TYPE_RAPID: {
-                final CharSequence title = res.getText(R.string.data_usage_rapid_title);
-                body = res.getText(R.string.data_usage_rapid_body);
+                title = res.getText(R.string.data_usage_rapid_title);
+                if (rapidBlame != null) {
+                    body = res.getString(R.string.data_usage_rapid_app_body,
+                            rapidBlame.loadLabel(mContext.getPackageManager()));
+                } else {
+                    body = res.getString(R.string.data_usage_rapid_body);
+                }
 
                 builder.setSmallIcon(R.drawable.stat_notify_error);
-                builder.setTicker(title);
-                builder.setContentTitle(title);
-                builder.setContentText(body);
 
                 final Intent snoozeIntent = buildSnoozeRapidIntent(policy.template);
                 builder.setDeleteIntent(PendingIntent.getBroadcast(
@@ -1277,11 +1297,15 @@
                         mContext, 0, viewIntent, PendingIntent.FLAG_UPDATE_CURRENT));
                 break;
             }
+            default: {
+                return;
+            }
         }
 
-        if (!TextUtils.isEmpty(body)) {
-            builder.setStyle(new Notification.BigTextStyle().bigText(body));
-        }
+        builder.setTicker(title);
+        builder.setContentTitle(title);
+        builder.setContentText(body);
+        builder.setStyle(new Notification.BigTextStyle().bigText(body));
 
         mContext.getSystemService(NotificationManager.class).notifyAsUser(notificationId.getTag(),
                 notificationId.getId(), builder.build(), UserHandle.ALL);
@@ -1537,7 +1561,7 @@
         // TODO: reach into ConnectivityManager to proactively disable bringing
         // up this network, since we know that traffic will be blocked.
 
-        if (template.getMatchRule() == MATCH_MOBILE_ALL) {
+        if (template.getMatchRule() == MATCH_MOBILE) {
             // If mobile data usage hits the limit or if the user resumes the data, we need to
             // notify telephony.
             final SubscriptionManager sm = mContext.getSystemService(SubscriptionManager.class);
@@ -1954,9 +1978,7 @@
                             metered = readBooleanAttribute(in, ATTR_METERED);
                         } else {
                             switch (networkTemplate) {
-                                case MATCH_MOBILE_3G_LOWER:
-                                case MATCH_MOBILE_4G:
-                                case MATCH_MOBILE_ALL:
+                                case MATCH_MOBILE:
                                     metered = true;
                                     break;
                                 default:
@@ -3243,8 +3265,6 @@
         }
         try {
             mNetworkStats.setUidForeground(uid, uidForeground);
-        } catch (RemoteException e) {
-            // ignored; service lives in system_server
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
         }
@@ -4028,13 +4048,9 @@
 
                     synchronized (mNetworkPoliciesSecondLock) {
                         if (mMeteredIfaces.contains(iface)) {
-                            try {
-                                // force stats update to make sure we have
-                                // numbers that caused alert to trigger.
-                                mNetworkStats.forceUpdate();
-                            } catch (RemoteException e) {
-                                // ignored; service lives in system_server
-                            }
+                            // force stats update to make sure we have
+                            // numbers that caused alert to trigger.
+                            mNetworkStats.forceUpdate();
 
                             updateNetworkEnabledNL();
                             updateNotificationsNL();
@@ -4075,14 +4091,10 @@
                 }
                 case MSG_ADVISE_PERSIST_THRESHOLD: {
                     final long lowestRule = (Long) msg.obj;
-                    try {
-                        // make sure stats are recorded frequently enough; we aim
-                        // for 2MB threshold for 2GB/month rules.
-                        final long persistThreshold = lowestRule / 1000;
-                        mNetworkStats.advisePersistThreshold(persistThreshold);
-                    } catch (RemoteException e) {
-                        // ignored; service lives in system_server
-                    }
+                    // make sure stats are recorded frequently enough; we aim
+                    // for 2MB threshold for 2GB/month rules.
+                    final long persistThreshold = lowestRule / 1000;
+                    mNetworkStats.advisePersistThreshold(persistThreshold);
                     return true;
                 }
                 case MSG_UPDATE_INTERFACE_QUOTA: {
@@ -4366,15 +4378,26 @@
         }
     }
 
+    @Deprecated
     private long getTotalBytes(NetworkTemplate template, long start, long end) {
+        return getNetworkTotalBytes(template, start, end);
+    }
+
+    private long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
         try {
             return mNetworkStats.getNetworkTotalBytes(template, start, end);
         } catch (RuntimeException e) {
-            Slog.w(TAG, "problem reading network stats: " + e);
+            Slog.w(TAG, "Failed to read network stats: " + e);
             return 0;
-        } catch (RemoteException e) {
-            // ignored; service lives in system_server
-            return 0;
+        }
+    }
+
+    private NetworkStats getNetworkUidBytes(NetworkTemplate template, long start, long end) {
+        try {
+            return mNetworkStats.getNetworkUidBytes(template, start, end);
+        } catch (RuntimeException e) {
+            Slog.w(TAG, "Failed to read network stats: " + e);
+            return new NetworkStats(SystemClock.elapsedRealtime(), 0);
         }
     }
 
diff --git a/services/core/java/com/android/server/net/NetworkStatsCollection.java b/services/core/java/com/android/server/net/NetworkStatsCollection.java
index 3cc4d83..a5f8dc7 100644
--- a/services/core/java/com/android/server/net/NetworkStatsCollection.java
+++ b/services/core/java/com/android/server/net/NetworkStatsCollection.java
@@ -106,6 +106,10 @@
         reset();
     }
 
+    public void clear() {
+        reset();
+    }
+
     public void reset() {
         mStats.clear();
         mStartMillis = Long.MAX_VALUE;
diff --git a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
new file mode 100644
index 0000000..4843ede
--- /dev/null
+++ b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import android.net.NetworkStats;
+import android.net.NetworkTemplate;
+
+public abstract class NetworkStatsManagerInternal {
+    /** Return network layer usage total for traffic that matches template. */
+    public abstract long getNetworkTotalBytes(NetworkTemplate template, long start, long end);
+
+    /** Return network layer usage per-UID for traffic that matches template. */
+    public abstract NetworkStats getNetworkUidBytes(NetworkTemplate template, long start, long end);
+
+    /** Mark given UID as being in foreground for stats purposes. */
+    public abstract void setUidForeground(int uid, boolean uidForeground);
+
+    /** Advise persistance threshold; may be overridden internally. */
+    public abstract void advisePersistThreshold(long thresholdBytes);
+
+    /** Force update of statistics. */
+    public abstract void forceUpdate();
+}
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 32e15c9..93c04fe 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -49,7 +49,6 @@
 import static android.provider.Settings.Global.NETSTATS_GLOBAL_ALERT_BYTES;
 import static android.provider.Settings.Global.NETSTATS_POLL_INTERVAL;
 import static android.provider.Settings.Global.NETSTATS_SAMPLE_ENABLED;
-import static android.provider.Settings.Global.NETSTATS_TIME_CACHE_MAX_AGE;
 import static android.provider.Settings.Global.NETSTATS_UID_BUCKET_DURATION;
 import static android.provider.Settings.Global.NETSTATS_UID_DELETE_AGE;
 import static android.provider.Settings.Global.NETSTATS_UID_PERSIST_BYTES;
@@ -95,10 +94,10 @@
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
 import android.net.TrafficStats;
+import android.os.BestClock;
 import android.os.Binder;
 import android.os.DropBoxManager;
 import android.os.Environment;
-import android.os.BestClock;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -134,6 +133,7 @@
 import com.android.internal.util.FileRotator;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.EventLogTags;
+import com.android.server.LocalServices;
 import com.android.server.connectivity.Tethering;
 
 import java.io.File;
@@ -333,6 +333,9 @@
         mStatsObservers = checkNotNull(statsObservers, "missing NetworkStatsObservers");
         mSystemDir = checkNotNull(systemDir, "missing systemDir");
         mBaseDir = checkNotNull(baseDir, "missing baseDir");
+
+        LocalServices.addService(NetworkStatsManagerInternal.class,
+                new NetworkStatsManagerInternalImpl());
     }
 
     @VisibleForTesting
@@ -640,7 +643,7 @@
     private SubscriptionPlan resolveSubscriptionPlan(NetworkTemplate template, int flags) {
         SubscriptionPlan plan = null;
         if ((flags & NetworkStatsManager.FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN) != 0
-                && (template.getMatchRule() == NetworkTemplate.MATCH_MOBILE_ALL)
+                && (template.getMatchRule() == NetworkTemplate.MATCH_MOBILE)
                 && mSettings.getAugmentEnabled()) {
             if (LOGD) Slog.d(TAG, "Resolving plan for " + template);
             final long token = Binder.clearCallingIdentity();
@@ -701,12 +704,8 @@
         }
     }
 
-    @Override
-    public long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
-        // Special case - since this is for internal use only, don't worry about
-        // a full access level check and just require the signature/privileged
-        // permission.
-        mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
+    private long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
+        assertSystemReady();
         assertBandwidthControlEnabled();
 
         // NOTE: if callers want to get non-augmented data, they should go
@@ -716,6 +715,18 @@
                 NetworkStatsAccess.Level.DEVICE, Binder.getCallingUid()).getTotalBytes();
     }
 
+    private NetworkStats getNetworkUidBytes(NetworkTemplate template, long start, long end) {
+        assertSystemReady();
+        assertBandwidthControlEnabled();
+
+        final NetworkStatsCollection uidComplete;
+        synchronized (mStatsLock) {
+            uidComplete = mUidRecorder.getOrLoadCompleteLocked();
+        }
+        return uidComplete.getSummary(template, start, end, NetworkStatsAccess.Level.DEVICE,
+                android.os.Process.SYSTEM_UID);
+    }
+
     @Override
     public NetworkStats getDataLayerSnapshotForUid(int uid) throws RemoteException {
         if (Binder.getCallingUid() != uid) {
@@ -777,10 +788,8 @@
         }
     }
 
-    @Override
-    public void setUidForeground(int uid, boolean uidForeground) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-
+    @VisibleForTesting
+    void setUidForeground(int uid, boolean uidForeground) {
         synchronized (mStatsLock) {
             final int set = uidForeground ? SET_FOREGROUND : SET_DEFAULT;
             final int oldSet = mActiveUidCounterSet.get(uid, SET_DEFAULT);
@@ -817,9 +826,7 @@
         }
     }
 
-    @Override
-    public void advisePersistThreshold(long thresholdBytes) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+    private void advisePersistThreshold(long thresholdBytes) {
         assertBandwidthControlEnabled();
 
         // clamp threshold into safe range
@@ -1330,6 +1337,33 @@
         removeUidsLocked(uids);
     }
 
+    private class NetworkStatsManagerInternalImpl extends NetworkStatsManagerInternal {
+        @Override
+        public long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
+            return NetworkStatsService.this.getNetworkTotalBytes(template, start, end);
+        }
+
+        @Override
+        public NetworkStats getNetworkUidBytes(NetworkTemplate template, long start, long end) {
+            return NetworkStatsService.this.getNetworkUidBytes(template, start, end);
+        }
+
+        @Override
+        public void setUidForeground(int uid, boolean uidForeground) {
+            NetworkStatsService.this.setUidForeground(uid, uidForeground);
+        }
+
+        @Override
+        public void advisePersistThreshold(long thresholdBytes) {
+            NetworkStatsService.this.advisePersistThreshold(thresholdBytes);
+        }
+
+        @Override
+        public void forceUpdate() {
+            NetworkStatsService.this.forceUpdate();
+        }
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter rawWriter, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, rawWriter)) return;
@@ -1550,6 +1584,12 @@
         }
     }
 
+    private void assertSystemReady() {
+        if (!mSystemReady) {
+            throw new IllegalStateException("System not ready");
+        }
+    }
+
     private void assertBandwidthControlEnabled() {
         if (!isBandwidthControlEnabled()) {
             throw new IllegalStateException("Bandwidth module disabled");
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 61c8b79..b53b10f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1088,8 +1088,8 @@
 
             traceBeginAndSlog("StartNetworkPolicyManagerService");
             try {
-                networkPolicy = new NetworkPolicyManagerService(context,
-                    mActivityManagerService, networkStats, networkManagement);
+                networkPolicy = new NetworkPolicyManagerService(context, mActivityManagerService,
+                        networkManagement);
                 ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, networkPolicy);
             } catch (Throwable e) {
                 reportWtf("starting NetworkPolicy Service", e);
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index e1b4422..d31d550 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -26,6 +26,9 @@
 import static android.net.NetworkPolicyManager.POLICY_NONE;
 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
 import static android.net.NetworkPolicyManager.uidPoliciesToString;
+import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.SET_ALL;
+import static android.net.NetworkStats.TAG_ALL;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
 import static android.net.TrafficStats.MB_IN_BYTES;
 import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
@@ -42,8 +45,6 @@
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_RAPID;
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_WARNING;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -86,7 +87,6 @@
 import android.net.IConnectivityManager;
 import android.net.INetworkManagementEventObserver;
 import android.net.INetworkPolicyListener;
-import android.net.INetworkStatsService;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -105,6 +105,7 @@
 import android.os.PowerSaveState;
 import android.os.RemoteException;
 import android.os.SimpleClock;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
@@ -125,6 +126,7 @@
 import com.android.internal.util.test.BroadcastInterceptingContext.FutureIntent;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.net.NetworkPolicyManagerService;
+import com.android.server.net.NetworkStatsManagerInternal;
 
 import libcore.io.IoUtils;
 import libcore.io.Streams;
@@ -214,7 +216,6 @@
     private String mNetpolicyXml;
 
     private @Mock IActivityManager mActivityManager;
-    private @Mock INetworkStatsService mStatsService;
     private @Mock INetworkManagementService mNetworkManager;
     private @Mock IConnectivityManager mConnManager;
     private @Mock NotificationManager mNotifManager;
@@ -224,7 +225,8 @@
     private @Mock CarrierConfigManager mCarrierConfigManager;
     private @Mock TelephonyManager mTelephonyManager;
 
-    private static ActivityManagerInternal mActivityManagerInternal;
+    private ActivityManagerInternal mActivityManagerInternal;
+    private NetworkStatsManagerInternal mStatsService;
 
     private IUidObserver mUidObserver;
     private INetworkManagementEventObserver mNetworkObserver;
@@ -264,6 +266,8 @@
     private static final int UID_F = UserHandle.getUid(USER_ID, APP_ID_F);
 
     private static final String PKG_NAME_A = "name.is.A,pkg.A";
+    private static final String PKG_NAME_B = "name.is.B,pkg.B";
+    private static final String PKG_NAME_C = "name.is.C,pkg.C";
 
     public final @Rule NetPolicyMethodRule mNetPolicyXmlRule = new NetPolicyMethodRule();
 
@@ -287,6 +291,8 @@
                 .setBatterySaverEnabled(false).build();
         final PowerManagerInternal pmInternal = addLocalServiceMock(PowerManagerInternal.class);
         when(pmInternal.getLowPowerState(anyInt())).thenReturn(state);
+
+        mStatsService = addLocalServiceMock(NetworkStatsManagerInternal.class);
     }
 
     @Before
@@ -347,7 +353,7 @@
                 eq(ActivityManager.PROCESS_STATE_UNKNOWN), isNull(String.class));
 
         mFutureIntent = newRestrictBackgroundChangedFuture();
-        mService = new NetworkPolicyManagerService(mServiceContext, mActivityManager, mStatsService,
+        mService = new NetworkPolicyManagerService(mServiceContext, mActivityManager,
                 mNetworkManager, mIpm, mClock, mPolicyDir, true);
         mService.bindConnectivityManager(mConnManager);
         mPolicyListener = new NetworkPolicyListenerAnswer(mService);
@@ -375,6 +381,14 @@
         when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
                 .thenReturn(new ApplicationInfo());
         when(mPackageManager.getPackagesForUid(UID_A)).thenReturn(new String[] {PKG_NAME_A});
+        when(mPackageManager.getPackagesForUid(UID_B)).thenReturn(new String[] {PKG_NAME_B});
+        when(mPackageManager.getPackagesForUid(UID_C)).thenReturn(new String[] {PKG_NAME_C});
+        when(mPackageManager.getApplicationInfo(eq(PKG_NAME_A), anyInt()))
+                .thenReturn(buildApplicationInfo(PKG_NAME_A));
+        when(mPackageManager.getApplicationInfo(eq(PKG_NAME_B), anyInt()))
+                .thenReturn(buildApplicationInfo(PKG_NAME_B));
+        when(mPackageManager.getApplicationInfo(eq(PKG_NAME_C), anyInt()))
+                .thenReturn(buildApplicationInfo(PKG_NAME_C));
         when(mNetworkManager.isBandwidthControlEnabled()).thenReturn(true);
         when(mNetworkManager.setDataSaverModeEnabled(anyBoolean())).thenReturn(true);
 
@@ -409,6 +423,7 @@
         LocalServices.removeServiceForTest(PowerManagerInternal.class);
         LocalServices.removeServiceForTest(DeviceIdleController.LocalService.class);
         LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
+        LocalServices.removeServiceForTest(NetworkStatsManagerInternal.class);
     }
 
     @After
@@ -515,7 +530,7 @@
         mService.updateRestrictBackgroundByLowPowerModeUL(stateOn);
 
         // RestrictBackground should be on even though battery saver want to turn it off
-        assertThat(mService.getRestrictBackground()).isTrue();
+        assertTrue(mService.getRestrictBackground());
 
         PowerSaveState stateOff = new PowerSaveState.Builder()
                 .setGlobalBatterySaverEnabled(false)
@@ -524,7 +539,7 @@
         mService.updateRestrictBackgroundByLowPowerModeUL(stateOff);
 
         // RestrictBackground should be on, following its previous state
-        assertThat(mService.getRestrictBackground()).isTrue();
+        assertTrue(mService.getRestrictBackground());
     }
 
     @Test
@@ -539,7 +554,7 @@
         mService.updateRestrictBackgroundByLowPowerModeUL(stateOn);
 
         // RestrictBackground should be turned on because of battery saver
-        assertThat(mService.getRestrictBackground()).isTrue();
+        assertTrue(mService.getRestrictBackground());
 
         PowerSaveState stateOff = new PowerSaveState.Builder()
                 .setGlobalBatterySaverEnabled(false)
@@ -548,7 +563,7 @@
         mService.updateRestrictBackgroundByLowPowerModeUL(stateOff);
 
         // RestrictBackground should be off, following its previous state
-        assertThat(mService.getRestrictBackground()).isFalse();
+        assertFalse(mService.getRestrictBackground());
     }
 
     @Test
@@ -562,7 +577,7 @@
         mService.updateRestrictBackgroundByLowPowerModeUL(stateOn);
 
         // RestrictBackground should still be on
-        assertThat(mService.getRestrictBackground()).isTrue();
+        assertTrue(mService.getRestrictBackground());
 
         // User turns off RestrictBackground manually
         setRestrictBackground(false);
@@ -571,7 +586,7 @@
         mService.updateRestrictBackgroundByLowPowerModeUL(stateOff);
 
         // RestrictBackground should be off because user changes it manually
-        assertThat(mService.getRestrictBackground()).isFalse();
+        assertFalse(mService.getRestrictBackground());
     }
 
     private void removeRestrictBackgroundWhitelist(boolean expectIntent) throws Exception {
@@ -974,6 +989,7 @@
     public void testNotificationWarningLimitSnooze() throws Exception {
         // Create a place to store fake usage
         final NetworkStatsHistory history = new NetworkStatsHistory(TimeUnit.HOURS.toMillis(1));
+        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
         when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
                 .thenAnswer(new Answer<Long>() {
                     @Override
@@ -983,6 +999,13 @@
                         return entry.rxBytes + entry.txBytes;
                     }
                 });
+        when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
+                .thenAnswer(new Answer<NetworkStats>() {
+                    @Override
+                    public NetworkStats answer(InvocationOnMock invocation) throws Throwable {
+                        return stats;
+                    }
+                });
 
         // Get active mobile network in place
         expectMobileDefaults();
@@ -1003,7 +1026,7 @@
 
         // Normal usage means no notification
         {
-            history.removeBucketsBefore(Long.MAX_VALUE);
+            history.clear();
             history.recordData(start, end,
                     new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(360), 0L, 0L, 0L, 0));
 
@@ -1020,7 +1043,7 @@
 
         // Push over warning
         {
-            history.removeBucketsBefore(Long.MAX_VALUE);
+            history.clear();
             history.recordData(start, end,
                     new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1799), 0L, 0L, 0L, 0));
 
@@ -1038,7 +1061,7 @@
 
         // Push over limit
         {
-            history.removeBucketsBefore(Long.MAX_VALUE);
+            history.clear();
             history.recordData(start, end,
                     new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1810), 0L, 0L, 0L, 0));
 
@@ -1073,6 +1096,7 @@
     public void testNotificationRapid() throws Exception {
         // Create a place to store fake usage
         final NetworkStatsHistory history = new NetworkStatsHistory(TimeUnit.HOURS.toMillis(1));
+        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
         when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
                 .thenAnswer(new Answer<Long>() {
                     @Override
@@ -1082,6 +1106,13 @@
                         return entry.rxBytes + entry.txBytes;
                     }
                 });
+        when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
+                .thenAnswer(new Answer<NetworkStats>() {
+                    @Override
+                    public NetworkStats answer(InvocationOnMock invocation) throws Throwable {
+                        return stats;
+                    }
+                });
 
         // Get active mobile network in place
         expectMobileDefaults();
@@ -1102,7 +1133,7 @@
 
         // Using 20% data in 20% time is normal
         {
-            history.removeBucketsBefore(Long.MAX_VALUE);
+            history.clear();
             history.recordData(start, end,
                     new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(360), 0L, 0L, 0L, 0));
 
@@ -1111,16 +1142,58 @@
             verify(mNotifManager, never()).notifyAsUser(any(), anyInt(), any(), any());
         }
 
-        // Using 80% data in 20% time is alarming
+        // Using 80% data in 20% time is alarming; but spread equally among
+        // three UIDs means we get generic alert
         {
-            history.removeBucketsBefore(Long.MAX_VALUE);
+            history.clear();
             history.recordData(start, end,
                     new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1440), 0L, 0L, 0L, 0));
+            stats.clear();
+            stats.addValues(IFACE_ALL, UID_A, SET_ALL, TAG_ALL,
+                    DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
+            stats.addValues(IFACE_ALL, UID_B, SET_ALL, TAG_ALL,
+                    DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
+            stats.addValues(IFACE_ALL, UID_C, SET_ALL, TAG_ALL,
+                    DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
 
             reset(mNotifManager);
             mService.updateNetworks();
+
+            final ArgumentCaptor<Notification> notif = ArgumentCaptor.forClass(Notification.class);
             verify(mNotifManager, atLeastOnce()).notifyAsUser(any(), eq(TYPE_RAPID),
-                    isA(Notification.class), eq(UserHandle.ALL));
+                    notif.capture(), eq(UserHandle.ALL));
+
+            final String text = notif.getValue().extras.getCharSequence(Notification.EXTRA_TEXT)
+                    .toString();
+            assertFalse(text.contains(PKG_NAME_A));
+            assertFalse(text.contains(PKG_NAME_B));
+            assertFalse(text.contains(PKG_NAME_C));
+        }
+
+        // Using 80% data in 20% time is alarming; but mostly done by one UID
+        // means we get specific alert
+        {
+            history.clear();
+            history.recordData(start, end,
+                    new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1440), 0L, 0L, 0L, 0));
+            stats.clear();
+            stats.addValues(IFACE_ALL, UID_A, SET_ALL, TAG_ALL,
+                    DataUnit.MEGABYTES.toBytes(960), 0, 0, 0, 0);
+            stats.addValues(IFACE_ALL, UID_B, SET_ALL, TAG_ALL,
+                    DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
+
+            reset(mNotifManager);
+            mService.updateNetworks();
+
+            final ArgumentCaptor<Notification> notif = ArgumentCaptor.forClass(Notification.class);
+            verify(mNotifManager, atLeastOnce()).notifyAsUser(any(), eq(TYPE_RAPID),
+                    notif.capture(), eq(UserHandle.ALL));
+
+            final String text = notif.getValue().extras.getCharSequence(Notification.EXTRA_TEXT)
+                    .toString();
+            assertTrue(text.contains(PKG_NAME_A));
+            assertFalse(text.contains(PKG_NAME_B));
+            assertFalse(text.contains(PKG_NAME_C));
         }
     }
 
@@ -1411,6 +1484,12 @@
                 true);
     }
 
+    private ApplicationInfo buildApplicationInfo(String label) {
+        final ApplicationInfo ai = new ApplicationInfo();
+        ai.nonLocalizedLabel = label;
+        return ai;
+    }
+
     private NetworkInfo buildNetworkInfo() {
         final NetworkInfo ni = new NetworkInfo(ConnectivityManager.TYPE_MOBILE,
                 TelephonyManager.NETWORK_TYPE_LTE, null, null);
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 28f8122..2aea1d7 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -914,8 +914,10 @@
                 mock(INetworkPolicyManager.class),
                 mock(IpConnectivityLog.class));
 
-        mService.systemReady();
+        // Create local CM before sending system ready so that we can answer
+        // getSystemService() correctly.
         mCm = new WrappedConnectivityManager(InstrumentationRegistry.getContext(), mService);
+        mService.systemReady();
         mCm.bindProcessToNetwork(null);
 
         // Ensure that the default setting for Captive Portals is used for most tests
@@ -3412,8 +3414,10 @@
 
     @Test
     public void testNetworkCallbackMaximum() {
-        final int MAX_REQUESTS = 100;
-        final int CALLBACKS = 90;
+        // We can only have 99 callbacks, because MultipathPolicyTracker is
+        // already one of them.
+        final int MAX_REQUESTS = 99;
+        final int CALLBACKS = 89;
         final int INTENTS = 10;
         assertEquals(MAX_REQUESTS, CALLBACKS + INTENTS);
 
diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index b1b05e8..49b2643 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -95,6 +95,7 @@
 
 import com.android.internal.net.VpnInfo;
 import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.server.LocalServices;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
 
@@ -221,6 +222,9 @@
 
     @After
     public void tearDown() throws Exception {
+        // Registered by NetworkStatsService's constructor.
+        LocalServices.removeServiceForTest(NetworkStatsManagerInternal.class);
+
         IoUtils.deleteContents(mStatsDir);
 
         mServiceContext = null;