Merge changes I325b13d5,I89719fe7 am: 23868e9c09 am: 9335d2d0b1
am: 4fa0187015

Change-Id: Ib980879b0e50ffe1c8209339df22da4a50d5b74e
diff --git a/core/java/android/net/ITetheringStatsProvider.aidl b/core/java/android/net/ITetheringStatsProvider.aidl
index 1aeabc1..da0bf4c 100644
--- a/core/java/android/net/ITetheringStatsProvider.aidl
+++ b/core/java/android/net/ITetheringStatsProvider.aidl
@@ -30,7 +30,10 @@
  */
 interface ITetheringStatsProvider {
     // Returns cumulative statistics for all tethering sessions since boot, on all upstreams.
-    NetworkStats getTetherStats();
+    // @code {how} is one of the NetworkStats.STATS_PER_* constants. If {@code how} is
+    // {@code STATS_PER_IFACE}, the provider should not include any traffic that is already
+    // counted by kernel interface counters.
+    NetworkStats getTetherStats(int how);
 
     // Sets the interface quota for the specified upstream interface. This is defined as the number
     // of bytes, starting from zero and counting from now, after which data should stop being
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index be9e809..171adc0 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -82,6 +82,11 @@
     /** {@link #roaming} value where roaming data is accounted. */
     public static final int ROAMING_YES = 1;
 
+    /** Denotes a request for stats at the interface level. */
+    public static final int STATS_PER_IFACE = 0;
+    /** Denotes a request for stats at the interface and UID level. */
+    public static final int STATS_PER_UID = 1;
+
     // TODO: move fields to "mVariable" notation
 
     /**
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 3de2174..b63e302 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -220,6 +220,21 @@
     void unregisterTetheringStatsProvider(ITetheringStatsProvider provider);
 
     /**
+     * Reports that a tethering provider has reached a data limit.
+     *
+     * Currently triggers a global alert, which causes NetworkStatsService to poll counters and
+     * re-evaluate data usage.
+     *
+     * This does not take an interface name because:
+     * 1. The tethering offload stats provider cannot reliably determine the interface on which the
+     *    limit was reached, because the HAL does not provide it.
+     * 2. Firing an interface-specific alert instead of a global alert isn't really useful since in
+     *    all cases of interest, the system responds to both in the same way - it polls stats, and
+     *    then notifies NetworkPolicyManagerService of the fact.
+     */
+    void tetherLimitReached(ITetheringStatsProvider provider);
+
+    /**
      ** PPPD
      **/
 
@@ -266,7 +281,7 @@
     /**
      * Return summary of network statistics all tethering interfaces.
      */
-    NetworkStats getNetworkStatsTethering();
+    NetworkStats getNetworkStatsTethering(int how);
 
     /**
      * Set quota for an interface.
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index e536e8d..f3e8b82 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -34,6 +34,7 @@
 import static android.net.NetworkPolicyManager.FIREWALL_TYPE_BLACKLIST;
 import static android.net.NetworkPolicyManager.FIREWALL_TYPE_WHITELIST;
 import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.STATS_PER_UID;
 import static android.net.NetworkStats.TAG_ALL;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
@@ -550,6 +551,18 @@
         }
     }
 
+    @Override
+    public void tetherLimitReached(ITetheringStatsProvider provider) {
+        mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG);
+        synchronized(mTetheringStatsProviders) {
+            if (!mTetheringStatsProviders.containsKey(provider)) {
+                return;
+            }
+            // No current code examines the interface parameter in a global alert. Just pass null.
+            notifyLimitReached(LIMIT_GLOBAL_ALERT, null);
+        }
+    }
+
     // Sync the state of the given chain with the native daemon.
     private void syncFirewallChainLocked(int chain, String name) {
         SparseIntArray rules;
@@ -1851,7 +1864,13 @@
 
     private class NetdTetheringStatsProvider extends ITetheringStatsProvider.Stub {
         @Override
-        public NetworkStats getTetherStats() {
+        public NetworkStats getTetherStats(int how) {
+            // We only need to return per-UID stats. Per-device stats are already counted by
+            // interface counters.
+            if (how != STATS_PER_UID) {
+                return new NetworkStats(SystemClock.elapsedRealtime(), 0);
+            }
+
             final NativeDaemonEvent[] events;
             try {
                 events = mConnector.executeForList("bandwidth", "gettetherstats");
@@ -1894,14 +1913,14 @@
     }
 
     @Override
-    public NetworkStats getNetworkStatsTethering() {
+    public NetworkStats getNetworkStatsTethering(int how) {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
 
         final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
         synchronized (mTetheringStatsProviders) {
             for (ITetheringStatsProvider provider: mTetheringStatsProviders.keySet()) {
                 try {
-                    stats.combineAllValues(provider.getTetherStats());
+                    stats.combineAllValues(provider.getTetherStats(how));
                 } catch (RemoteException e) {
                     Log.e(TAG, "Problem reading tethering stats from " +
                             mTetheringStatsProviders.get(provider) + ": " + e);
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
index 55e290a..4393e35 100644
--- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
@@ -17,7 +17,9 @@
 package com.android.server.connectivity.tethering;
 
 import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.STATS_PER_UID;
 import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
 import static android.net.TrafficStats.UID_TETHERING;
 import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
 
@@ -60,6 +62,8 @@
     private final Handler mHandler;
     private final OffloadHardwareInterface mHwInterface;
     private final ContentResolver mContentResolver;
+    private final INetworkManagementService mNms;
+    private final ITetheringStatsProvider mStatsProvider;
     private final SharedLog mLog;
     private boolean mConfigInitialized;
     private boolean mControlInitialized;
@@ -89,13 +93,14 @@
         mHandler = h;
         mHwInterface = hwi;
         mContentResolver = contentResolver;
+        mNms = nms;
+        mStatsProvider = new OffloadTetheringStatsProvider();
         mLog = log.forSubComponent(TAG);
         mExemptPrefixes = new HashSet<>();
         mLastLocalPrefixStrs = new HashSet<>();
 
         try {
-            nms.registerTetheringStatsProvider(
-                    new OffloadTetheringStatsProvider(), getClass().getSimpleName());
+            mNms.registerTetheringStatsProvider(mStatsProvider, getClass().getSimpleName());
         } catch (RemoteException e) {
             mLog.e("Cannot register offload stats provider: " + e);
         }
@@ -150,7 +155,26 @@
                     @Override
                     public void onStoppedLimitReached() {
                         mLog.log("onStoppedLimitReached");
-                        // Poll for statistics and notify NetworkStats
+
+                        // We cannot reliably determine on which interface the limit was reached,
+                        // because the HAL interface does not specify it. We cannot just use the
+                        // current upstream, because that might have changed since the time that
+                        // the HAL queued the callback.
+                        // TODO: rev the HAL so that it provides an interface name.
+
+                        // Fetch current stats, so that when our notification reaches
+                        // NetworkStatsService and triggers a poll, we will respond with
+                        // current data (which will be above the limit that was reached).
+                        // Note that if we just changed upstream, this is unnecessary but harmless.
+                        // The stats for the previous upstream were already updated on this thread
+                        // just after the upstream was changed, so they are also up-to-date.
+                        updateStatsForCurrentUpstream();
+
+                        try {
+                            mNms.tetherLimitReached(mStatsProvider);
+                        } catch (RemoteException e) {
+                            mLog.e("Cannot report data limit reached: " + e);
+                        }
                     }
 
                     @Override
@@ -180,16 +204,18 @@
 
     private class OffloadTetheringStatsProvider extends ITetheringStatsProvider.Stub {
         @Override
-        public NetworkStats getTetherStats() {
+        public NetworkStats getTetherStats(int how) {
             NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
 
             // We can't just post to mHandler because we are mostly (but not always) called by
             // NetworkStatsService#performPollLocked, which is (currently) on the same thread as us.
             mHandler.runWithScissors(() -> {
+                // We have to report both per-interface and per-UID stats, because offloaded traffic
+                // is not seen by kernel interface counters.
                 NetworkStats.Entry entry = new NetworkStats.Entry();
                 entry.set = SET_DEFAULT;
                 entry.tag = TAG_NONE;
-                entry.uid = UID_TETHERING;
+                entry.uid = (how == STATS_PER_UID) ? UID_TETHERING : UID_ALL;
 
                 updateStatsForCurrentUpstream();
 
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index ab685ca..421db40 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -30,6 +30,8 @@
 import static android.net.NetworkStats.SET_ALL;
 import static android.net.NetworkStats.SET_DEFAULT;
 import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.STATS_PER_IFACE;
+import static android.net.NetworkStats.STATS_PER_UID;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
 import static android.net.NetworkTemplate.buildTemplateMobileWildcard;
@@ -1041,6 +1043,11 @@
         final NetworkStats xtSnapshot = getNetworkStatsXt();
         final NetworkStats devSnapshot = mNetworkManager.getNetworkStatsSummaryDev();
 
+        // Tethering snapshot for dev and xt stats. Counts per-interface data from tethering stats
+        // providers that isn't already counted by dev and XT stats.
+        final NetworkStats tetherSnapshot = getNetworkStatsTethering(STATS_PER_IFACE);
+        xtSnapshot.combineAllValues(tetherSnapshot);
+        devSnapshot.combineAllValues(tetherSnapshot);
 
         // For xt/dev, we pass a null VPN array because usage is aggregated by UID, so VPN traffic
         // can't be reattributed to responsible apps.
@@ -1371,14 +1378,14 @@
         final NetworkStats uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL);
 
         // fold tethering stats and operations into uid snapshot
-        final NetworkStats tetherSnapshot = getNetworkStatsTethering();
+        final NetworkStats tetherSnapshot = getNetworkStatsTethering(STATS_PER_UID);
         uidSnapshot.combineAllValues(tetherSnapshot);
 
         final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
                 Context.TELEPHONY_SERVICE);
 
         // fold video calling data usage stats into uid snapshot
-        final NetworkStats vtStats = telephonyManager.getVtDataUsage(true);
+        final NetworkStats vtStats = telephonyManager.getVtDataUsage(STATS_PER_UID);
         if (vtStats != null) {
             uidSnapshot.combineAllValues(vtStats);
         }
@@ -1397,7 +1404,7 @@
                 Context.TELEPHONY_SERVICE);
 
         // Merge video calling data usage into XT
-        final NetworkStats vtSnapshot = telephonyManager.getVtDataUsage(false);
+        final NetworkStats vtSnapshot = telephonyManager.getVtDataUsage(STATS_PER_IFACE);
         if (vtSnapshot != null) {
             xtSnapshot.combineAllValues(vtSnapshot);
         }
@@ -1409,9 +1416,9 @@
      * Return snapshot of current tethering statistics. Will return empty
      * {@link NetworkStats} if any problems are encountered.
      */
-    private NetworkStats getNetworkStatsTethering() throws RemoteException {
+    private NetworkStats getNetworkStatsTethering(int how) throws RemoteException {
         try {
-            return mNetworkManager.getNetworkStatsTethering();
+            return mNetworkManager.getNetworkStatsTethering(how);
         } catch (IllegalStateException e) {
             Log.wtf(TAG, "problem reading network stats", e);
             return new NetworkStats(0L, 10);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 06575bf..aa3b612 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6688,11 +6688,13 @@
      * Get aggregated video call data usage since boot.
      * Permissions android.Manifest.permission.READ_NETWORK_USAGE_HISTORY is required.
      *
-     * @param perUidStats True if requesting data usage per uid, otherwise overall usage.
+     * @param how one of the NetworkStats.STATS_PER_* constants depending on whether the request is
+     * for data usage per uid or overall usage.
      * @return Snapshot of video call data usage
      * @hide
      */
-    public NetworkStats getVtDataUsage(boolean perUidStats) {
+    public NetworkStats getVtDataUsage(int how) {
+        boolean perUidStats = (how == NetworkStats.STATS_PER_UID);
         try {
             ITelephony service = getITelephony();
             if (service != null) {
diff --git a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
index c4965a0..53a9b5a 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
@@ -17,7 +17,10 @@
 package com.android.server.connectivity.tethering;
 
 import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.STATS_PER_IFACE;
+import static android.net.NetworkStats.STATS_PER_UID;
 import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
 import static android.net.TrafficStats.UID_TETHERING;
 import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
 import static com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;
@@ -85,6 +88,8 @@
             ArgumentCaptor.forClass(ArrayList.class);
     private final ArgumentCaptor<ITetheringStatsProvider.Stub> mTetherStatsProviderCaptor =
             ArgumentCaptor.forClass(ITetheringStatsProvider.Stub.class);
+    private final ArgumentCaptor<OffloadHardwareInterface.ControlCallback> mControlCallbackCaptor =
+            ArgumentCaptor.forClass(OffloadHardwareInterface.ControlCallback.class);
     private MockContentResolver mContentResolver;
 
     @Before public void setUp() {
@@ -103,7 +108,7 @@
 
     private void setupFunctioningHardwareInterface() {
         when(mHardware.initOffloadConfig()).thenReturn(true);
-        when(mHardware.initOffloadControl(any(OffloadHardwareInterface.ControlCallback.class)))
+        when(mHardware.initOffloadControl(mControlCallbackCaptor.capture()))
                 .thenReturn(true);
         when(mHardware.getForwardedStats(any())).thenReturn(new ForwardedStats());
     }
@@ -371,7 +376,6 @@
         assertEquals(stats.txBytes, entry.txBytes);
         assertEquals(SET_DEFAULT, entry.set);
         assertEquals(TAG_NONE, entry.tag);
-        assertEquals(UID_TETHERING, entry.uid);
     }
 
     @Test
@@ -410,20 +414,33 @@
         ethernetStats.txBytes = 100000;
         offload.setUpstreamLinkProperties(null);
 
-        NetworkStats stats = mTetherStatsProviderCaptor.getValue().getTetherStats();
+        ITetheringStatsProvider provider = mTetherStatsProviderCaptor.getValue();
+        NetworkStats stats = provider.getTetherStats(STATS_PER_IFACE);
+        NetworkStats perUidStats = provider.getTetherStats(STATS_PER_UID);
+
         assertEquals(2, stats.size());
+        assertEquals(2, perUidStats.size());
 
         NetworkStats.Entry entry = null;
+        for (int i = 0; i < stats.size(); i++) {
+            assertEquals(UID_ALL, stats.getValues(i, entry).uid);
+            assertEquals(UID_TETHERING, perUidStats.getValues(i, entry).uid);
+        }
+
         int ethernetPosition = ethernetIface.equals(stats.getValues(0, entry).iface) ? 0 : 1;
         int mobilePosition = 1 - ethernetPosition;
 
         entry = stats.getValues(mobilePosition, entry);
         assertNetworkStats(mobileIface, mobileStats, entry);
+        entry = perUidStats.getValues(mobilePosition, entry);
+        assertNetworkStats(mobileIface, mobileStats, entry);
 
         ethernetStats.rxBytes = 12345 + 100000;
         ethernetStats.txBytes = 54321 + 100000;
         entry = stats.getValues(ethernetPosition, entry);
         assertNetworkStats(ethernetIface, ethernetStats, entry);
+        entry = perUidStats.getValues(ethernetPosition, entry);
+        assertNetworkStats(ethernetIface, ethernetStats, entry);
     }
 
     @Test
@@ -489,4 +506,17 @@
         waitForIdle();
         inOrder.verify(mHardware).stopOffloadControl();
     }
+
+    @Test
+    public void testDataLimitCallback() throws Exception {
+        setupFunctioningHardwareInterface();
+        enableOffload();
+
+        final OffloadController offload = makeOffloadController();
+        offload.start();
+
+        OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue();
+        callback.onStoppedLimitReached();
+        verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue());
+    }
 }
diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index feb46d3..fa99795 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -31,6 +31,8 @@
 import static android.net.NetworkStats.SET_ALL;
 import static android.net.NetworkStats.SET_DEFAULT;
 import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.STATS_PER_IFACE;
+import static android.net.NetworkStats.STATS_PER_UID;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
 import static android.net.NetworkStatsHistory.FIELD_ALL;
@@ -823,17 +825,24 @@
         incrementCurrentTime(HOUR_IN_MILLIS);
         expectCurrentTime();
         expectDefaultSettings();
-        expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
-                .addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L));
 
+        // Traffic seen by kernel counters (includes software tethering).
+        final NetworkStats ifaceStats = new NetworkStats(getElapsedRealtime(), 1)
+                .addIfaceValues(TEST_IFACE, 1536L, 12L, 384L, 3L);
+        // Hardware tethering traffic, not seen by kernel counters.
+        final NetworkStats tetherStatsHardware = new NetworkStats(getElapsedRealtime(), 1)
+                .addIfaceValues(TEST_IFACE, 512L, 4L, 128L, 1L);
+
+        // Traffic for UID_RED.
         final NetworkStats uidStats = new NetworkStats(getElapsedRealtime(), 1)
                 .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L);
-        final String[] tetherIfacePairs = new String[] { TEST_IFACE, "wlan0" };
+        // All tethering traffic, both hardware and software.
         final NetworkStats tetherStats = new NetworkStats(getElapsedRealtime(), 1)
                 .addValues(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L,
                         0L);
 
-        expectNetworkStatsUidDetail(uidStats, tetherIfacePairs, tetherStats);
+        expectNetworkStatsSummary(ifaceStats, tetherStatsHardware);
+        expectNetworkStatsUidDetail(uidStats, tetherStats);
         forcePollAndWaitForIdle();
 
         // verify service recorded history
@@ -1013,10 +1022,16 @@
     }
 
     private void expectNetworkStatsSummary(NetworkStats summary) throws Exception {
+        expectNetworkStatsSummary(summary, new NetworkStats(0L, 0));
+    }
+
+    private void expectNetworkStatsSummary(NetworkStats summary, NetworkStats tetherStats)
+            throws Exception {
         when(mConnManager.getAllVpnInfo()).thenReturn(new VpnInfo[0]);
 
-        expectNetworkStatsSummaryDev(summary);
-        expectNetworkStatsSummaryXt(summary);
+        expectNetworkStatsTethering(STATS_PER_IFACE, tetherStats);
+        expectNetworkStatsSummaryDev(summary.clone());
+        expectNetworkStatsSummaryXt(summary.clone());
     }
 
     private void expectNetworkStatsSummaryDev(NetworkStats summary) throws Exception {
@@ -1027,17 +1042,21 @@
         when(mNetManager.getNetworkStatsSummaryXt()).thenReturn(summary);
     }
 
-    private void expectNetworkStatsUidDetail(NetworkStats detail) throws Exception {
-        expectNetworkStatsUidDetail(detail, new String[0], new NetworkStats(0L, 0));
+    private void expectNetworkStatsTethering(int how, NetworkStats stats)
+            throws Exception {
+        when(mNetManager.getNetworkStatsTethering(how)).thenReturn(stats);
     }
 
-    private void expectNetworkStatsUidDetail(
-            NetworkStats detail, String[] tetherIfacePairs, NetworkStats tetherStats)
+    private void expectNetworkStatsUidDetail(NetworkStats detail) throws Exception {
+        expectNetworkStatsUidDetail(detail, new NetworkStats(0L, 0));
+    }
+
+    private void expectNetworkStatsUidDetail(NetworkStats detail, NetworkStats tetherStats)
             throws Exception {
         when(mNetManager.getNetworkStatsUidDetail(UID_ALL)).thenReturn(detail);
 
         // also include tethering details, since they are folded into UID
-        when(mNetManager.getNetworkStatsTethering()).thenReturn(tetherStats);
+        when(mNetManager.getNetworkStatsTethering(STATS_PER_UID)).thenReturn(tetherStats);
     }
 
     private void expectDefaultSettings() throws Exception {