diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl index 6d57036fbd..d38d16c4e7 100644 --- a/core/java/android/net/INetworkStatsService.aidl +++ b/core/java/android/net/INetworkStatsService.aidl @@ -16,12 +16,18 @@ package android.net; +import android.net.NetworkStats; import android.net.NetworkStatsHistory; /** {@hide} */ interface INetworkStatsService { - NetworkStatsHistory[] getNetworkStatsSummary(int networkType); - NetworkStatsHistory getNetworkStatsUid(int uid); + /** Return historical stats for traffic that matches template. */ + NetworkStatsHistory getHistoryForNetwork(int networkTemplate); + /** Return historical stats for specific UID traffic that matches template. */ + NetworkStatsHistory getHistoryForUid(int uid, int networkTemplate); + + /** Return usage summary per UID for traffic that matches template. */ + NetworkStats getSummaryPerUid(long start, long end, int networkTemplate); } diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java index b16101fae6..60af475de2 100644 --- a/core/java/android/net/NetworkStatsHistory.java +++ b/core/java/android/net/NetworkStatsHistory.java @@ -40,26 +40,17 @@ import java.util.Arrays; public class NetworkStatsHistory implements Parcelable { private static final int VERSION = 1; - /** {@link #uid} value when UID details unavailable. */ - public static final int UID_ALL = -1; - // TODO: teach about zigzag encoding to use less disk space // TODO: teach how to convert between bucket sizes - public final int networkType; - public final String identity; - public final int uid; public final long bucketDuration; - int bucketCount; - long[] bucketStart; - long[] rx; - long[] tx; + public int bucketCount; + public long[] bucketStart; + public long[] rx; + public long[] tx; - public NetworkStatsHistory(int networkType, String identity, int uid, long bucketDuration) { - this.networkType = networkType; - this.identity = identity; - this.uid = uid; + public NetworkStatsHistory(long bucketDuration) { this.bucketDuration = bucketDuration; bucketStart = new long[0]; rx = new long[0]; @@ -68,9 +59,6 @@ public class NetworkStatsHistory implements Parcelable { } public NetworkStatsHistory(Parcel in) { - networkType = in.readInt(); - identity = in.readString(); - uid = in.readInt(); bucketDuration = in.readLong(); bucketStart = readLongArray(in); rx = in.createLongArray(); @@ -80,9 +68,6 @@ public class NetworkStatsHistory implements Parcelable { /** {@inheritDoc} */ public void writeToParcel(Parcel out, int flags) { - out.writeInt(networkType); - out.writeString(identity); - out.writeInt(uid); out.writeLong(bucketDuration); writeLongArray(out, bucketStart, bucketCount); writeLongArray(out, rx, bucketCount); @@ -91,9 +76,6 @@ public class NetworkStatsHistory implements Parcelable { public NetworkStatsHistory(DataInputStream in) throws IOException { final int version = in.readInt(); - networkType = in.readInt(); - identity = in.readUTF(); - uid = in.readInt(); bucketDuration = in.readLong(); bucketStart = readLongArray(in); rx = readLongArray(in); @@ -103,9 +85,6 @@ public class NetworkStatsHistory implements Parcelable { public void writeToStream(DataOutputStream out) throws IOException { out.writeInt(VERSION); - out.writeInt(networkType); - out.writeUTF(identity); - out.writeInt(uid); out.writeLong(bucketDuration); writeLongArray(out, bucketStart, bucketCount); writeLongArray(out, rx, bucketCount); @@ -214,11 +193,8 @@ public class NetworkStatsHistory implements Parcelable { } public void dump(String prefix, PrintWriter pw) { - // TODO: consider stripping identity when dumping pw.print(prefix); - pw.print("NetworkStatsHistory: networkType="); pw.print(networkType); - pw.print(" identity="); pw.print(identity); - pw.print(" uid="); pw.println(uid); + pw.println("NetworkStatsHistory:"); for (int i = 0; i < bucketCount; i++) { pw.print(prefix); pw.print(" timestamp="); pw.print(bucketStart[i]); diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java index 8ab64fabd2..a0738c1da2 100644 --- a/core/java/android/net/TrafficStats.java +++ b/core/java/android/net/TrafficStats.java @@ -41,6 +41,42 @@ public class TrafficStats { */ public final static int UNSUPPORTED = -1; + // TODO: find better home for these template constants + + /** + * Template to combine all {@link ConnectivityManager#TYPE_MOBILE} style + * networks together. Only uses statistics for currently active IMSI. + * + * @hide + */ + public static final int TEMPLATE_MOBILE_ALL = 1; + + /** + * Template to combine all {@link ConnectivityManager#TYPE_MOBILE} style + * networks together that roughly meet a "3G" definition, or lower. Only + * uses statistics for currently active IMSI. + * + * @hide + */ + public static final int TEMPLATE_MOBILE_3G_LOWER = 2; + + /** + * Template to combine all {@link ConnectivityManager#TYPE_MOBILE} style + * networks together that meet a "4G" definition. Only uses statistics for + * currently active IMSI. + * + * @hide + */ + public static final int TEMPLATE_MOBILE_4G = 3; + + /** + * Template to combine all {@link ConnectivityManager#TYPE_WIFI} style + * networks together. + * + * @hide + */ + public static final int TEMPLATE_WIFI = 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/InterfaceIdentity.java b/services/java/com/android/server/net/InterfaceIdentity.java new file mode 100644 index 0000000000..ff86581eff --- /dev/null +++ b/services/java/com/android/server/net/InterfaceIdentity.java @@ -0,0 +1,73 @@ +/* + * 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 java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.ProtocolException; +import java.util.HashSet; + +/** + * Identity of a {@code iface}, defined by the set of {@link NetworkIdentity} + * active on that interface. + * + * @hide + */ +public class InterfaceIdentity extends HashSet { + private static final int VERSION_CURRENT = 1; + + public InterfaceIdentity() { + } + + public InterfaceIdentity(DataInputStream in) throws IOException { + final int version = in.readInt(); + switch (version) { + case VERSION_CURRENT: { + final int size = in.readInt(); + for (int i = 0; i < size; i++) { + add(new NetworkIdentity(in)); + } + break; + } + default: { + throw new ProtocolException("unexpected version: " + version); + } + } + } + + public void writeToStream(DataOutputStream out) throws IOException { + out.writeInt(VERSION_CURRENT); + out.writeInt(size()); + for (NetworkIdentity ident : this) { + ident.writeToStream(out); + } + } + + /** + * Test if any {@link NetworkIdentity} on this interface matches the given + * template and IMEI. + */ + public boolean matchesTemplate(int networkTemplate, String subscriberId) { + for (NetworkIdentity ident : this) { + if (ident.matchesTemplate(networkTemplate, subscriberId)) { + return true; + } + } + return false; + } +} diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java index d9c1f25b72..66d127447a 100644 --- a/services/java/com/android/server/net/NetworkStatsService.java +++ b/services/java/com/android/server/net/NetworkStatsService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * 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. @@ -16,12 +16,13 @@ package com.android.server.net; +import static android.Manifest.permission.CONNECTIVITY_INTERNAL; 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.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.NetworkStats.UID_ALL; +import static com.android.internal.util.Preconditions.checkNotNull; import android.app.AlarmManager; import android.app.IAlarmManager; @@ -30,11 +31,12 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.net.IConnectivityManager; import android.net.INetworkStatsService; -import android.net.LinkProperties; +import android.net.NetworkInfo; +import android.net.NetworkState; 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; @@ -46,12 +48,12 @@ 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.Lists; import com.google.android.collect.Maps; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.HashMap; /** @@ -67,6 +69,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private final IAlarmManager mAlarmManager; private final TrustedTime mTime; + private IConnectivityManager mConnManager; + private static final String ACTION_NETWORK_STATS_POLL = "com.android.server.action.NETWORK_STATS_POLL"; @@ -94,9 +98,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private final Object mStatsLock = new Object(); /** Set of active ifaces during this boot. */ - private HashMap mActiveIface = Maps.newHashMap(); + private HashMap mActiveIface = Maps.newHashMap(); /** Set of historical stats for known ifaces. */ - private HashMap mIfaceStats = Maps.newHashMap(); + private HashMap mStats = Maps.newHashMap(); private NetworkStats mLastPollStats; private NetworkStats mLastPersistStats; @@ -132,13 +136,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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); + final IntentFilter ifaceFilter = new IntentFilter(); + ifaceFilter.addAction(CONNECTIVITY_ACTION); + mContext.registerReceiver(mIfaceReceiver, ifaceFilter, CONNECTIVITY_INTERNAL, mHandler); // listen for periodic polling events final IntentFilter pollFilter = new IntentFilter(ACTION_NETWORK_STATS_POLL); @@ -155,6 +155,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } + public void bindConnectivityManager(IConnectivityManager connManager) { + mConnManager = checkNotNull(connManager, "missing IConnectivityManager"); + } + /** * Clear any existing {@link #ACTION_NETWORK_STATS_POLL} alarms, and * reschedule based on current {@link #POLL_INTERVAL} value. @@ -173,45 +177,54 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } @Override - public NetworkStatsHistory[] getNetworkStatsSummary(int networkType) { - // TODO: return history for requested types - return null; + public NetworkStatsHistory getHistoryForNetwork(int networkTemplate) { + // TODO: create relaxed permission for reading stats + mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG); + + synchronized (mStatsLock) { + // combine all interfaces that match template + final String subscriberId = getActiveSubscriberId(); + final NetworkStatsHistory combined = new NetworkStatsHistory(SUMMARY_BUCKET_DURATION); + for (InterfaceIdentity ident : mStats.keySet()) { + final NetworkStatsHistory history = mStats.get(ident); + if (ident.matchesTemplate(networkTemplate, subscriberId)) { + // TODO: combine all matching history data into a single history + } + } + return combined; + } } @Override - public NetworkStatsHistory getNetworkStatsUid(int uid) { + public NetworkStatsHistory getHistoryForUid(int uid, int networkTemplate) { + // TODO: create relaxed permission for reading stats + mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG); + // TODO: return history for requested uid return null; } + @Override + public NetworkStats getSummaryPerUid(long start, long end, int networkTemplate) { + // TODO: create relaxed permission for reading stats + mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG); + + // TODO: total UID-granularity usage between time range + return null; + } + /** - * Receiver that watches for other system components that claim network + * Receiver that watches for {@link IConnectivityManager} to claim network * interfaces. Used to associate {@link TelephonyManager#getSubscriberId()} * with mobile interfaces. */ - private BroadcastReceiver mInterfaceReceiver = new BroadcastReceiver() { + private BroadcastReceiver mIfaceReceiver = 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); - } + // on background handler thread, and verified CONNECTIVITY_INTERNAL + // permission above. + synchronized (mStatsLock) { + updateIfacesLocked(); } } }; @@ -219,8 +232,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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. + // on background handler thread, and verified UPDATE_DEVICE_STATS + // permission above. synchronized (mStatsLock) { // TODO: acquire wakelock while performing poll performPollLocked(); @@ -231,13 +244,49 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - // persist stats during clean shutdown + // verified SHUTDOWN permission above. synchronized (mStatsLock) { writeStatsLocked(); } } }; + /** + * Inspect all current {@link NetworkState} to derive mapping from {@code + * iface} to {@link NetworkStatsHistory}. When multiple {@link NetworkInfo} + * are active on a single {@code iface}, they are combined under a single + * {@link InterfaceIdentity}. + */ + private void updateIfacesLocked() { + if (LOGD) Slog.v(TAG, "updateIfacesLocked()"); + + // take one last stats snapshot before updating iface mapping. this + // isn't perfect, since the kernel may already be counting traffic from + // the updated network. + // TODO: verify that we only poll summary stats, not uid details + performPollLocked(); + + final NetworkState[] states; + try { + states = mConnManager.getAllNetworkState(); + } catch (RemoteException e) { + Slog.w(TAG, "problem reading network state"); + return; + } + + // rebuild active interfaces based on connected networks + mActiveIface.clear(); + + for (NetworkState state : states) { + if (state.networkInfo.isConnected()) { + // collect networks under their parent interfaces + final String iface = state.linkProperties.getInterfaceName(); + final InterfaceIdentity ident = findOrCreateInterfaceLocked(iface); + ident.add(NetworkIdentity.buildNetworkIdentity(mContext, state)); + } + } + } + private void performPollLocked() { if (LOGD) Slog.v(TAG, "performPollLocked()"); @@ -258,13 +307,15 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return; } + final ArrayList unknownIface = Lists.newArrayList(); + // 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"); + final InterfaceIdentity ident = mActiveIface.get(iface); + if (ident == null) { + unknownIface.add(iface); continue; } @@ -272,11 +323,15 @@ public class NetworkStatsService extends INetworkStatsService.Stub { final long rx = pollDelta.rx[index]; final long tx = pollDelta.tx[index]; - final NetworkStatsHistory history = findOrCreateHistoryLocked(info); + final NetworkStatsHistory history = findOrCreateHistoryLocked(ident); history.recordData(timeStart, currentTime, rx, tx); history.removeBucketsBefore(currentTime - SUMMARY_MAX_HISTORY); } + if (LOGD && unknownIface.size() > 0) { + Slog.w(TAG, "unknown interfaces " + unknownIface.toString() + ", ignoring those stats"); + } + mLastPollStats = current; // decide if enough has changed to trigger persist @@ -292,16 +347,24 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } - private NetworkStatsHistory findOrCreateHistoryLocked(InterfaceInfo info) { - NetworkStatsHistory stats = mIfaceStats.get(info); + private NetworkStatsHistory findOrCreateHistoryLocked(InterfaceIdentity ident) { + NetworkStatsHistory stats = mStats.get(ident); if (stats == null) { - stats = new NetworkStatsHistory( - info.networkType, info.identity, UID_ALL, SUMMARY_BUCKET_DURATION); - mIfaceStats.put(info, stats); + stats = new NetworkStatsHistory(SUMMARY_BUCKET_DURATION); + mStats.put(ident, stats); } return stats; } + private InterfaceIdentity findOrCreateInterfaceLocked(String iface) { + InterfaceIdentity ident = mActiveIface.get(iface); + if (ident == null) { + ident = new InterfaceIdentity(); + mActiveIface.put(iface, ident); + } + return ident; + } + private void readStatsLocked() { if (LOGD) Slog.v(TAG, "readStatsLocked()"); // TODO: read historical stats from disk using AtomicFile @@ -310,6 +373,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private void writeStatsLocked() { if (LOGD) Slog.v(TAG, "writeStatsLocked()"); // TODO: persist historical stats to disk using AtomicFile + // TODO: consider duplicating stats and releasing lock while writing } @Override @@ -317,63 +381,17 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mContext.enforceCallingOrSelfPermission(DUMP, TAG); pw.println("Active interfaces:"); - for (InterfaceInfo info : mActiveIface.values()) { - info.dump(" ", pw); + for (String iface : mActiveIface.keySet()) { + final InterfaceIdentity ident = mActiveIface.get(iface); + pw.print(" iface="); pw.print(iface); + pw.print(" ident="); pw.println(ident.toString()); } 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); + for (InterfaceIdentity ident : mStats.keySet()) { + final NetworkStatsHistory stats = mStats.get(ident); + pw.print(" ident="); pw.println(ident.toString()); + stats.dump(" ", pw); } } @@ -389,15 +407,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } - private static boolean equal(Object a, Object b) { - return a == b || (a != null && a.equals(b)); - } - - private static T checkNotNull(T value, String message) { - if (value == null) { - throw new NullPointerException(message); - } - return value; + private String getActiveSubscriberId() { + final TelephonyManager telephony = (TelephonyManager) mContext.getSystemService( + Context.TELEPHONY_SERVICE); + return telephony.getSubscriberId(); } }