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;