Merge "return value of String.replace() is ignored."
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index fb7a4f8..446bbf0 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -21,6 +21,7 @@
 import android.os.SystemClock;
 import android.util.SparseBooleanArray;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Objects;
 
 import java.io.CharArrayWriter;
@@ -608,13 +609,13 @@
      * Return all rows except those attributed to the requested UID; doesn't
      * mutate the original structure.
      */
-    public NetworkStats withoutUid(int uid) {
+    public NetworkStats withoutUids(int[] uids) {
         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) {
+            if (!ArrayUtils.contains(uids, entry.uid)) {
                 stats.addValues(entry);
             }
         }
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index a37c26f..382b25e 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -177,6 +177,12 @@
                 throw new ProtocolException("unexpected version: " + version);
             }
         }
+
+        if (bucketStart.length != bucketCount || rxBytes.length != bucketCount
+                || rxPackets.length != bucketCount || txBytes.length != bucketCount
+                || txPackets.length != bucketCount || operations.length != bucketCount) {
+            throw new ProtocolException("Mismatched history lengths");
+        }
     }
 
     public void writeToStream(DataOutputStream out) throws IOException {
diff --git a/services/java/com/android/server/NativeDaemonConnector.java b/services/java/com/android/server/NativeDaemonConnector.java
index f71125a..92af9a9 100644
--- a/services/java/com/android/server/NativeDaemonConnector.java
+++ b/services/java/com/android/server/NativeDaemonConnector.java
@@ -35,6 +35,9 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
 import java.util.LinkedList;
 
 /**
@@ -482,102 +485,108 @@
 
     private static class ResponseQueue {
 
-        private static class Response {
+        private static class PendingCmd {
             public int cmdNum;
-            public LinkedList<NativeDaemonEvent> responses = new LinkedList<NativeDaemonEvent>();
+            public BlockingQueue<NativeDaemonEvent> responses =
+                    new ArrayBlockingQueue<NativeDaemonEvent>(10);
             public String request;
-            public Response(int c, String r) {cmdNum = c; request = r;}
+
+            // The availableResponseCount member is used to track when we can remove this
+            // instance from the ResponseQueue.
+            // This is used under the protection of a sync of the mPendingCmds object.
+            // A positive value means we've had more writers retreive this object while
+            // a negative value means we've had more readers.  When we've had an equal number
+            // (it goes to zero) we can remove this object from the mPendingCmds list.
+            // Note that we may have more responses for this command (and more readers
+            // coming), but that would result in a new PendingCmd instance being created
+            // and added with the same cmdNum.
+            // Also note that when this goes to zero it just means a parity of readers and
+            // writers have retrieved this object - not that they are done using it.  The
+            // responses queue may well have more responses yet to be read or may get more
+            // responses added to it.  But all those readers/writers have retreived and
+            // hold references to this instance already so it can be removed from
+            // mPendingCmds queue.
+            public int availableResponseCount;
+            public PendingCmd(int c, String r) {cmdNum = c; request = r;}
         }
 
-        private final LinkedList<Response> mResponses;
+        private final LinkedList<PendingCmd> mPendingCmds;
         private int mMaxCount;
 
         ResponseQueue(int maxCount) {
-            mResponses = new LinkedList<Response>();
+            mPendingCmds = new LinkedList<PendingCmd>();
             mMaxCount = maxCount;
         }
 
         public void add(int cmdNum, NativeDaemonEvent response) {
-            Response found = null;
-            synchronized (mResponses) {
-                for (Response r : mResponses) {
-                    if (r.cmdNum == cmdNum) {
-                        found = r;
+            PendingCmd found = null;
+            synchronized (mPendingCmds) {
+                for (PendingCmd pendingCmd : mPendingCmds) {
+                    if (pendingCmd.cmdNum == cmdNum) {
+                        found = pendingCmd;
                         break;
                     }
                 }
                 if (found == null) {
                     // didn't find it - make sure our queue isn't too big before adding
-                    // another..
-                    while (mResponses.size() >= mMaxCount) {
+                    while (mPendingCmds.size() >= mMaxCount) {
                         Slog.e("NativeDaemonConnector.ResponseQueue",
-                                "more buffered than allowed: " + mResponses.size() +
+                                "more buffered than allowed: " + mPendingCmds.size() +
                                 " >= " + mMaxCount);
                         // let any waiter timeout waiting for this
-                        Response r = mResponses.remove();
+                        PendingCmd pendingCmd = mPendingCmds.remove();
                         Slog.e("NativeDaemonConnector.ResponseQueue",
-                                "Removing request: " + r.request + " (" + r.cmdNum + ")");
+                                "Removing request: " + pendingCmd.request + " (" +
+                                pendingCmd.cmdNum + ")");
                     }
-                    found = new Response(cmdNum, null);
-                    mResponses.add(found);
+                    found = new PendingCmd(cmdNum, null);
+                    mPendingCmds.add(found);
                 }
-                found.responses.add(response);
+                found.availableResponseCount++;
+                // if a matching remove call has already retrieved this we can remove this
+                // instance from our list
+                if (found.availableResponseCount == 0) mPendingCmds.remove(found);
             }
-            synchronized (found) {
-                found.notify();
-            }
+            try {
+                found.responses.put(response);
+            } catch (InterruptedException e) { }
         }
 
         // note that the timeout does not count time in deep sleep.  If you don't want
         // the device to sleep, hold a wakelock
         public NativeDaemonEvent remove(int cmdNum, int timeoutMs, String origCmd) {
-            long endTime = SystemClock.uptimeMillis() + timeoutMs;
-            long nowTime;
-            Response found = null;
-            while (true) {
-                synchronized (mResponses) {
-                    for (Response response : mResponses) {
-                        if (response.cmdNum == cmdNum) {
-                            found = response;
-                            // how many response fragments are left
-                            switch (response.responses.size()) {
-                            case 0:  // haven't got any - must wait
-                                break;
-                            case 1:  // last one - remove this from the master list
-                                mResponses.remove(response); // fall through
-                            default: // take one and move on
-                                response.request = origCmd;
-                                return response.responses.remove();
-                            }
-                        }
-                    }
-                    nowTime = SystemClock.uptimeMillis();
-                    if (endTime <= nowTime) {
-                        Slog.e("NativeDaemonConnector.ResponseQueue",
-                                "Timeout waiting for response");
-                        return null;
-                    }
-                    /* pre-allocate so we have something unique to wait on */
-                    if (found == null) {
-                        found = new Response(cmdNum, origCmd);
-                        mResponses.add(found);
+            PendingCmd found = null;
+            synchronized (mPendingCmds) {
+                for (PendingCmd pendingCmd : mPendingCmds) {
+                    if (pendingCmd.cmdNum == cmdNum) {
+                        found = pendingCmd;
+                        break;
                     }
                 }
-                try {
-                    synchronized (found) {
-                        found.wait(endTime - nowTime);
-                    }
-                } catch (InterruptedException e) {
-                    // loop around to check if we're done or if it's time to stop waiting
+                if (found == null) {
+                    found = new PendingCmd(cmdNum, origCmd);
+                    mPendingCmds.add(found);
                 }
+                found.availableResponseCount--;
+                // if a matching add call has already retrieved this we can remove this
+                // instance from our list
+                if (found.availableResponseCount == 0) mPendingCmds.remove(found);
             }
+            NativeDaemonEvent result = null;
+            try {
+                result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {}
+            if (result == null) {
+                Slog.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
+            }
+            return result;
         }
 
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             pw.println("Pending requests:");
-            synchronized (mResponses) {
-                for (Response response : mResponses) {
-                    pw.println("  Cmd " + response.cmdNum + " - " + response.request);
+            synchronized (mPendingCmds) {
+                for (PendingCmd pendingCmd : mPendingCmds) {
+                    pw.println("  Cmd " + pendingCmd.cmdNum + " - " + pendingCmd.request);
                 }
             }
         }
diff --git a/services/java/com/android/server/NsdService.java b/services/java/com/android/server/NsdService.java
index 87843d9..1b9742c 100644
--- a/services/java/com/android/server/NsdService.java
+++ b/services/java/com/android/server/NsdService.java
@@ -31,6 +31,7 @@
 import android.os.Message;
 import android.os.Messenger;
 import android.os.IBinder;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -130,7 +131,7 @@
             };
 
             mContext.getContentResolver().registerContentObserver(
-                    Settings.Secure.getUriFor(Settings.Secure.NSD_ON),
+                    Settings.Global.getUriFor(Settings.Global.NSD_ON),
                     false, contentObserver);
         }
 
@@ -432,7 +433,7 @@
     public void setEnabled(boolean enable) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL,
                 "NsdService");
-        Settings.Secure.putInt(mContentResolver, Settings.Secure.NSD_ON, enable ? 1 : 0);
+        Settings.Global.putInt(mContentResolver, Settings.Global.NSD_ON, enable ? 1 : 0);
         if (enable) {
             mNsdStateMachine.sendMessage(NsdManager.ENABLE);
         } else {
@@ -448,11 +449,11 @@
         } else {
             intent.putExtra(NsdManager.EXTRA_NSD_STATE, NsdManager.NSD_STATE_DISABLED);
         }
-        mContext.sendStickyBroadcast(intent);
+        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
     private boolean isNsdEnabled() {
-        boolean ret = Settings.Secure.getInt(mContentResolver, Settings.Secure.NSD_ON, 1) == 1;
+        boolean ret = Settings.Global.getInt(mContentResolver, Settings.Global.NSD_ON, 1) == 1;
         if (DBG) Slog.d(TAG, "Network service discovery enabled " + ret);
         return ret;
     }
diff --git a/services/java/com/android/server/net/NetworkStatsCollection.java b/services/java/com/android/server/net/NetworkStatsCollection.java
index 9ddf011..3169035 100644
--- a/services/java/com/android/server/net/NetworkStatsCollection.java
+++ b/services/java/com/android/server/net/NetworkStatsCollection.java
@@ -29,8 +29,9 @@
 import android.net.NetworkTemplate;
 import android.net.TrafficStats;
 import android.text.format.DateUtils;
+import android.util.AtomicFile;
 
-import com.android.internal.os.AtomicFile;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FileRotator;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Objects;
@@ -431,13 +432,13 @@
      * moving any {@link NetworkStats#TAG_NONE} series to
      * {@link TrafficStats#UID_REMOVED}.
      */
-    public void removeUid(int uid) {
+    public void removeUids(int[] uids) {
         final ArrayList<Key> knownKeys = Lists.newArrayList();
         knownKeys.addAll(mStats.keySet());
 
         // migrate all UID stats into special "removed" bucket
         for (Key key : knownKeys) {
-            if (key.uid == uid) {
+            if (ArrayUtils.contains(uids, key.uid)) {
                 // only migrate combined TAG_NONE history
                 if (key.tag == TAG_NONE) {
                     final NetworkStatsHistory uidHistory = mStats.get(key);
diff --git a/services/java/com/android/server/net/NetworkStatsRecorder.java b/services/java/com/android/server/net/NetworkStatsRecorder.java
index c3ecf54..2b32b41 100644
--- a/services/java/com/android/server/net/NetworkStatsRecorder.java
+++ b/services/java/com/android/server/net/NetworkStatsRecorder.java
@@ -42,6 +42,7 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.lang.ref.WeakReference;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Map;
 
@@ -233,23 +234,27 @@
      * Remove the given UID from all {@link FileRotator} history, migrating it
      * to {@link TrafficStats#UID_REMOVED}.
      */
-    public void removeUidLocked(int uid) {
+    public void removeUidsLocked(int[] uids) {
         try {
-            // process all existing data to migrate uid
-            mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uid));
+            // Rewrite all persisted data to migrate UID stats
+            mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uids));
         } catch (IOException e) {
-            Log.wtf(TAG, "problem removing UID " + uid, e);
+            Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
             recoverFromWtf();
         }
 
-        // clear UID from current stats snapshot
+        // Remove any pending stats
+        mPending.removeUids(uids);
+        mSinceBoot.removeUids(uids);
+
+        // Clear UID from current stats snapshot
         if (mLastSnapshot != null) {
-            mLastSnapshot = mLastSnapshot.withoutUid(uid);
+            mLastSnapshot = mLastSnapshot.withoutUids(uids);
         }
 
         final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
         if (complete != null) {
-            complete.removeUid(uid);
+            complete.removeUids(uids);
         }
     }
 
@@ -293,11 +298,11 @@
      */
     public static class RemoveUidRewriter implements FileRotator.Rewriter {
         private final NetworkStatsCollection mTemp;
-        private final int mUid;
+        private final int[] mUids;
 
-        public RemoveUidRewriter(long bucketDuration, int uid) {
+        public RemoveUidRewriter(long bucketDuration, int[] uids) {
             mTemp = new NetworkStatsCollection(bucketDuration);
-            mUid = uid;
+            mUids = uids;
         }
 
         @Override
@@ -309,7 +314,7 @@
         public void read(InputStream in) throws IOException {
             mTemp.read(in);
             mTemp.clearDirty();
-            mTemp.removeUid(mUid);
+            mTemp.removeUids(mUids);
         }
 
         @Override
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index ba122ec..a3b44e2 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -23,6 +23,7 @@
 import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
 import static android.content.Intent.ACTION_SHUTDOWN;
 import static android.content.Intent.ACTION_UID_REMOVED;
+import static android.content.Intent.ACTION_USER_REMOVED;
 import static android.content.Intent.EXTRA_UID;
 import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
@@ -38,23 +39,23 @@
 import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
 import static android.net.TrafficStats.KB_IN_BYTES;
 import static android.net.TrafficStats.MB_IN_BYTES;
-import static android.provider.Settings.Secure.NETSTATS_DEV_BUCKET_DURATION;
-import static android.provider.Settings.Secure.NETSTATS_DEV_DELETE_AGE;
-import static android.provider.Settings.Secure.NETSTATS_DEV_PERSIST_BYTES;
-import static android.provider.Settings.Secure.NETSTATS_DEV_ROTATE_AGE;
-import static android.provider.Settings.Secure.NETSTATS_GLOBAL_ALERT_BYTES;
-import static android.provider.Settings.Secure.NETSTATS_POLL_INTERVAL;
-import static android.provider.Settings.Secure.NETSTATS_REPORT_XT_OVER_DEV;
-import static android.provider.Settings.Secure.NETSTATS_SAMPLE_ENABLED;
-import static android.provider.Settings.Secure.NETSTATS_TIME_CACHE_MAX_AGE;
-import static android.provider.Settings.Secure.NETSTATS_UID_BUCKET_DURATION;
-import static android.provider.Settings.Secure.NETSTATS_UID_DELETE_AGE;
-import static android.provider.Settings.Secure.NETSTATS_UID_PERSIST_BYTES;
-import static android.provider.Settings.Secure.NETSTATS_UID_ROTATE_AGE;
-import static android.provider.Settings.Secure.NETSTATS_UID_TAG_BUCKET_DURATION;
-import static android.provider.Settings.Secure.NETSTATS_UID_TAG_DELETE_AGE;
-import static android.provider.Settings.Secure.NETSTATS_UID_TAG_PERSIST_BYTES;
-import static android.provider.Settings.Secure.NETSTATS_UID_TAG_ROTATE_AGE;
+import static android.provider.Settings.Global.NETSTATS_DEV_BUCKET_DURATION;
+import static android.provider.Settings.Global.NETSTATS_DEV_DELETE_AGE;
+import static android.provider.Settings.Global.NETSTATS_DEV_PERSIST_BYTES;
+import static android.provider.Settings.Global.NETSTATS_DEV_ROTATE_AGE;
+import static android.provider.Settings.Global.NETSTATS_GLOBAL_ALERT_BYTES;
+import static android.provider.Settings.Global.NETSTATS_POLL_INTERVAL;
+import static android.provider.Settings.Global.NETSTATS_REPORT_XT_OVER_DEV;
+import static android.provider.Settings.Global.NETSTATS_SAMPLE_ENABLED;
+import static android.provider.Settings.Global.NETSTATS_TIME_CACHE_MAX_AGE;
+import static android.provider.Settings.Global.NETSTATS_UID_BUCKET_DURATION;
+import static android.provider.Settings.Global.NETSTATS_UID_DELETE_AGE;
+import static android.provider.Settings.Global.NETSTATS_UID_PERSIST_BYTES;
+import static android.provider.Settings.Global.NETSTATS_UID_ROTATE_AGE;
+import static android.provider.Settings.Global.NETSTATS_UID_TAG_BUCKET_DURATION;
+import static android.provider.Settings.Global.NETSTATS_UID_TAG_DELETE_AGE;
+import static android.provider.Settings.Global.NETSTATS_UID_TAG_PERSIST_BYTES;
+import static android.provider.Settings.Global.NETSTATS_UID_TAG_ROTATE_AGE;
 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;
@@ -76,6 +77,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.net.IConnectivityManager;
 import android.net.INetworkManagementEventObserver;
 import android.net.INetworkStatsService;
@@ -99,8 +102,9 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.provider.Settings;
-import android.provider.Settings.Secure;
+import android.provider.Settings.Global;
 import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
 import android.util.EventLog;
@@ -111,6 +115,7 @@
 import android.util.SparseIntArray;
 import android.util.TrustedTime;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FileRotator;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.EventLogTags;
@@ -121,8 +126,10 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 
 /**
  * Collect and persist detailed network statistics, and provide this data to
@@ -321,6 +328,10 @@
         final IntentFilter removedFilter = new IntentFilter(ACTION_UID_REMOVED);
         mContext.registerReceiver(mRemovedReceiver, removedFilter, null, mHandler);
 
+        // listen for user changes to clean stats
+        final IntentFilter userFilter = new IntentFilter(ACTION_USER_REMOVED);
+        mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);
+
         // persist stats during clean shutdown
         final IntentFilter shutdownFilter = new IntentFilter(ACTION_SHUTDOWN);
         mContext.registerReceiver(mShutdownReceiver, shutdownFilter);
@@ -685,7 +696,7 @@
     /**
      * Update {@link NetworkStatsRecorder} and {@link #mGlobalAlertBytes} to
      * reflect current {@link #mPersistThreshold} value. Always defers to
-     * {@link Secure} values when defined.
+     * {@link Global} values when defined.
      */
     private void updatePersistThresholds() {
         mDevRecorder.setPersistThreshold(mSettings.getDevPersistBytes(mPersistThreshold));
@@ -738,11 +749,34 @@
         public void onReceive(Context context, Intent intent) {
             // on background handler thread, and UID_REMOVED is protected
             // broadcast.
-            final int uid = intent.getIntExtra(EXTRA_UID, 0);
+
+            final int uid = intent.getIntExtra(EXTRA_UID, -1);
+            if (uid == -1) return;
+
             synchronized (mStatsLock) {
                 mWakeLock.acquire();
                 try {
-                    removeUidLocked(uid);
+                    removeUidsLocked(uid);
+                } finally {
+                    mWakeLock.release();
+                }
+            }
+        }
+    };
+
+    private BroadcastReceiver mUserReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // On background handler thread, and USER_REMOVED is protected
+            // broadcast.
+
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+            if (userId == -1) return;
+
+            synchronized (mStatsLock) {
+                mWakeLock.acquire();
+                try {
+                    removeUserLocked(userId);
                 } finally {
                     mWakeLock.release();
                 }
@@ -763,7 +797,7 @@
     /**
      * Observer that watches for {@link INetworkManagementService} alerts.
      */
-    private INetworkManagementEventObserver mAlertObserver = new NetworkAlertObserver() {
+    private INetworkManagementEventObserver mAlertObserver = new BaseNetworkObserver() {
         @Override
         public void limitReached(String limitName, String iface) {
             // only someone like NMS should be calling us
@@ -905,14 +939,14 @@
     }
 
     private void performPoll(int flags) {
+        // try refreshing time source when stale
+        if (mTime.getCacheAge() > mSettings.getTimeCacheMaxAge()) {
+            mTime.forceRefresh();
+        }
+
         synchronized (mStatsLock) {
             mWakeLock.acquire();
 
-            // try refreshing time source when stale
-            if (mTime.getCacheAge() > mSettings.getTimeCacheMaxAge()) {
-                mTime.forceRefresh();
-            }
-
             try {
                 performPollLocked(flags);
             } finally {
@@ -989,7 +1023,8 @@
         // finally, dispatch updated event to any listeners
         final Intent updatedIntent = new Intent(ACTION_NETWORK_STATS_UPDATED);
         updatedIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-        mContext.sendBroadcast(updatedIntent, READ_NETWORK_USAGE_HISTORY);
+        mContext.sendBroadcastAsUser(updatedIntent, UserHandle.ALL,
+                READ_NETWORK_USAGE_HISTORY);
     }
 
     /**
@@ -1032,15 +1067,37 @@
     /**
      * Clean up {@link #mUidRecorder} after UID is removed.
      */
-    private void removeUidLocked(int uid) {
-        // perform one last poll before removing
+    private void removeUidsLocked(int... uids) {
+        if (LOGV) Slog.v(TAG, "removeUidsLocked() for UIDs " + Arrays.toString(uids));
+
+        // Perform one last poll before removing
         performPollLocked(FLAG_PERSIST_ALL);
 
-        mUidRecorder.removeUidLocked(uid);
-        mUidTagRecorder.removeUidLocked(uid);
+        mUidRecorder.removeUidsLocked(uids);
+        mUidTagRecorder.removeUidsLocked(uids);
 
-        // clear kernel stats associated with UID
-        resetKernelUidStats(uid);
+        // Clear kernel stats associated with UID
+        for (int uid : uids) {
+            resetKernelUidStats(uid);
+        }
+    }
+
+    /**
+     * Clean up {@link #mUidRecorder} after user is removed.
+     */
+    private void removeUserLocked(int userId) {
+        if (LOGV) Slog.v(TAG, "removeUserLocked() for userId=" + userId);
+
+        // Build list of UIDs that we should clean up
+        int[] uids = new int[0];
+        final List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(
+                PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_DISABLED_COMPONENTS);
+        for (ApplicationInfo app : apps) {
+            final int uid = UserHandle.getUid(userId, app.uid);
+            uids = ArrayUtils.appendInt(uids, uid);
+        }
+
+        removeUidsLocked(uids);
     }
 
     @Override
@@ -1206,7 +1263,7 @@
 
     /**
      * Default external settings that read from
-     * {@link android.provider.Settings.Secure}.
+     * {@link android.provider.Settings.Global}.
      */
     private static class DefaultNetworkStatsSettings implements NetworkStatsSettings {
         private final ContentResolver mResolver;
@@ -1216,39 +1273,39 @@
             // TODO: adjust these timings for production builds
         }
 
-        private long getSecureLong(String name, long def) {
-            return Settings.Secure.getLong(mResolver, name, def);
+        private long getGlobalLong(String name, long def) {
+            return Settings.Global.getLong(mResolver, name, def);
         }
-        private boolean getSecureBoolean(String name, boolean def) {
+        private boolean getGlobalBoolean(String name, boolean def) {
             final int defInt = def ? 1 : 0;
-            return Settings.Secure.getInt(mResolver, name, defInt) != 0;
+            return Settings.Global.getInt(mResolver, name, defInt) != 0;
         }
 
         @Override
         public long getPollInterval() {
-            return getSecureLong(NETSTATS_POLL_INTERVAL, 30 * MINUTE_IN_MILLIS);
+            return getGlobalLong(NETSTATS_POLL_INTERVAL, 30 * MINUTE_IN_MILLIS);
         }
         @Override
         public long getTimeCacheMaxAge() {
-            return getSecureLong(NETSTATS_TIME_CACHE_MAX_AGE, DAY_IN_MILLIS);
+            return getGlobalLong(NETSTATS_TIME_CACHE_MAX_AGE, DAY_IN_MILLIS);
         }
         @Override
         public long getGlobalAlertBytes(long def) {
-            return getSecureLong(NETSTATS_GLOBAL_ALERT_BYTES, def);
+            return getGlobalLong(NETSTATS_GLOBAL_ALERT_BYTES, def);
         }
         @Override
         public boolean getSampleEnabled() {
-            return getSecureBoolean(NETSTATS_SAMPLE_ENABLED, true);
+            return getGlobalBoolean(NETSTATS_SAMPLE_ENABLED, true);
         }
         @Override
         public boolean getReportXtOverDev() {
-            return getSecureBoolean(NETSTATS_REPORT_XT_OVER_DEV, true);
+            return getGlobalBoolean(NETSTATS_REPORT_XT_OVER_DEV, true);
         }
         @Override
         public Config getDevConfig() {
-            return new Config(getSecureLong(NETSTATS_DEV_BUCKET_DURATION, HOUR_IN_MILLIS),
-                    getSecureLong(NETSTATS_DEV_ROTATE_AGE, 15 * DAY_IN_MILLIS),
-                    getSecureLong(NETSTATS_DEV_DELETE_AGE, 90 * DAY_IN_MILLIS));
+            return new Config(getGlobalLong(NETSTATS_DEV_BUCKET_DURATION, HOUR_IN_MILLIS),
+                    getGlobalLong(NETSTATS_DEV_ROTATE_AGE, 15 * DAY_IN_MILLIS),
+                    getGlobalLong(NETSTATS_DEV_DELETE_AGE, 90 * DAY_IN_MILLIS));
         }
         @Override
         public Config getXtConfig() {
@@ -1256,19 +1313,19 @@
         }
         @Override
         public Config getUidConfig() {
-            return new Config(getSecureLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS),
-                    getSecureLong(NETSTATS_UID_ROTATE_AGE, 15 * DAY_IN_MILLIS),
-                    getSecureLong(NETSTATS_UID_DELETE_AGE, 90 * DAY_IN_MILLIS));
+            return new Config(getGlobalLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS),
+                    getGlobalLong(NETSTATS_UID_ROTATE_AGE, 15 * DAY_IN_MILLIS),
+                    getGlobalLong(NETSTATS_UID_DELETE_AGE, 90 * DAY_IN_MILLIS));
         }
         @Override
         public Config getUidTagConfig() {
-            return new Config(getSecureLong(NETSTATS_UID_TAG_BUCKET_DURATION, 2 * HOUR_IN_MILLIS),
-                    getSecureLong(NETSTATS_UID_TAG_ROTATE_AGE, 5 * DAY_IN_MILLIS),
-                    getSecureLong(NETSTATS_UID_TAG_DELETE_AGE, 15 * DAY_IN_MILLIS));
+            return new Config(getGlobalLong(NETSTATS_UID_TAG_BUCKET_DURATION, 2 * HOUR_IN_MILLIS),
+                    getGlobalLong(NETSTATS_UID_TAG_ROTATE_AGE, 5 * DAY_IN_MILLIS),
+                    getGlobalLong(NETSTATS_UID_TAG_DELETE_AGE, 15 * DAY_IN_MILLIS));
         }
         @Override
         public long getDevPersistBytes(long def) {
-            return getSecureLong(NETSTATS_DEV_PERSIST_BYTES, def);
+            return getGlobalLong(NETSTATS_DEV_PERSIST_BYTES, def);
         }
         @Override
         public long getXtPersistBytes(long def) {
@@ -1276,11 +1333,11 @@
         }
         @Override
         public long getUidPersistBytes(long def) {
-            return getSecureLong(NETSTATS_UID_PERSIST_BYTES, def);
+            return getGlobalLong(NETSTATS_UID_PERSIST_BYTES, def);
         }
         @Override
         public long getUidTagPersistBytes(long def) {
-            return getSecureLong(NETSTATS_UID_TAG_PERSIST_BYTES, def);
+            return getGlobalLong(NETSTATS_UID_TAG_PERSIST_BYTES, def);
         }
     }
 }