summaryrefslogtreecommitdiffstats
path: root/services/java/com/android/server/net
diff options
context:
space:
mode:
Diffstat (limited to 'services/java/com/android/server/net')
-rw-r--r--services/java/com/android/server/net/NetworkPolicyManagerService.java382
-rw-r--r--services/java/com/android/server/net/NetworkStatsService.java403
2 files changed, 785 insertions, 0 deletions
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
new file mode 100644
index 0000000..17c7161
--- /dev/null
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import static android.Manifest.permission.DUMP;
+import static android.Manifest.permission.MANAGE_APP_TOKENS;
+import static android.Manifest.permission.UPDATE_DEVICE_STATS;
+import static android.net.NetworkPolicyManager.POLICY_NONE;
+import static android.net.NetworkPolicyManager.POLICY_REJECT_PAID_BACKGROUND;
+import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
+import static android.net.NetworkPolicyManager.RULE_REJECT_PAID;
+import static android.net.NetworkPolicyManager.dumpPolicy;
+import static android.net.NetworkPolicyManager.dumpRules;
+
+import android.app.IActivityManager;
+import android.app.IProcessObserver;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.INetworkPolicyListener;
+import android.net.INetworkPolicyManager;
+import android.net.INetworkStatsService;
+import android.os.IPowerManager;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Service that maintains low-level network policy rules and collects usage
+ * statistics to drive those rules.
+ * <p>
+ * Derives active rules by combining a given policy with other system status,
+ * and delivers to listeners, such as {@link ConnectivityManager}, for
+ * enforcement.
+ */
+public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
+ private static final String TAG = "NetworkPolicy";
+ private static final boolean LOGD = true;
+
+ private final Context mContext;
+ private final IActivityManager mActivityManager;
+ private final IPowerManager mPowerManager;
+ private final INetworkStatsService mNetworkStats;
+
+ private final Object mRulesLock = new Object();
+
+ private boolean mScreenOn;
+
+ /** Current network policy for each UID. */
+ private SparseIntArray mUidPolicy = new SparseIntArray();
+ /** Current derived network rules for each UID. */
+ private SparseIntArray mUidRules = new SparseIntArray();
+
+ /** Foreground at both UID and PID granularity. */
+ private SparseBooleanArray mUidForeground = new SparseBooleanArray();
+ private SparseArray<SparseBooleanArray> mUidPidForeground = new SparseArray<
+ SparseBooleanArray>();
+
+ private final RemoteCallbackList<INetworkPolicyListener> mListeners = new RemoteCallbackList<
+ INetworkPolicyListener>();
+
+ // TODO: save/restore policy information from disk
+
+ // TODO: keep whitelist of system-critical services that should never have
+ // rules enforced, such as system, phone, and radio UIDs.
+
+ // TODO: keep record of billing cycle details, and limit rules
+ // TODO: keep map of interfaces-to-billing-relationship
+
+ public NetworkPolicyManagerService(Context context, IActivityManager activityManager,
+ IPowerManager powerManager, INetworkStatsService networkStats) {
+ mContext = checkNotNull(context, "missing context");
+ mActivityManager = checkNotNull(activityManager, "missing activityManager");
+ mPowerManager = checkNotNull(powerManager, "missing powerManager");
+ mNetworkStats = checkNotNull(networkStats, "missing networkStats");
+ }
+
+ public void systemReady() {
+ // TODO: read current policy from disk
+
+ updateScreenOn();
+
+ try {
+ mActivityManager.registerProcessObserver(mProcessObserver);
+ } catch (RemoteException e) {
+ // ouch, no foregroundActivities updates means some processes may
+ // never get network access.
+ Slog.e(TAG, "unable to register IProcessObserver", e);
+ }
+
+ // TODO: traverse existing processes to know foreground state, or have
+ // activitymanager dispatch current state when new observer attached.
+
+ final IntentFilter screenFilter = new IntentFilter();
+ screenFilter.addAction(Intent.ACTION_SCREEN_ON);
+ screenFilter.addAction(Intent.ACTION_SCREEN_OFF);
+ mContext.registerReceiver(mScreenReceiver, screenFilter);
+
+ }
+
+ private IProcessObserver mProcessObserver = new IProcessObserver.Stub() {
+ @Override
+ public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
+ // only someone like AMS should only be calling us
+ mContext.enforceCallingOrSelfPermission(MANAGE_APP_TOKENS, TAG);
+
+ synchronized (mRulesLock) {
+ // because a uid can have multiple pids running inside, we need to
+ // remember all pid states and summarize foreground at uid level.
+
+ // record foreground for this specific pid
+ SparseBooleanArray pidForeground = mUidPidForeground.get(uid);
+ if (pidForeground == null) {
+ pidForeground = new SparseBooleanArray(2);
+ mUidPidForeground.put(uid, pidForeground);
+ }
+ pidForeground.put(pid, foregroundActivities);
+ computeUidForegroundL(uid);
+ }
+ }
+
+ @Override
+ public void onProcessDied(int pid, int uid) {
+ // only someone like AMS should only be calling us
+ mContext.enforceCallingOrSelfPermission(MANAGE_APP_TOKENS, TAG);
+
+ synchronized (mRulesLock) {
+ // clear records and recompute, when they exist
+ final SparseBooleanArray pidForeground = mUidPidForeground.get(uid);
+ if (pidForeground != null) {
+ pidForeground.delete(pid);
+ computeUidForegroundL(uid);
+ }
+ }
+ }
+ };
+
+ private BroadcastReceiver mScreenReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ synchronized (mRulesLock) {
+ // screen-related broadcasts are protected by system, no need
+ // for permissions check.
+ updateScreenOn();
+ }
+ }
+ };
+
+ @Override
+ public void setUidPolicy(int uid, int policy) {
+ // TODO: create permission for modifying data policy
+ mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG);
+
+ final int oldPolicy;
+ synchronized (mRulesLock) {
+ oldPolicy = getUidPolicy(uid);
+ mUidPolicy.put(uid, policy);
+ updateRulesForUidL(uid);
+ }
+
+ // TODO: consider dispatching BACKGROUND_DATA_SETTING broadcast
+ }
+
+ @Override
+ public int getUidPolicy(int uid) {
+ synchronized (mRulesLock) {
+ return mUidPolicy.get(uid, POLICY_NONE);
+ }
+ }
+
+ @Override
+ public void registerListener(INetworkPolicyListener listener) {
+ mListeners.register(listener);
+
+ synchronized (mRulesLock) {
+ // dispatch any existing rules to new listeners
+ final int size = mUidRules.size();
+ for (int i = 0; i < size; i++) {
+ final int uid = mUidRules.keyAt(i);
+ final int uidRules = mUidRules.valueAt(i);
+ if (uidRules != RULE_ALLOW_ALL) {
+ try {
+ listener.onRulesChanged(uid, uidRules);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void unregisterListener(INetworkPolicyListener listener) {
+ mListeners.unregister(listener);
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+ mContext.enforceCallingOrSelfPermission(DUMP, TAG);
+
+ synchronized (mRulesLock) {
+ fout.println("Policy status for known UIDs:");
+
+ final SparseBooleanArray knownUids = new SparseBooleanArray();
+ collectKeys(mUidPolicy, knownUids);
+ collectKeys(mUidForeground, knownUids);
+ collectKeys(mUidRules, knownUids);
+
+ final int size = knownUids.size();
+ for (int i = 0; i < size; i++) {
+ final int uid = knownUids.keyAt(i);
+ fout.print(" UID=");
+ fout.print(uid);
+
+ fout.print(" policy=");
+ final int policyIndex = mUidPolicy.indexOfKey(uid);
+ if (policyIndex < 0) {
+ fout.print("UNKNOWN");
+ } else {
+ dumpPolicy(fout, mUidPolicy.valueAt(policyIndex));
+ }
+
+ fout.print(" foreground=");
+ final int foregroundIndex = mUidPidForeground.indexOfKey(uid);
+ if (foregroundIndex < 0) {
+ fout.print("UNKNOWN");
+ } else {
+ dumpSparseBooleanArray(fout, mUidPidForeground.valueAt(foregroundIndex));
+ }
+
+ fout.print(" rules=");
+ final int rulesIndex = mUidRules.indexOfKey(uid);
+ if (rulesIndex < 0) {
+ fout.print("UNKNOWN");
+ } else {
+ dumpRules(fout, mUidRules.valueAt(rulesIndex));
+ }
+
+ fout.println();
+ }
+ }
+ }
+
+ @Override
+ public boolean isUidForeground(int uid) {
+ synchronized (mRulesLock) {
+ // only really in foreground when screen is also on
+ return mUidForeground.get(uid, false) && mScreenOn;
+ }
+ }
+
+ /**
+ * Foreground for PID changed; recompute foreground at UID level. If
+ * changed, will trigger {@link #updateRulesForUidL(int)}.
+ */
+ private void computeUidForegroundL(int uid) {
+ final SparseBooleanArray pidForeground = mUidPidForeground.get(uid);
+
+ // current pid is dropping foreground; examine other pids
+ boolean uidForeground = false;
+ final int size = pidForeground.size();
+ for (int i = 0; i < size; i++) {
+ if (pidForeground.valueAt(i)) {
+ uidForeground = true;
+ break;
+ }
+ }
+
+ final boolean oldUidForeground = mUidForeground.get(uid, false);
+ if (oldUidForeground != uidForeground) {
+ // foreground changed, push updated rules
+ mUidForeground.put(uid, uidForeground);
+ updateRulesForUidL(uid);
+ }
+ }
+
+ private void updateScreenOn() {
+ synchronized (mRulesLock) {
+ try {
+ mScreenOn = mPowerManager.isScreenOn();
+ } catch (RemoteException e) {
+ }
+ updateRulesForScreenL();
+ }
+ }
+
+ /**
+ * Update rules that might be changed by {@link #mScreenOn} value.
+ */
+ private void updateRulesForScreenL() {
+ // only update rules for anyone with foreground activities
+ final int size = mUidForeground.size();
+ for (int i = 0; i < size; i++) {
+ if (mUidForeground.valueAt(i)) {
+ final int uid = mUidForeground.keyAt(i);
+ updateRulesForUidL(uid);
+ }
+ }
+ }
+
+ private void updateRulesForUidL(int uid) {
+ final int uidPolicy = getUidPolicy(uid);
+ final boolean uidForeground = isUidForeground(uid);
+
+ // derive active rules based on policy and active state
+ int uidRules = RULE_ALLOW_ALL;
+ if (!uidForeground && (uidPolicy & POLICY_REJECT_PAID_BACKGROUND) != 0) {
+ // uid in background, and policy says to block paid data
+ uidRules = RULE_REJECT_PAID;
+ }
+
+ // TODO: only dispatch when rules actually change
+
+ // record rule locally to dispatch to new listeners
+ mUidRules.put(uid, uidRules);
+
+ // dispatch changed rule to existing listeners
+ final int length = mListeners.beginBroadcast();
+ for (int i = 0; i < length; i++) {
+ final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
+ if (listener != null) {
+ try {
+ listener.onRulesChanged(uid, uidRules);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ mListeners.finishBroadcast();
+ }
+
+ private static <T> T checkNotNull(T value, String message) {
+ if (value == null) {
+ throw new NullPointerException(message);
+ }
+ return value;
+ }
+
+ private static void collectKeys(SparseIntArray source, SparseBooleanArray target) {
+ final int size = source.size();
+ for (int i = 0; i < size; i++) {
+ target.put(source.keyAt(i), true);
+ }
+ }
+
+ private static void collectKeys(SparseBooleanArray source, SparseBooleanArray target) {
+ final int size = source.size();
+ for (int i = 0; i < size; i++) {
+ target.put(source.keyAt(i), true);
+ }
+ }
+
+ private static void dumpSparseBooleanArray(PrintWriter fout, SparseBooleanArray value) {
+ fout.print("[");
+ final int size = value.size();
+ for (int i = 0; i < size; i++) {
+ fout.print(value.keyAt(i) + "=" + value.valueAt(i));
+ if (i < size - 1) fout.print(",");
+ }
+ fout.print("]");
+ }
+}
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
new file mode 100644
index 0000000..d9c1f25
--- /dev/null
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import static android.Manifest.permission.DUMP;
+import static android.Manifest.permission.SHUTDOWN;
+import static android.Manifest.permission.UPDATE_DEVICE_STATS;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.NetworkStats.UID_ALL;
+
+import android.app.AlarmManager;
+import android.app.IAlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.INetworkStatsService;
+import android.net.LinkProperties;
+import android.net.NetworkStats;
+import android.net.NetworkStatsHistory;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.telephony.TelephonyManager;
+import android.text.format.DateUtils;
+import android.util.NtpTrustedTime;
+import android.util.Slog;
+import android.util.TrustedTime;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.TelephonyIntents;
+import com.google.android.collect.Maps;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashMap;
+
+/**
+ * Collect and persist detailed network statistics, and provide this data to
+ * other system services.
+ */
+public class NetworkStatsService extends INetworkStatsService.Stub {
+ private static final String TAG = "NetworkStatsService";
+ private static final boolean LOGD = true;
+
+ private final Context mContext;
+ private final INetworkManagementService mNetworkManager;
+ private final IAlarmManager mAlarmManager;
+ private final TrustedTime mTime;
+
+ private static final String ACTION_NETWORK_STATS_POLL =
+ "com.android.server.action.NETWORK_STATS_POLL";
+
+ private PendingIntent mPollIntent;
+
+ // TODO: move tweakable params to Settings.Secure
+ // TODO: listen for kernel push events through netd instead of polling
+
+ private static final long KB_IN_BYTES = 1024;
+
+ private static final long POLL_INTERVAL = AlarmManager.INTERVAL_FIFTEEN_MINUTES;
+ private static final long SUMMARY_BUCKET_DURATION = 6 * DateUtils.HOUR_IN_MILLIS;
+ private static final long SUMMARY_MAX_HISTORY = 90 * DateUtils.DAY_IN_MILLIS;
+
+ // TODO: remove these high-frequency testing values
+// private static final long POLL_INTERVAL = 5 * DateUtils.SECOND_IN_MILLIS;
+// private static final long SUMMARY_BUCKET_DURATION = 10 * DateUtils.SECOND_IN_MILLIS;
+// private static final long SUMMARY_MAX_HISTORY = 2 * DateUtils.MINUTE_IN_MILLIS;
+
+ /** Minimum delta required to persist to disk. */
+ private static final long SUMMARY_PERSIST_THRESHOLD = 64 * KB_IN_BYTES;
+
+ private static final long TIME_CACHE_MAX_AGE = DateUtils.DAY_IN_MILLIS;
+
+ private final Object mStatsLock = new Object();
+
+ /** Set of active ifaces during this boot. */
+ private HashMap<String, InterfaceInfo> mActiveIface = Maps.newHashMap();
+ /** Set of historical stats for known ifaces. */
+ private HashMap<InterfaceInfo, NetworkStatsHistory> mIfaceStats = Maps.newHashMap();
+
+ private NetworkStats mLastPollStats;
+ private NetworkStats mLastPersistStats;
+
+ private final HandlerThread mHandlerThread;
+ private final Handler mHandler;
+
+ // TODO: collect detailed uid stats, storing tag-granularity data until next
+ // dropbox, and uid summary for a specific bucket count.
+
+ // TODO: periodically compile statistics and send to dropbox.
+
+ public NetworkStatsService(
+ Context context, INetworkManagementService networkManager, IAlarmManager alarmManager) {
+ // TODO: move to using cached NtpTrustedTime
+ this(context, networkManager, alarmManager, new NtpTrustedTime());
+ }
+
+ public NetworkStatsService(Context context, INetworkManagementService networkManager,
+ IAlarmManager alarmManager, TrustedTime time) {
+ mContext = checkNotNull(context, "missing Context");
+ mNetworkManager = checkNotNull(networkManager, "missing INetworkManagementService");
+ mAlarmManager = checkNotNull(alarmManager, "missing IAlarmManager");
+ mTime = checkNotNull(time, "missing TrustedTime");
+
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ }
+
+ public void systemReady() {
+ // read historical stats from disk
+ readStatsLocked();
+
+ // watch other system services that claim interfaces
+ // TODO: protect incoming broadcast with permissions check.
+ // TODO: consider migrating this to ConnectivityService, but it might
+ // cause a circular dependency.
+ final IntentFilter interfaceFilter = new IntentFilter();
+ interfaceFilter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
+ interfaceFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ mContext.registerReceiver(mInterfaceReceiver, interfaceFilter);
+
+ // listen for periodic polling events
+ final IntentFilter pollFilter = new IntentFilter(ACTION_NETWORK_STATS_POLL);
+ mContext.registerReceiver(mPollReceiver, pollFilter, UPDATE_DEVICE_STATS, mHandler);
+
+ // persist stats during clean shutdown
+ final IntentFilter shutdownFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
+ mContext.registerReceiver(mShutdownReceiver, shutdownFilter, SHUTDOWN, null);
+
+ try {
+ registerPollAlarmLocked();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "unable to register poll alarm");
+ }
+ }
+
+ /**
+ * Clear any existing {@link #ACTION_NETWORK_STATS_POLL} alarms, and
+ * reschedule based on current {@link #POLL_INTERVAL} value.
+ */
+ private void registerPollAlarmLocked() throws RemoteException {
+ if (mPollIntent != null) {
+ mAlarmManager.remove(mPollIntent);
+ }
+
+ mPollIntent = PendingIntent.getBroadcast(
+ mContext, 0, new Intent(ACTION_NETWORK_STATS_POLL), 0);
+
+ final long currentRealtime = SystemClock.elapsedRealtime();
+ mAlarmManager.setInexactRepeating(
+ AlarmManager.ELAPSED_REALTIME, currentRealtime, POLL_INTERVAL, mPollIntent);
+ }
+
+ @Override
+ public NetworkStatsHistory[] getNetworkStatsSummary(int networkType) {
+ // TODO: return history for requested types
+ return null;
+ }
+
+ @Override
+ public NetworkStatsHistory getNetworkStatsUid(int uid) {
+ // TODO: return history for requested uid
+ return null;
+ }
+
+ /**
+ * Receiver that watches for other system components that claim network
+ * interfaces. Used to associate {@link TelephonyManager#getSubscriberId()}
+ * with mobile interfaces.
+ */
+ private BroadcastReceiver mInterfaceReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED.equals(action)) {
+ final LinkProperties prop = intent.getParcelableExtra(
+ Phone.DATA_LINK_PROPERTIES_KEY);
+ final String iface = prop != null ? prop.getInterfaceName() : null;
+ if (iface != null) {
+ final TelephonyManager teleManager = (TelephonyManager) context
+ .getSystemService(Context.TELEPHONY_SERVICE);
+ final InterfaceInfo info = new InterfaceInfo(
+ iface, TYPE_MOBILE, teleManager.getSubscriberId());
+ reportActiveInterface(info);
+ }
+ } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
+ final LinkProperties prop = intent.getParcelableExtra(
+ WifiManager.EXTRA_LINK_PROPERTIES);
+ final String iface = prop != null ? prop.getInterfaceName() : null;
+ if (iface != null) {
+ final InterfaceInfo info = new InterfaceInfo(iface, TYPE_WIFI, null);
+ reportActiveInterface(info);
+ }
+ }
+ }
+ };
+
+ private BroadcastReceiver mPollReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // already running on background handler, network/io is safe, and
+ // caller verified to have UPDATE_DEVICE_STATS permission above.
+ synchronized (mStatsLock) {
+ // TODO: acquire wakelock while performing poll
+ performPollLocked();
+ }
+ }
+ };
+
+ private BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // persist stats during clean shutdown
+ synchronized (mStatsLock) {
+ writeStatsLocked();
+ }
+ }
+ };
+
+ private void performPollLocked() {
+ if (LOGD) Slog.v(TAG, "performPollLocked()");
+
+ // try refreshing time source when stale
+ if (mTime.getCacheAge() > TIME_CACHE_MAX_AGE) {
+ mTime.forceRefresh();
+ }
+
+ // TODO: consider marking "untrusted" times in historical stats
+ final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
+ : System.currentTimeMillis();
+
+ final NetworkStats current;
+ try {
+ current = mNetworkManager.getNetworkStatsSummary();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "problem reading network stats");
+ return;
+ }
+
+ // update historical usage with delta since last poll
+ final NetworkStats pollDelta = computeStatsDelta(mLastPollStats, current);
+ final long timeStart = currentTime - pollDelta.elapsedRealtime;
+ for (String iface : pollDelta.getKnownIfaces()) {
+ final InterfaceInfo info = mActiveIface.get(iface);
+ if (info == null) {
+ if (LOGD) Slog.w(TAG, "unknown interface " + iface + ", ignoring stats");
+ continue;
+ }
+
+ final int index = pollDelta.findIndex(iface, UID_ALL);
+ final long rx = pollDelta.rx[index];
+ final long tx = pollDelta.tx[index];
+
+ final NetworkStatsHistory history = findOrCreateHistoryLocked(info);
+ history.recordData(timeStart, currentTime, rx, tx);
+ history.removeBucketsBefore(currentTime - SUMMARY_MAX_HISTORY);
+ }
+
+ mLastPollStats = current;
+
+ // decide if enough has changed to trigger persist
+ final NetworkStats persistDelta = computeStatsDelta(mLastPersistStats, current);
+ for (String iface : persistDelta.getKnownIfaces()) {
+ final int index = persistDelta.findIndex(iface, UID_ALL);
+ if (persistDelta.rx[index] > SUMMARY_PERSIST_THRESHOLD
+ || persistDelta.tx[index] > SUMMARY_PERSIST_THRESHOLD) {
+ writeStatsLocked();
+ mLastPersistStats = current;
+ break;
+ }
+ }
+ }
+
+ private NetworkStatsHistory findOrCreateHistoryLocked(InterfaceInfo info) {
+ NetworkStatsHistory stats = mIfaceStats.get(info);
+ if (stats == null) {
+ stats = new NetworkStatsHistory(
+ info.networkType, info.identity, UID_ALL, SUMMARY_BUCKET_DURATION);
+ mIfaceStats.put(info, stats);
+ }
+ return stats;
+ }
+
+ private void readStatsLocked() {
+ if (LOGD) Slog.v(TAG, "readStatsLocked()");
+ // TODO: read historical stats from disk using AtomicFile
+ }
+
+ private void writeStatsLocked() {
+ if (LOGD) Slog.v(TAG, "writeStatsLocked()");
+ // TODO: persist historical stats to disk using AtomicFile
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mContext.enforceCallingOrSelfPermission(DUMP, TAG);
+
+ pw.println("Active interfaces:");
+ for (InterfaceInfo info : mActiveIface.values()) {
+ info.dump(" ", pw);
+ }
+
+ pw.println("Known historical stats:");
+ for (NetworkStatsHistory stats : mIfaceStats.values()) {
+ stats.dump(" ", pw);
+ }
+ }
+
+ /**
+ * Details for a well-known network interface, including its name, network
+ * type, and billing relationship identity (such as IMSI).
+ */
+ private static class InterfaceInfo {
+ public final String iface;
+ public final int networkType;
+ public final String identity;
+
+ public InterfaceInfo(String iface, int networkType, String identity) {
+ this.iface = iface;
+ this.networkType = networkType;
+ this.identity = identity;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((identity == null) ? 0 : identity.hashCode());
+ result = prime * result + ((iface == null) ? 0 : iface.hashCode());
+ result = prime * result + networkType;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof InterfaceInfo) {
+ final InterfaceInfo info = (InterfaceInfo) obj;
+ return equal(iface, info.iface) && networkType == info.networkType
+ && equal(identity, info.identity);
+ }
+ return false;
+ }
+
+ public void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix);
+ pw.print("InterfaceInfo: iface="); pw.print(iface);
+ pw.print(" networkType="); pw.print(networkType);
+ pw.print(" identity="); pw.println(identity);
+ }
+ }
+
+ private void reportActiveInterface(InterfaceInfo info) {
+ synchronized (mStatsLock) {
+ // TODO: when interface redefined, port over historical stats
+ mActiveIface.put(info.iface, info);
+ }
+ }
+
+ /**
+ * Return the delta between two {@link NetworkStats} snapshots, where {@code
+ * before} can be {@code null}.
+ */
+ private static NetworkStats computeStatsDelta(NetworkStats before, NetworkStats current) {
+ if (before != null) {
+ return current.subtract(before, false);
+ } else {
+ return current;
+ }
+ }
+
+ private static boolean equal(Object a, Object b) {
+ return a == b || (a != null && a.equals(b));
+ }
+
+ private static <T> T checkNotNull(T value, String message) {
+ if (value == null) {
+ throw new NullPointerException(message);
+ }
+ return value;
+ }
+
+}