diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index 4430e00057..0f207bc953 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -36,6 +36,9 @@ public class NetworkStats implements Parcelable { /** {@link #uid} value when entry is summarized over all UIDs. */ public static final int UID_ALL = 0; + // NOTE: data should only be accounted for once in this structure; if data + // is broken out, the summarized version should not be included. + /** * {@link SystemClock#elapsedRealtime()} timestamp when this data was * generated. @@ -81,12 +84,13 @@ public class NetworkStats implements Parcelable { mTx = new long[size]; } - public void addEntry(String iface, int uid, long rx, long tx) { + public Builder addEntry(String iface, int uid, long rx, long tx) { mIface[mIndex] = iface; mUid[mIndex] = uid; mRx[mIndex] = rx; mTx[mIndex] = tx; mIndex++; + return this; } public NetworkStats build() { @@ -97,11 +101,17 @@ public class NetworkStats implements Parcelable { } } + public int length() { + // length is identical for all fields + return iface.length; + } + /** * Find first stats index that matches the requested parameters. */ public int findIndex(String iface, int uid) { - for (int i = 0; i < this.iface.length; i++) { + final int length = length(); + for (int i = 0; i < length; i++) { if (equal(iface, this.iface[i]) && uid == this.uid[i]) { return i; } @@ -109,13 +119,38 @@ public class NetworkStats implements Parcelable { return -1; } - private static boolean equal(Object a, Object b) { - return a == b || (a != null && a.equals(b)); + /** + * Subtract the given {@link NetworkStats}, effectively leaving the delta + * between two snapshots in time. Assumes that statistics rows collect over + * time, and that none of them have disappeared. + */ + public NetworkStats subtract(NetworkStats value) { + // result will have our rows, but no meaningful timestamp + final int length = length(); + final NetworkStats.Builder result = new NetworkStats.Builder(-1, length); + + for (int i = 0; i < length; i++) { + final String iface = this.iface[i]; + final int uid = this.uid[i]; + + // find remote row that matches, and subtract + final int j = value.findIndex(iface, uid); + if (j == -1) { + // newly appearing row, return entire value + result.addEntry(iface, uid, this.rx[i], this.tx[i]); + } else { + // existing row, subtract remote value + final long rx = this.rx[i] - value.rx[j]; + final long tx = this.tx[i] - value.tx[j]; + result.addEntry(iface, uid, rx, tx); + } + } + + return result.build(); } - /** {@inheritDoc} */ - public int describeContents() { - return 0; + private static boolean equal(Object a, Object b) { + return a == b || (a != null && a.equals(b)); } public void dump(String prefix, PrintWriter pw) { @@ -137,6 +172,11 @@ public class NetworkStats implements Parcelable { return writer.toString(); } + /** {@inheritDoc} */ + public int describeContents() { + return 0; + } + /** {@inheritDoc} */ public void writeToParcel(Parcel dest, int flags) { dest.writeLong(elapsedRealtime); diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java index 7ee7a81f40..c0ff73418a 100644 --- a/core/java/android/net/TrafficStats.java +++ b/core/java/android/net/TrafficStats.java @@ -16,6 +16,12 @@ package android.net; +import android.content.Context; +import android.os.IBinder; +import android.os.INetworkManagementService; +import android.os.RemoteException; +import android.os.ServiceManager; + import dalvik.system.BlockGuard; import java.net.Socket; @@ -35,6 +41,17 @@ public class TrafficStats { */ public final static int UNSUPPORTED = -1; + /** + * Snapshot of {@link NetworkStats} when the currently active profiling + * session started, or {@code null} if no session active. + * + * @see #startDataProfiling(Context) + * @see #stopDataProfiling(Context) + */ + private static NetworkStats sActiveProfilingStart; + + private static Object sProfilingLock = new Object(); + /** * Set active tag to use when accounting {@link Socket} traffic originating * from the current thread. Only one active tag per thread is supported. @@ -92,6 +109,44 @@ public class TrafficStats { BlockGuard.untagSocketFd(socket.getFileDescriptor$()); } + /** + * Start profiling data usage for current UID. Only one profiling session + * can be active at a time. + * + * @hide + */ + public static void startDataProfiling(Context context) { + synchronized (sProfilingLock) { + if (sActiveProfilingStart != null) { + throw new IllegalStateException("already profiling data"); + } + + // take snapshot in time; we calculate delta later + sActiveProfilingStart = getNetworkStatsForUid(context); + } + } + + /** + * Stop profiling data usage for current UID. + * + * @return Detailed {@link NetworkStats} of data that occurred since last + * {@link #startDataProfiling(Context)} call. + * @hide + */ + public static NetworkStats stopDataProfiling(Context context) { + synchronized (sProfilingLock) { + if (sActiveProfilingStart == null) { + throw new IllegalStateException("not profiling data"); + } + + // subtract starting values and return delta + final NetworkStats profilingStop = getNetworkStatsForUid(context); + final NetworkStats profilingDelta = profilingStop.subtract(sActiveProfilingStart); + sActiveProfilingStart = null; + return profilingDelta; + } + } + /** * Get the total number of packets transmitted through the mobile interface. * @@ -350,4 +405,21 @@ public class TrafficStats { * {@link #UNSUPPORTED} will be returned. */ public static native long getUidUdpRxPackets(int uid); + + /** + * Return detailed {@link NetworkStats} for the current UID. Requires no + * special permission. + */ + private static NetworkStats getNetworkStatsForUid(Context context) { + final IBinder binder = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + final INetworkManagementService service = INetworkManagementService.Stub.asInterface( + binder); + + final int uid = android.os.Process.myUid(); + try { + return service.getNetworkStatsUidDetail(uid); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } }