Handle removed UIDs in network stats and policy.

When UID_REMOVED, clean up any existing UID network policy so it
doesn't linger for future apps.  Also move any NetworkStatsHistory
to special UID_REMOVED tracking bucket.

Tests for new removal code.  Also test detailed UID stats, including
network changes to verify template matching logic.

Bug: 4584212
Change-Id: I9faadf6b6f3830eb45d86c7f1980a27cdbcdb11e
diff --git a/core/java/android/net/NetworkIdentity.java b/core/java/android/net/NetworkIdentity.java
index f82d922..23ebbab 100644
--- a/core/java/android/net/NetworkIdentity.java
+++ b/core/java/android/net/NetworkIdentity.java
@@ -95,9 +95,13 @@
 
         final String subscriberId;
         if (isNetworkTypeMobile(type)) {
-            final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
-                    Context.TELEPHONY_SERVICE);
-            subscriberId = telephony.getSubscriberId();
+            if (state.subscriberId != null) {
+                subscriberId = state.subscriberId;
+            } else {
+                final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
+                        Context.TELEPHONY_SERVICE);
+                subscriberId = telephony.getSubscriberId();
+            }
         } else {
             subscriberId = null;
         }
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index e163abf..cb47193 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -42,6 +42,14 @@
     public final static int UNSUPPORTED = -1;
 
     /**
+     * Special UID value used when collecting {@link NetworkStatsHistory} for
+     * removed applications.
+     *
+     * @hide
+     */
+    public static final int UID_REMOVED = -4;
+
+    /**
      * Snapshot of {@link NetworkStats} when the currently active profiling
      * session started, or {@code null} if no session active.
      *
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index 54a806a..79612e3 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -19,11 +19,14 @@
 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
 import static android.Manifest.permission.DUMP;
 import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
-import static android.Manifest.permission.SHUTDOWN;
+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;
 import static android.net.NetworkStats.IFACE_ALL;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
+import static android.net.TrafficStats.UID_REMOVED;
 import static android.provider.Settings.Secure.NETSTATS_NETWORK_BUCKET_DURATION;
 import static android.provider.Settings.Secure.NETSTATS_NETWORK_MAX_HISTORY;
 import static android.provider.Settings.Secure.NETSTATS_PERSIST_THRESHOLD;
@@ -206,17 +209,20 @@
         }
 
         // watch for network interfaces to be claimed
-        final IntentFilter ifaceFilter = new IntentFilter();
-        ifaceFilter.addAction(CONNECTIVITY_ACTION);
-        mContext.registerReceiver(mIfaceReceiver, ifaceFilter, CONNECTIVITY_INTERNAL, mHandler);
+        final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION);
+        mContext.registerReceiver(mConnReceiver, connFilter, 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);
 
+        // listen for uid removal to clean stats
+        final IntentFilter removedFilter = new IntentFilter(ACTION_UID_REMOVED);
+        mContext.registerReceiver(mRemovedReceiver, removedFilter, null, mHandler);
+
         // persist stats during clean shutdown
-        final IntentFilter shutdownFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
-        mContext.registerReceiver(mShutdownReceiver, shutdownFilter, SHUTDOWN, null);
+        final IntentFilter shutdownFilter = new IntentFilter(ACTION_SHUTDOWN);
+        mContext.registerReceiver(mShutdownReceiver, shutdownFilter);
 
         try {
             registerPollAlarmLocked();
@@ -226,8 +232,9 @@
     }
 
     private void shutdownLocked() {
-        mContext.unregisterReceiver(mIfaceReceiver);
+        mContext.unregisterReceiver(mConnReceiver);
         mContext.unregisterReceiver(mPollReceiver);
+        mContext.unregisterReceiver(mRemovedReceiver);
         mContext.unregisterReceiver(mShutdownReceiver);
 
         writeNetworkStatsLocked();
@@ -352,7 +359,7 @@
      * interfaces. Used to associate {@link TelephonyManager#getSubscriberId()}
      * with mobile interfaces.
      */
-    private BroadcastReceiver mIfaceReceiver = new BroadcastReceiver() {
+    private BroadcastReceiver mConnReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             // on background handler thread, and verified CONNECTIVITY_INTERNAL
@@ -375,10 +382,22 @@
         }
     };
 
+    private BroadcastReceiver mRemovedReceiver = new BroadcastReceiver() {
+        @Override
+        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);
+            synchronized (mStatsLock) {
+                removeUidLocked(uid);
+            }
+        }
+    };
+
     private BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            // verified SHUTDOWN permission above.
+            // SHUTDOWN is protected broadcast.
             synchronized (mStatsLock) {
                 shutdownLocked();
             }
@@ -545,6 +564,31 @@
         mLastUidPoll = uidStats;
     }
 
+    /**
+     * Clean up {@link #mUidStats} after UID is removed.
+     */
+    private void removeUidLocked(int uid) {
+        ensureUidStatsLoadedLocked();
+
+        // migrate all UID stats into special "removed" bucket
+        for (NetworkIdentitySet ident : mUidStats.keySet()) {
+            final SparseArray<NetworkStatsHistory> uidStats = mUidStats.get(ident);
+            final NetworkStatsHistory uidHistory = uidStats.get(uid);
+            if (uidHistory != null) {
+                final NetworkStatsHistory removedHistory = findOrCreateUidStatsLocked(
+                        ident, UID_REMOVED);
+                removedHistory.recordEntireHistory(uidHistory);
+                uidStats.remove(uid);
+            }
+        }
+
+        // TODO: push kernel event to wipe stats for UID, otherwise we risk
+        // picking them up again during next poll.
+
+        // since this was radical rewrite, push to disk
+        writeUidStatsLocked();
+    }
+
     private NetworkStatsHistory findOrCreateNetworkStatsLocked(NetworkIdentitySet ident) {
         final long bucketDuration = mSettings.getNetworkBucketDuration();
         final NetworkStatsHistory existing = mNetworkStats.get(ident);
@@ -568,6 +612,8 @@
     }
 
     private NetworkStatsHistory findOrCreateUidStatsLocked(NetworkIdentitySet ident, int uid) {
+        ensureUidStatsLoadedLocked();
+
         // find bucket for identity first, then find uid
         SparseArray<NetworkStatsHistory> uidStats = mUidStats.get(ident);
         if (uidStats == null) {
@@ -734,6 +780,11 @@
     private void writeUidStatsLocked() {
         if (LOGV) Slog.v(TAG, "writeUidStatsLocked()");
 
+        if (!mUidStatsLoaded) {
+            Slog.w(TAG, "asked to write UID stats when not loaded; skipping");
+            return;
+        }
+
         // TODO: consider duplicating stats and releasing lock while writing
 
         FileOutputStream fos = null;