Clamp non-monotonic stats instead of dropping.

When encountering non-monotonic stats rows, recover remaining data by
clamping to 0.  In particular, this avoids edge-case where persisting
threshold checks would never trigger.  Also recover when tethering
snapshots are missing.

Bug: 5600785, 5433871, 5600678
Change-Id: I1871954ce3955cc4ac8846f9841bae0066176ffe
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 3605652..f6e627c 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -464,6 +464,19 @@
      * time, and that none of them have disappeared.
      */
     public NetworkStats subtract(NetworkStats value) throws NonMonotonicException {
+        return subtract(value, false);
+    }
+
+    /**
+     * Subtract the given {@link NetworkStats}, effectively leaving the delta
+     * between two snapshots in time. Assumes that statistics rows collect over
+     * time, and that none of them have disappeared.
+     *
+     * @param clampNonMonotonic When non-monotonic stats are found, just clamp
+     *            to 0 instead of throwing {@link NonMonotonicException}.
+     */
+    public NetworkStats subtract(NetworkStats value, boolean clampNonMonotonic)
+            throws NonMonotonicException {
         final long deltaRealtime = this.elapsedRealtime - value.elapsedRealtime;
         if (deltaRealtime < 0) {
             throw new NonMonotonicException(this, value);
@@ -497,7 +510,15 @@
 
                 if (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
                         || entry.txPackets < 0 || entry.operations < 0) {
-                    throw new NonMonotonicException(this, i, value, j);
+                    if (clampNonMonotonic) {
+                        entry.rxBytes = Math.max(entry.rxBytes, 0);
+                        entry.rxPackets = Math.max(entry.rxPackets, 0);
+                        entry.txBytes = Math.max(entry.txBytes, 0);
+                        entry.txPackets = Math.max(entry.txPackets, 0);
+                        entry.operations = Math.max(entry.operations, 0);
+                    } else {
+                        throw new NonMonotonicException(this, i, value, j);
+                    }
                 }
             }
 
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index 77b0d96..6365525 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -806,9 +806,7 @@
         final NetworkStats networkDevSnapshot;
         try {
             // collect any tethering stats
-            final String[] tetheredIfacePairs = mConnManager.getTetheredIfacePairs();
-            final NetworkStats tetherSnapshot = mNetworkManager.getNetworkStatsTethering(
-                    tetheredIfacePairs);
+            final NetworkStats tetherSnapshot = getNetworkStatsTethering();
 
             // record uid stats, folding in tethering stats
             uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL);
@@ -1505,7 +1503,7 @@
             NetworkStats before, NetworkStats current, boolean collectStale, String type) {
         if (before != null) {
             try {
-                return current.subtract(before);
+                return current.subtract(before, false);
             } catch (NonMonotonicException e) {
                 Log.w(TAG, "found non-monotonic values; saving to dropbox");
 
@@ -1517,8 +1515,13 @@
                 builder.append("right=").append(e.right).append('\n');
                 mDropBox.addText(TAG_NETSTATS_ERROR, builder.toString());
 
-                // return empty delta to avoid recording broken stats
-                return new NetworkStats(0L, 10);
+                try {
+                    // return clamped delta to help recover
+                    return current.subtract(before, true);
+                } catch (NonMonotonicException e1) {
+                    Log.wtf(TAG, "found non-monotonic values; returning empty delta", e1);
+                    return new NetworkStats(0L, 10);
+                }
             }
         } else if (collectStale) {
             // caller is okay collecting stale stats for first call.
@@ -1530,6 +1533,20 @@
         }
     }
 
+    /**
+     * Return snapshot of current tethering statistics. Will return empty
+     * {@link NetworkStats} if any problems are encountered.
+     */
+    private NetworkStats getNetworkStatsTethering() throws RemoteException {
+        try {
+            final String[] tetheredIfacePairs = mConnManager.getTetheredIfacePairs();
+            return mNetworkManager.getNetworkStatsTethering(tetheredIfacePairs);
+        } catch (IllegalStateException e) {
+            Log.wtf(TAG, "problem reading network stats", e);
+            return new NetworkStats(0L, 10);
+        }
+    }
+
     private static NetworkStats computeNetworkXtSnapshotFromUid(NetworkStats uidSnapshot) {
         return uidSnapshot.groupedByIface();
     }