diff options
Diffstat (limited to 'services/java/com/android/server/net')
| -rw-r--r-- | services/java/com/android/server/net/NetworkPolicyManagerService.java | 382 | ||||
| -rw-r--r-- | services/java/com/android/server/net/NetworkStatsService.java | 403 |
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; + } + +} |
