merge in ics-mr1-release history after reset to ics-mr1
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 69ac1e7..5c6ef1a 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -16,10 +16,11 @@
 
 package android.net;
 
+import static com.android.internal.util.Preconditions.checkNotNull;
+
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
-import android.util.Log;
 import android.util.SparseBooleanArray;
 
 import com.android.internal.util.Objects;
@@ -54,6 +55,8 @@
     /** {@link #tag} value for total data across all tags. */
     public static final int TAG_NONE = 0;
 
+    // TODO: move fields to "mVariable" notation
+
     /**
      * {@link SystemClock#elapsedRealtime()} timestamp when this data was
      * generated.
@@ -295,8 +298,33 @@
      */
     public int findIndex(String iface, int uid, int set, int tag) {
         for (int i = 0; i < size; i++) {
-            if (Objects.equal(iface, this.iface[i]) && uid == this.uid[i] && set == this.set[i]
-                    && tag == this.tag[i]) {
+            if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i]
+                    && Objects.equal(iface, this.iface[i])) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Find first stats index that matches the requested parameters, starting
+     * search around the hinted index as an optimization.
+     */
+    // @VisibleForTesting
+    public int findIndexHinted(String iface, int uid, int set, int tag, int hintIndex) {
+        for (int offset = 0; offset < size; offset++) {
+            final int halfOffset = offset / 2;
+
+            // search outwards from hint index, alternating forward and backward
+            final int i;
+            if (offset % 2 == 0) {
+                i = (hintIndex + halfOffset) % size;
+            } else {
+                i = (size + hintIndex - halfOffset - 1) % size;
+            }
+
+            if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i]
+                    && Objects.equal(iface, this.iface[i])) {
                 return i;
             }
         }
@@ -423,40 +451,10 @@
      * 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.
-     *
-     * @throws IllegalArgumentException when given {@link NetworkStats} is
-     *             non-monotonic.
      */
-    public NetworkStats subtract(NetworkStats value) {
-        return subtract(value, true, 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.
-     * <p>
-     * Instead of throwing when counters are non-monotonic, this variant clamps
-     * results to never be negative.
-     */
-    public NetworkStats subtractClamped(NetworkStats value) {
-        return subtract(value, false, true);
-    }
-
-    /**
-     * 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 enforceMonotonic Validate that incoming value is strictly
-     *            monotonic compared to this object.
-     * @param clampNegative Instead of throwing like {@code enforceMonotonic},
-     *            clamp resulting counters at 0 to prevent negative values.
-     */
-    private NetworkStats subtract(
-            NetworkStats value, boolean enforceMonotonic, boolean clampNegative) {
+    public NetworkStats subtract(NetworkStats value) throws NonMonotonicException {
         final long deltaRealtime = this.elapsedRealtime - value.elapsedRealtime;
-        if (enforceMonotonic && deltaRealtime < 0) {
+        if (deltaRealtime < 0) {
             throw new IllegalArgumentException("found non-monotonic realtime");
         }
 
@@ -470,7 +468,7 @@
             entry.tag = tag[i];
 
             // find remote row that matches, and subtract
-            final int j = value.findIndex(entry.iface, entry.uid, entry.set, entry.tag);
+            final int j = value.findIndexHinted(entry.iface, entry.uid, entry.set, entry.tag, i);
             if (j == -1) {
                 // newly appearing row, return entire value
                 entry.rxBytes = rxBytes[i];
@@ -485,20 +483,10 @@
                 entry.txBytes = txBytes[i] - value.txBytes[j];
                 entry.txPackets = txPackets[i] - value.txPackets[j];
                 entry.operations = operations[i] - value.operations[j];
-                if (enforceMonotonic
-                        && (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
-                                || entry.txPackets < 0 || entry.operations < 0)) {
-                    Log.v(TAG, "lhs=" + this);
-                    Log.v(TAG, "rhs=" + value);
-                    throw new IllegalArgumentException(
-                            "found non-monotonic values at lhs[" + i + "] - rhs[" + j + "]");
-                }
-                if (clampNegative) {
-                    entry.rxBytes = Math.max(0, entry.rxBytes);
-                    entry.rxPackets = Math.max(0, entry.rxPackets);
-                    entry.txBytes = Math.max(0, entry.txBytes);
-                    entry.txPackets = Math.max(0, entry.txPackets);
-                    entry.operations = Math.max(0, entry.operations);
+
+                if (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
+                        || entry.txPackets < 0 || entry.operations < 0) {
+                    throw new NonMonotonicException(this, i, value, j);
                 }
             }
 
@@ -564,6 +552,24 @@
         return stats;
     }
 
+    /**
+     * Return all rows except those attributed to the requested UID; doesn't
+     * mutate the original structure.
+     */
+    public NetworkStats withoutUid(int uid) {
+        final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
+
+        Entry entry = new Entry();
+        for (int i = 0; i < size; i++) {
+            entry = getValues(i, entry);
+            if (entry.uid != uid) {
+                stats.addValues(entry);
+            }
+        }
+
+        return stats;
+    }
+
     public void dump(String prefix, PrintWriter pw) {
         pw.print(prefix);
         pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
@@ -625,4 +631,19 @@
             return new NetworkStats[size];
         }
     };
+
+    public static class NonMonotonicException extends Exception {
+        public final NetworkStats left;
+        public final NetworkStats right;
+        public final int leftIndex;
+        public final int rightIndex;
+
+        public NonMonotonicException(
+                NetworkStats left, int leftIndex, NetworkStats right, int rightIndex) {
+            this.left = checkNotNull(left, "missing left");
+            this.right = checkNotNull(right, "missing right");
+            this.leftIndex = leftIndex;
+            this.rightIndex = rightIndex;
+        }
+    }
 }
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 18eb9f6..cd585b2 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -20,6 +20,7 @@
 import android.app.backup.BackupManager;
 import android.content.Context;
 import android.media.MediaPlayer;
+import android.net.NetworkStats.NonMonotonicException;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 
@@ -192,12 +193,15 @@
                 throw new IllegalStateException("not profiling data");
             }
 
-            // subtract starting values and return delta
-            final NetworkStats profilingStop = getDataLayerSnapshotForUid(context);
-            final NetworkStats profilingDelta = profilingStop.subtractClamped(
-                    sActiveProfilingStart);
-            sActiveProfilingStart = null;
-            return profilingDelta;
+            try {
+                // subtract starting values and return delta
+                final NetworkStats profilingStop = getDataLayerSnapshotForUid(context);
+                final NetworkStats profilingDelta = profilingStop.subtract(sActiveProfilingStart);
+                sActiveProfilingStart = null;
+                return profilingDelta;
+            } catch (NonMonotonicException e) {
+                throw new RuntimeException(e);
+            }
         }
     }
 
diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java
index ee3f23b..41993c4 100644
--- a/core/java/com/android/internal/net/NetworkStatsFactory.java
+++ b/core/java/com/android/internal/net/NetworkStatsFactory.java
@@ -25,12 +25,14 @@
 import android.os.SystemClock;
 import android.util.Slog;
 
+import com.android.internal.util.ProcFileReader;
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
 import com.google.android.collect.Sets;
 
 import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileReader;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -107,6 +109,7 @@
         final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
         final NetworkStats.Entry entry = new NetworkStats.Entry();
 
+        // TODO: transition to ProcFileReader
         // TODO: read directly from proc once headers are added
         final ArrayList<String> keys = Lists.newArrayList(KEY_IFACE, KEY_ACTIVE, KEY_SNAP_RX_BYTES,
                 KEY_SNAP_RX_PACKETS, KEY_SNAP_TX_BYTES, KEY_SNAP_TX_PACKETS, KEY_RX_BYTES,
@@ -257,71 +260,58 @@
         final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 24);
         final NetworkStats.Entry entry = new NetworkStats.Entry();
 
-        // TODO: remove knownLines check once 5087722 verified
-        final HashSet<String> knownLines = Sets.newHashSet();
-        // TODO: remove lastIdx check once 5270106 verified
-        int lastIdx;
+        int idx = 1;
+        int lastIdx = 1;
 
-        final ArrayList<String> keys = Lists.newArrayList();
-        final ArrayList<String> values = Lists.newArrayList();
-        final HashMap<String, String> parsed = Maps.newHashMap();
-
-        BufferedReader reader = null;
-        String line = null;
+        ProcFileReader reader = null;
         try {
-            reader = new BufferedReader(new FileReader(mStatsXtUid));
+            // open and consume header line
+            reader = new ProcFileReader(new FileInputStream(mStatsXtUid));
+            reader.finishLine();
 
-            // parse first line as header
-            line = reader.readLine();
-            splitLine(line, keys);
-            lastIdx = 1;
-
-            // parse remaining lines
-            while ((line = reader.readLine()) != null) {
-                splitLine(line, values);
-                parseLine(keys, values, parsed);
-
-                if (!knownLines.add(line)) {
-                    throw new IllegalStateException("duplicate proc entry: " + line);
-                }
-
-                final int idx = getParsedInt(parsed, KEY_IDX);
+            while (reader.hasMoreData()) {
+                idx = reader.nextInt();
                 if (idx != lastIdx + 1) {
                     throw new IllegalStateException(
                             "inconsistent idx=" + idx + " after lastIdx=" + lastIdx);
                 }
                 lastIdx = idx;
 
-                entry.iface = parsed.get(KEY_IFACE);
-                entry.uid = getParsedInt(parsed, KEY_UID);
-                entry.set = getParsedInt(parsed, KEY_COUNTER_SET);
-                entry.tag = kernelToTag(parsed.get(KEY_TAG_HEX));
-                entry.rxBytes = getParsedLong(parsed, KEY_RX_BYTES);
-                entry.rxPackets = getParsedLong(parsed, KEY_RX_PACKETS);
-                entry.txBytes = getParsedLong(parsed, KEY_TX_BYTES);
-                entry.txPackets = getParsedLong(parsed, KEY_TX_PACKETS);
+                entry.iface = reader.nextString();
+                entry.tag = kernelToTag(reader.nextString());
+                entry.uid = reader.nextInt();
+                entry.set = reader.nextInt();
+                entry.rxBytes = reader.nextLong();
+                entry.rxPackets = reader.nextLong();
+                entry.txBytes = reader.nextLong();
+                entry.txPackets = reader.nextLong();
 
                 if (limitUid == UID_ALL || limitUid == entry.uid) {
                     stats.addValues(entry);
                 }
+
+                reader.finishLine();
             }
         } catch (NullPointerException e) {
-            throw new IllegalStateException("problem parsing line: " + line, e);
+            throw new IllegalStateException("problem parsing idx " + idx, e);
         } catch (NumberFormatException e) {
-            throw new IllegalStateException("problem parsing line: " + line, e);
+            throw new IllegalStateException("problem parsing idx " + idx, e);
         } catch (IOException e) {
-            throw new IllegalStateException("problem parsing line: " + line, e);
+            throw new IllegalStateException("problem parsing idx " + idx, e);
         } finally {
             IoUtils.closeQuietly(reader);
         }
+
         return stats;
     }
 
+    @Deprecated
     private static int getParsedInt(HashMap<String, String> parsed, String key) {
         final String value = parsed.get(key);
         return value != null ? Integer.parseInt(value) : 0;
     }
 
+    @Deprecated
     private static long getParsedLong(HashMap<String, String> parsed, String key) {
         final String value = parsed.get(key);
         return value != null ? Long.parseLong(value) : 0;
@@ -330,6 +320,7 @@
     /**
      * Split given line into {@link ArrayList}.
      */
+    @Deprecated
     private static void splitLine(String line, ArrayList<String> outSplit) {
         outSplit.clear();
 
@@ -343,6 +334,7 @@
      * Zip the two given {@link ArrayList} as key and value pairs into
      * {@link HashMap}.
      */
+    @Deprecated
     private static void parseLine(
             ArrayList<String> keys, ArrayList<String> values, HashMap<String, String> outParsed) {
         outParsed.clear();
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index 789681e..494c655 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -32,7 +32,6 @@
 import static android.net.NetworkStats.SET_FOREGROUND;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
-import static android.net.NetworkStatsHistory.randomLong;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
 import static android.net.NetworkTemplate.buildTemplateWifi;
 import static android.net.TrafficStats.UID_REMOVED;
@@ -49,7 +48,6 @@
 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 android.text.format.DateUtils.WEEK_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;
@@ -73,9 +71,11 @@
 import android.net.NetworkInfo;
 import android.net.NetworkState;
 import android.net.NetworkStats;
+import android.net.NetworkStats.NonMonotonicException;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
 import android.os.Binder;
+import android.os.DropBoxManager;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -150,6 +150,12 @@
     /** Sample recent usage after each poll event. */
     private static final boolean ENABLE_SAMPLE_AFTER_POLL = true;
 
+    private static final String TAG_NETSTATS_ERROR = "netstats_error";
+
+    private static final String DEV = "dev";
+    private static final String XT = "xt";
+    private static final String UID = "uid";
+
     private final Context mContext;
     private final INetworkManagementService mNetworkManager;
     private final IAlarmManager mAlarmManager;
@@ -160,6 +166,7 @@
     private final PowerManager.WakeLock mWakeLock;
 
     private IConnectivityManager mConnManager;
+    private DropBoxManager mDropBox;
 
     // @VisibleForTesting
     public static final String ACTION_NETWORK_STATS_POLL =
@@ -306,6 +313,8 @@
 
         // bootstrap initial stats to prevent double-counting later
         bootstrapStats();
+
+        mDropBox = (DropBoxManager) mContext.getSystemService(Context.DROPBOX_SERVICE);
     }
 
     private void shutdownLocked() {
@@ -621,7 +630,6 @@
             // broadcast.
             final int uid = intent.getIntExtra(EXTRA_UID, 0);
             synchronized (mStatsLock) {
-                // TODO: perform one last stats poll for UID
                 mWakeLock.acquire();
                 try {
                     removeUidLocked(uid);
@@ -829,9 +837,9 @@
 
         // persist when enough network data has occurred
         final long persistNetworkDevDelta = computeStatsDelta(
-                mLastPersistNetworkDevSnapshot, networkDevSnapshot, true).getTotalBytes();
+                mLastPersistNetworkDevSnapshot, networkDevSnapshot, true, DEV).getTotalBytes();
         final long persistNetworkXtDelta = computeStatsDelta(
-                mLastPersistNetworkXtSnapshot, networkXtSnapshot, true).getTotalBytes();
+                mLastPersistNetworkXtSnapshot, networkXtSnapshot, true, XT).getTotalBytes();
         final boolean networkOverThreshold = persistNetworkDevDelta > threshold
                 || persistNetworkXtDelta > threshold;
         if (persistForce || (persistNetwork && networkOverThreshold)) {
@@ -842,8 +850,8 @@
         }
 
         // persist when enough uid data has occurred
-        final long persistUidDelta = computeStatsDelta(mLastPersistUidSnapshot, uidSnapshot, true)
-                .getTotalBytes();
+        final long persistUidDelta = computeStatsDelta(
+                mLastPersistUidSnapshot, uidSnapshot, true, UID).getTotalBytes();
         if (persistForce || (persistUid && persistUidDelta > threshold)) {
             writeUidStatsLocked();
             mLastPersistUidSnapshot = uidSnapshot;
@@ -872,7 +880,7 @@
         final HashSet<String> unknownIface = Sets.newHashSet();
 
         final NetworkStats delta = computeStatsDelta(
-                mLastPollNetworkDevSnapshot, networkDevSnapshot, false);
+                mLastPollNetworkDevSnapshot, networkDevSnapshot, false, DEV);
         final long timeStart = currentTime - delta.getElapsedRealtime();
 
         NetworkStats.Entry entry = null;
@@ -902,7 +910,7 @@
         final HashSet<String> unknownIface = Sets.newHashSet();
 
         final NetworkStats delta = computeStatsDelta(
-                mLastPollNetworkXtSnapshot, networkXtSnapshot, false);
+                mLastPollNetworkXtSnapshot, networkXtSnapshot, false, XT);
         final long timeStart = currentTime - delta.getElapsedRealtime();
 
         NetworkStats.Entry entry = null;
@@ -931,9 +939,10 @@
     private void performUidPollLocked(NetworkStats uidSnapshot, long currentTime) {
         ensureUidStatsLoadedLocked();
 
-        final NetworkStats delta = computeStatsDelta(mLastPollUidSnapshot, uidSnapshot, false);
+        final NetworkStats delta = computeStatsDelta(
+                mLastPollUidSnapshot, uidSnapshot, false, UID);
         final NetworkStats operationsDelta = computeStatsDelta(
-                mLastPollOperationsSnapshot, mOperations, false);
+                mLastPollOperationsSnapshot, mOperations, false, UID);
         final long timeStart = currentTime - delta.getElapsedRealtime();
 
         NetworkStats.Entry entry = null;
@@ -1014,6 +1023,9 @@
     private void removeUidLocked(int uid) {
         ensureUidStatsLoadedLocked();
 
+        // perform one last poll before removing
+        performPollLocked(FLAG_PERSIST_ALL);
+
         final ArrayList<UidStatsKey> knownKeys = Lists.newArrayList();
         knownKeys.addAll(mUidStats.keySet());
 
@@ -1031,6 +1043,10 @@
             }
         }
 
+        // clear UID from current stats snapshot
+        mLastPollUidSnapshot = mLastPollUidSnapshot.withoutUid(uid);
+        mLastPollNetworkXtSnapshot = computeNetworkXtSnapshotFromUid(mLastPollUidSnapshot);
+
         // clear kernel stats associated with UID
         resetKernelUidStats(uid);
 
@@ -1490,10 +1506,25 @@
      * Return the delta between two {@link NetworkStats} snapshots, where {@code
      * before} can be {@code null}.
      */
-    private static NetworkStats computeStatsDelta(
-            NetworkStats before, NetworkStats current, boolean collectStale) {
+    private NetworkStats computeStatsDelta(
+            NetworkStats before, NetworkStats current, boolean collectStale, String type) {
         if (before != null) {
-            return current.subtractClamped(before);
+            try {
+                return current.subtract(before);
+            } catch (NonMonotonicException e) {
+                Log.w(TAG, "found non-monotonic values; saving to dropbox");
+
+                // record error for debugging
+                final StringBuilder builder = new StringBuilder();
+                builder.append("found non-monotonic " + type + "values at left[" + e.leftIndex
+                        + "] - right[" + e.rightIndex + "]\n");
+                builder.append("left=").append(e.left).append('\n');
+                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);
+            }
         } else if (collectStale) {
             // caller is okay collecting stale stats for first call.
             return current;