Collect and persist tethering stats.

Use new "gettetherstats" netd command to retrieve statistics for
active tethering connections.  Keep tethering poll events separate
from UID poll, even though they end up same historical structures.

Bug: 5244846
Change-Id: Ia0c5165f6712c12b51586f86c331a2aad4ad6afb
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index eef658e..7046008 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -83,6 +83,12 @@
 
     String[] getTetheredIfaces();
 
+    /**
+     * Return list of interface pairs that are actively tethered.  Even indexes are
+     * remote interface, and odd indexes are corresponding local interfaces.
+     */
+    String[] getTetheredIfacePairs();
+
     String[] getTetheringErroredIfaces();
 
     String[] getTetherableUsbRegexs();
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 47cfa73..18eb9f6 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -53,25 +53,33 @@
     public static final int UID_REMOVED = -4;
 
     /**
+     * Special UID value used when collecting {@link NetworkStatsHistory} for
+     * tethering traffic.
+     *
+     * @hide
+     */
+    public static final int UID_TETHERING = -5;
+
+    /**
      * Default tag value for {@link DownloadManager} traffic.
      *
      * @hide
      */
-    public static final int TAG_SYSTEM_DOWNLOAD = 0xFFFF0001;
+    public static final int TAG_SYSTEM_DOWNLOAD = 0xFFFFFF01;
 
     /**
      * Default tag value for {@link MediaPlayer} traffic.
      *
      * @hide
      */
-    public static final int TAG_SYSTEM_MEDIA = 0xFFFF0002;
+    public static final int TAG_SYSTEM_MEDIA = 0xFFFFFF02;
 
     /**
      * Default tag value for {@link BackupManager} traffic.
      *
      * @hide
      */
-    public static final int TAG_SYSTEM_BACKUP = 0xFFFF0003;
+    public static final int TAG_SYSTEM_BACKUP = 0xFFFFFF03;
 
     /**
      * Snapshot of {@link NetworkStats} when the currently active profiling
@@ -90,6 +98,10 @@
      * <p>
      * Changes only take effect during subsequent calls to
      * {@link #tagSocket(Socket)}.
+     * <p>
+     * Tags between {@code 0xFFFFFF00} and {@code 0xFFFFFFFF} are reserved and
+     * used internally by system services like {@link DownloadManager} when
+     * performing traffic on behalf of an application.
      */
     public static void setThreadStatsTag(int tag) {
         NetworkManagementSocketTagger.setThreadSocketStatsTag(tag);
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 4e5645d..66373fe 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -231,6 +231,13 @@
     NetworkStats getNetworkStatsUidDetail(int uid);
 
     /**
+     * Return summary of network statistics for the requested pairs of
+     * tethering interfaces.  Even indexes are remote interface, and odd
+     * indexes are corresponding local interfaces.
+     */
+    NetworkStats getNetworkStatsTethering(in String[] ifacePairs);
+
+    /**
      * Set quota for an interface.
      */
     void setInterfaceQuota(String iface, long quotaBytes);
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 2348d76..e0a2adc 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -2394,6 +2394,12 @@
         return mTethering.getTetheredIfaces();
     }
 
+    @Override
+    public String[] getTetheredIfacePairs() {
+        enforceTetherAccessPermission();
+        return mTethering.getTetheredIfacePairs();
+    }
+
     public String[] getTetheringErroredIfaces() {
         enforceTetherAccessPermission();
         return mTethering.getErroredIfaces();
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index c517965..0cffb15 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -123,6 +123,8 @@
         public static final int InterfaceTxCounterResult  = 217;
         public static final int InterfaceRxThrottleResult = 218;
         public static final int InterfaceTxThrottleResult = 219;
+        public static final int QuotaCounterResult        = 220;
+        public static final int TetheringStatsResult      = 221;
 
         public static final int InterfaceChange           = 600;
         public static final int BandwidthControl          = 601;
@@ -1443,6 +1445,73 @@
         return stats;
     }
 
+    @Override
+    public NetworkStats getNetworkStatsTethering(String[] ifacePairs) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
+
+        if (ifacePairs.length % 2 != 0) {
+            throw new IllegalArgumentException(
+                    "unexpected ifacePairs; length=" + ifacePairs.length);
+        }
+
+        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
+        for (int i = 0; i < ifacePairs.length; i += 2) {
+            final String ifaceIn = ifacePairs[i];
+            final String ifaceOut = ifacePairs[i + 1];
+            if (ifaceIn != null && ifaceOut != null) {
+                stats.combineValues(getNetworkStatsTethering(ifaceIn, ifaceOut));
+            }
+        }
+        return stats;
+    }
+
+    private NetworkStats.Entry getNetworkStatsTethering(String ifaceIn, String ifaceOut) {
+        final StringBuilder command = new StringBuilder();
+        command.append("bandwidth gettetherstats ").append(ifaceIn).append(" ").append(ifaceOut);
+
+        final String rsp;
+        try {
+            rsp = mConnector.doCommand(command.toString()).get(0);
+        } catch (NativeDaemonConnectorException e) {
+            throw new IllegalStateException("Error communicating to native daemon", e);
+        }
+
+        final String[] tok = rsp.split(" ");
+        /* Expecting: "code ifaceIn ifaceOut rx_bytes rx_packets tx_bytes tx_packets" */
+        if (tok.length != 7) {
+            throw new IllegalStateException("Native daemon returned unexpected result: " + rsp);
+        }
+
+        final int code;
+        try {
+            code = Integer.parseInt(tok[0]);
+        } catch (NumberFormatException e) {
+            throw new IllegalStateException(
+                    "Failed to parse native daemon return code for " + ifaceIn + " " + ifaceOut);
+        }
+        if (code != NetdResponseCode.TetheringStatsResult) {
+            throw new IllegalStateException(
+                    "Unexpected return code from native daemon for " + ifaceIn + " " + ifaceOut);
+        }
+
+        try {
+            final NetworkStats.Entry entry = new NetworkStats.Entry();
+            entry.iface = ifaceIn;
+            entry.uid = UID_ALL;
+            entry.set = SET_DEFAULT;
+            entry.tag = TAG_NONE;
+            entry.rxBytes = Long.parseLong(tok[3]);
+            entry.rxPackets = Long.parseLong(tok[4]);
+            entry.txBytes = Long.parseLong(tok[5]);
+            entry.txPackets = Long.parseLong(tok[6]);
+            return entry;
+        } catch (NumberFormatException e) {
+            throw new IllegalStateException(
+                    "problem parsing tethering stats for " + ifaceIn + " " + ifaceOut + ": " + e);
+        }
+    }
+
     public void setInterfaceThrottle(String iface, int rxKbps, int txKbps) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index ae8b89d..5286824 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -19,7 +19,6 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
-import android.bluetooth.BluetoothPan;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -28,15 +27,14 @@
 import android.content.res.Resources;
 import android.hardware.usb.UsbManager;
 import android.net.ConnectivityManager;
-import android.net.InterfaceConfiguration;
 import android.net.IConnectivityManager;
 import android.net.INetworkManagementEventObserver;
+import android.net.InterfaceConfiguration;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.NetworkInfo;
 import android.net.NetworkUtils;
 import android.os.Binder;
-import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.INetworkManagementService;
@@ -51,6 +49,7 @@
 import com.android.internal.util.IState;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.google.android.collect.Lists;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -59,8 +58,8 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
-import java.util.LinkedList;
 import java.util.Set;
+
 /**
  * @hide
  *
@@ -68,7 +67,6 @@
  *
  * TODO - look for parent classes and code sharing
  */
-
 public class Tethering extends INetworkManagementEventObserver.Stub {
 
     private Context mContext;
@@ -629,6 +627,19 @@
         return retVal;
     }
 
+    public String[] getTetheredIfacePairs() {
+        final ArrayList<String> list = Lists.newArrayList();
+        synchronized (mIfaces) {
+            for (TetherInterfaceSM sm : mIfaces.values()) {
+                if (sm.isTethered()) {
+                    list.add(sm.mMyUpstreamIfaceName);
+                    list.add(sm.mIfaceName);
+                }
+            }
+        }
+        return list.toArray(new String[list.size()]);
+    }
+
     public String[] getTetherableIfaces() {
         ArrayList<String> list = new ArrayList<String>();
         synchronized (mIfaces) {
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index a2b097e..bb5f015 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -25,6 +25,7 @@
 import static android.content.Intent.ACTION_UID_REMOVED;
 import static android.content.Intent.EXTRA_UID;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
+import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED;
 import static android.net.NetworkStats.IFACE_ALL;
 import static android.net.NetworkStats.SET_ALL;
 import static android.net.NetworkStats.SET_DEFAULT;
@@ -34,6 +35,7 @@
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
 import static android.net.NetworkTemplate.buildTemplateWifi;
 import static android.net.TrafficStats.UID_REMOVED;
+import static android.net.TrafficStats.UID_TETHERING;
 import static android.provider.Settings.Secure.NETSTATS_FORCE_COMPLETE_POLL;
 import static android.provider.Settings.Secure.NETSTATS_NETWORK_BUCKET_DURATION;
 import static android.provider.Settings.Secure.NETSTATS_NETWORK_MAX_HISTORY;
@@ -68,6 +70,7 @@
 import android.net.NetworkStats;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
+import android.net.TrafficStats;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.Handler;
@@ -89,6 +92,7 @@
 import com.android.internal.os.AtomicFile;
 import com.android.internal.util.Objects;
 import com.android.server.EventLogTags;
+import com.android.server.connectivity.Tethering;
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
 import com.google.android.collect.Sets;
@@ -134,11 +138,12 @@
     /** Flags to control detail level of poll event. */
     private static final int FLAG_POLL_NETWORK = 0x1;
     private static final int FLAG_POLL_UID = 0x2;
+    private static final int FLAG_POLL_TETHER = 0x3;
     private static final int FLAG_PERSIST_NETWORK = 0x10;
     private static final int FLAG_PERSIST_UID = 0x20;
     private static final int FLAG_FORCE_PERSIST = 0x100;
 
-    private static final int FLAG_POLL_ALL = FLAG_POLL_NETWORK | FLAG_POLL_UID;
+    private static final int FLAG_POLL_ALL = FLAG_POLL_NETWORK | FLAG_POLL_UID | FLAG_POLL_TETHER;
     private static final int FLAG_PERSIST_ALL = FLAG_PERSIST_NETWORK | FLAG_PERSIST_UID;
 
     private final Context mContext;
@@ -195,6 +200,7 @@
     private NetworkStats mLastPollNetworkSnapshot;
     private NetworkStats mLastPollUidSnapshot;
     private NetworkStats mLastPollOperationsSnapshot;
+    private NetworkStats mLastPollTetherSnapshot;
 
     private NetworkStats mLastPersistNetworkSnapshot;
     private NetworkStats mLastPersistUidSnapshot;
@@ -258,6 +264,10 @@
         final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION_IMMEDIATE);
         mContext.registerReceiver(mConnReceiver, connFilter, CONNECTIVITY_INTERNAL, mHandler);
 
+        // watch for tethering changes
+        final IntentFilter tetherFilter = new IntentFilter(ACTION_TETHER_STATE_CHANGED);
+        mContext.registerReceiver(mTetherReceiver, tetherFilter, CONNECTIVITY_INTERNAL, mHandler);
+
         // listen for periodic polling events
         final IntentFilter pollFilter = new IntentFilter(ACTION_NETWORK_STATS_POLL);
         mContext.registerReceiver(mPollReceiver, pollFilter, READ_NETWORK_USAGE_HISTORY, mHandler);
@@ -543,6 +553,18 @@
         }
     };
 
+    /**
+     * Receiver that watches for {@link Tethering} to claim interface pairs.
+     */
+    private BroadcastReceiver mTetherReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // on background handler thread, and verified CONNECTIVITY_INTERNAL
+            // permission above.
+            performPoll(FLAG_POLL_TETHER);
+        }
+    };
+
     private BroadcastReceiver mPollReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -686,12 +708,14 @@
 
         boolean pollNetwork = (flags & FLAG_POLL_NETWORK) != 0;
         boolean pollUid = (flags & FLAG_POLL_UID) != 0;
+        boolean pollTether = (flags & FLAG_POLL_TETHER) != 0;
 
         // when complete poll requested, any partial poll enables everything
         final boolean forceCompletePoll = mSettings.getForceCompletePoll();
-        if (forceCompletePoll && (pollNetwork || pollUid)) {
+        if (forceCompletePoll && (pollNetwork || pollUid || pollTether)) {
             pollNetwork = true;
             pollUid = true;
+            pollTether = true;
         }
 
         final boolean persistNetwork = (flags & FLAG_PERSIST_NETWORK) != 0;
@@ -723,6 +747,15 @@
                 }
             }
 
+            if (pollTether) {
+                final String[] ifacePairs = mConnManager.getTetheredIfacePairs();
+                final NetworkStats tetherSnapshot = mNetworkManager.getNetworkStatsTethering(
+                        ifacePairs);
+                performTetherPollLocked(tetherSnapshot, currentTime);
+
+                // persisted during normal UID cycle below
+            }
+
             if (pollUid) {
                 final NetworkStats uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL);
                 performUidPollLocked(uidSnapshot, currentTime);
@@ -849,6 +882,38 @@
     }
 
     /**
+     * Update {@link #mUidStats} historical usage for
+     * {@link TrafficStats#UID_TETHERING} based on tethering statistics.
+     */
+    private void performTetherPollLocked(NetworkStats tetherSnapshot, long currentTime) {
+        ensureUidStatsLoadedLocked();
+
+        final NetworkStats delta = computeStatsDelta(
+                mLastPollTetherSnapshot, tetherSnapshot, false);
+        final long timeStart = currentTime - delta.getElapsedRealtime();
+
+        NetworkStats.Entry entry = null;
+        for (int i = 0; i < delta.size(); i++) {
+            entry = delta.getValues(i, entry);
+            final NetworkIdentitySet ident = mActiveIfaces.get(entry.iface);
+            if (ident == null) {
+                if (entry.rxBytes > 0 || entry.rxPackets > 0 || entry.txBytes > 0
+                        || entry.txPackets > 0) {
+                    Log.w(TAG, "dropping tether delta from unknown iface: " + entry);
+                }
+                continue;
+            }
+
+            final NetworkStatsHistory history = findOrCreateUidStatsLocked(
+                    ident, UID_TETHERING, SET_DEFAULT, TAG_NONE);
+            history.recordData(timeStart, currentTime, entry);
+        }
+
+        // normal UID poll will trim any history beyond max
+        mLastPollTetherSnapshot = tetherSnapshot;
+    }
+
+    /**
      * Sample recent statistics summary into {@link EventLog}.
      */
     private void performSample() {