Watch network subtype, tethering teardown, empty.

Watch for changes to telephony network subtype, and update iface
mapping to persist stats under correct type.  Update network stats
before removing tethering NAT rules.

Skip recording that would create empty historical buckets.  Query UID
stats before iface stats to always skew positive when counters are
actively rolling forward.

Bug: 5360042, 5359860, 5335674, 5334448
Change-Id: I8aa37b568e8ffb70647218aa1aff5195d3e44d5a
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index a5cdf70..a6635be 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -270,6 +270,11 @@
                 || entry.operations < 0) {
             throw new IllegalArgumentException("tried recording negative data");
         }
+        if (entry.rxBytes == 0 && entry.rxPackets == 0 && entry.txBytes == 0 && entry.txPackets == 0
+                && entry.operations == 0) {
+            // nothing to record; skip
+            return;
+        }
 
         // create any buckets needed by this range
         ensureBuckets(start, end);
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index bc65205..aa46795 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -24,8 +24,8 @@
 import static android.content.Intent.ACTION_SHUTDOWN;
 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.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
 import static android.net.NetworkStats.IFACE_ALL;
 import static android.net.NetworkStats.SET_ALL;
 import static android.net.NetworkStats.SET_DEFAULT;
@@ -43,9 +43,12 @@
 import static android.provider.Settings.Secure.NETSTATS_TAG_MAX_HISTORY;
 import static android.provider.Settings.Secure.NETSTATS_UID_BUCKET_DURATION;
 import static android.provider.Settings.Secure.NETSTATS_UID_MAX_HISTORY;
+import static android.telephony.PhoneStateListener.LISTEN_DATA_CONNECTION_STATE;
+import static android.telephony.PhoneStateListener.LISTEN_NONE;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
 import static com.android.internal.util.Preconditions.checkNotNull;
 import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
 import static com.android.server.NetworkManagementSocketTagger.resetKernelUidStats;
@@ -80,6 +83,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.provider.Settings;
+import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
 import android.util.EventLog;
 import android.util.Log;
@@ -121,7 +125,7 @@
  */
 public class NetworkStatsService extends INetworkStatsService.Stub {
     private static final String TAG = "NetworkStats";
-    private static final boolean LOGD = true;
+    private static final boolean LOGD = false;
     private static final boolean LOGV = false;
 
     /** File header magic number: "ANET" */
@@ -132,7 +136,8 @@
     private static final int VERSION_UID_WITH_TAG = 3;
     private static final int VERSION_UID_WITH_SET = 4;
 
-    private static final int MSG_PERFORM_POLL = 0x1;
+    private static final int MSG_PERFORM_POLL = 1;
+    private static final int MSG_UPDATE_IFACES = 2;
 
     /** Flags to control detail level of poll event. */
     private static final int FLAG_PERSIST_NETWORK = 0x10;
@@ -144,6 +149,7 @@
     private final INetworkManagementService mNetworkManager;
     private final IAlarmManager mAlarmManager;
     private final TrustedTime mTime;
+    private final TelephonyManager mTeleManager;
     private final NetworkStatsSettings mSettings;
 
     private final PowerManager.WakeLock mWakeLock;
@@ -227,6 +233,7 @@
         mNetworkManager = checkNotNull(networkManager, "missing INetworkManagementService");
         mAlarmManager = checkNotNull(alarmManager, "missing IAlarmManager");
         mTime = checkNotNull(time, "missing TrustedTime");
+        mTeleManager = checkNotNull(TelephonyManager.getDefault(), "missing TelephonyManager");
         mSettings = checkNotNull(settings, "missing NetworkStatsSettings");
 
         final PowerManager powerManager = (PowerManager) context.getSystemService(
@@ -279,6 +286,10 @@
             // ignored; service lives in system_server
         }
 
+        // watch for networkType changes that aren't broadcast through
+        // CONNECTIVITY_ACTION_IMMEDIATE above.
+        mTeleManager.listen(mPhoneListener, LISTEN_DATA_CONNECTION_STATE);
+
         registerPollAlarmLocked();
         registerGlobalAlert();
 
@@ -288,10 +299,13 @@
 
     private void shutdownLocked() {
         mContext.unregisterReceiver(mConnReceiver);
+        mContext.unregisterReceiver(mTetherReceiver);
         mContext.unregisterReceiver(mPollReceiver);
         mContext.unregisterReceiver(mRemovedReceiver);
         mContext.unregisterReceiver(mShutdownReceiver);
 
+        mTeleManager.listen(mPhoneListener, LISTEN_NONE);
+
         writeNetworkStatsLocked();
         if (mUidStatsLoaded) {
             writeUidStatsLocked();
@@ -535,14 +549,7 @@
         public void onReceive(Context context, Intent intent) {
             // on background handler thread, and verified CONNECTIVITY_INTERNAL
             // permission above.
-            synchronized (mStatsLock) {
-                mWakeLock.acquire();
-                try {
-                    updateIfacesLocked();
-                } finally {
-                    mWakeLock.release();
-                }
-            }
+            updateIfaces();
         }
     };
 
@@ -619,6 +626,46 @@
         }
     };
 
+    private int mLastPhoneState = TelephonyManager.DATA_UNKNOWN;
+    private int mLastPhoneNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+
+    /**
+     * Receiver that watches for {@link TelephonyManager} changes, such as
+     * transitioning between network types.
+     */
+    private PhoneStateListener mPhoneListener = new PhoneStateListener() {
+        @Override
+        public void onDataConnectionStateChanged(int state, int networkType) {
+            final boolean stateChanged = state != mLastPhoneState;
+            final boolean networkTypeChanged = networkType != mLastPhoneNetworkType;
+
+            if (networkTypeChanged && !stateChanged) {
+                // networkType changed without a state change, which means we
+                // need to roll our own update. delay long enough for
+                // ConnectivityManager to process.
+                // TODO: add direct event to ConnectivityService instead of
+                // relying on this delay.
+                if (LOGV) Slog.v(TAG, "triggering delayed updateIfaces()");
+                mHandler.sendMessageDelayed(
+                        mHandler.obtainMessage(MSG_UPDATE_IFACES), SECOND_IN_MILLIS);
+            }
+
+            mLastPhoneState = state;
+            mLastPhoneNetworkType = networkType;
+        }
+    };
+
+    private void updateIfaces() {
+        synchronized (mStatsLock) {
+            mWakeLock.acquire();
+            try {
+                updateIfacesLocked();
+            } finally {
+                mWakeLock.release();
+            }
+        }
+    }
+
     /**
      * Inspect all current {@link NetworkState} to derive mapping from {@code
      * iface} to {@link NetworkStatsHistory}. When multiple {@link NetworkInfo}
@@ -713,19 +760,6 @@
         final long threshold = mSettings.getPersistThreshold();
 
         try {
-            // record network stats
-            final NetworkStats networkSnapshot = mNetworkManager.getNetworkStatsSummary();
-            performNetworkPollLocked(networkSnapshot, currentTime);
-
-            // persist when enough network data has occurred
-            final NetworkStats persistNetworkDelta = computeStatsDelta(
-                    mLastPersistNetworkSnapshot, networkSnapshot, true);
-            final boolean networkPastThreshold = persistNetworkDelta.getTotalBytes() > threshold;
-            if (persistForce || (persistNetwork && networkPastThreshold)) {
-                writeNetworkStatsLocked();
-                mLastPersistNetworkSnapshot = networkSnapshot;
-            }
-
             // record tethering stats; persisted during normal UID cycle below
             final String[] ifacePairs = mConnManager.getTetheredIfacePairs();
             final NetworkStats tetherSnapshot = mNetworkManager.getNetworkStatsTethering(
@@ -744,6 +778,19 @@
                 writeUidStatsLocked();
                 mLastPersistUidSnapshot = uidSnapshot;
             }
+
+            // record network stats
+            final NetworkStats networkSnapshot = mNetworkManager.getNetworkStatsSummary();
+            performNetworkPollLocked(networkSnapshot, currentTime);
+
+            // persist when enough network data has occurred
+            final NetworkStats persistNetworkDelta = computeStatsDelta(
+                    mLastPersistNetworkSnapshot, networkSnapshot, true);
+            final boolean networkPastThreshold = persistNetworkDelta.getTotalBytes() > threshold;
+            if (persistForce || (persistNetwork && networkPastThreshold)) {
+                writeNetworkStatsLocked();
+                mLastPersistNetworkSnapshot = networkSnapshot;
+            }
         } catch (IllegalStateException e) {
             Log.wtf(TAG, "problem reading network stats", e);
         } catch (RemoteException e) {
@@ -1356,6 +1403,10 @@
                     performPoll(flags);
                     return true;
                 }
+                case MSG_UPDATE_IFACES: {
+                    updateIfaces();
+                    return true;
+                }
                 default: {
                     return false;
                 }