diff options
14 files changed, 610 insertions, 174 deletions
diff --git a/core/java/android/app/usage/NetworkStats.java b/core/java/android/app/usage/NetworkStats.java index 3670b914ecf3..222e9a0e5e0c 100644 --- a/core/java/android/app/usage/NetworkStats.java +++ b/core/java/android/app/usage/NetworkStats.java @@ -97,12 +97,12 @@ public final class NetworkStats implements AutoCloseable { private NetworkStatsHistory.Entry mRecycledHistoryEntry = null; /** @hide */ - NetworkStats(Context context, NetworkTemplate template, long startTimestamp, + NetworkStats(Context context, NetworkTemplate template, int flags, long startTimestamp, long endTimestamp) throws RemoteException, SecurityException { final INetworkStatsService statsService = INetworkStatsService.Stub.asInterface( ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); // Open network stats session - mSession = statsService.openSessionForUsageStats(context.getOpPackageName()); + mSession = statsService.openSessionForUsageStats(flags, context.getOpPackageName()); mCloseGuard.open("close"); mTemplate = template; mStartTimeStamp = startTimestamp; diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java index ef262e046021..853b00331a4d 100644 --- a/core/java/android/app/usage/NetworkStatsManager.java +++ b/core/java/android/app/usage/NetworkStatsManager.java @@ -24,15 +24,14 @@ import android.app.usage.NetworkStats.Bucket; import android.content.Context; import android.net.ConnectivityManager; import android.net.DataUsageRequest; +import android.net.INetworkStatsService; import android.net.NetworkIdentity; import android.net.NetworkTemplate; -import android.net.INetworkStatsService; import android.os.Binder; -import android.os.Build; -import android.os.Message; -import android.os.Messenger; import android.os.Handler; import android.os.Looper; +import android.os.Message; +import android.os.Messenger; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; @@ -79,7 +78,7 @@ import android.util.Log; * In addition to tethering usage, usage by removed users and apps, and usage by the system * is also included in the results for callers with one of these higher levels of access. * <p /> - * <b>NOTE:</b> Prior to API level {@value Build.VERSION_CODES#N}, all calls to these APIs required + * <b>NOTE:</b> Prior to API level {@value android.os.Build.VERSION_CODES#N}, all calls to these APIs required * the above permission, even to access an app's own data usage, and carrier-privileged apps were * not included. */ @@ -96,6 +95,13 @@ public class NetworkStatsManager { private final Context mContext; private final INetworkStatsService mService; + /** @hide */ + public static final int FLAG_POLL_ON_OPEN = 1 << 0; + /** @hide */ + public static final int FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN = 1 << 1; + + private int mFlags; + /** * {@hide} */ @@ -103,6 +109,25 @@ public class NetworkStatsManager { mContext = context; mService = INetworkStatsService.Stub.asInterface( ServiceManager.getServiceOrThrow(Context.NETWORK_STATS_SERVICE)); + setPollOnOpen(true); + } + + /** @hide */ + public void setPollOnOpen(boolean pollOnOpen) { + if (pollOnOpen) { + mFlags |= FLAG_POLL_ON_OPEN; + } else { + mFlags &= ~FLAG_POLL_ON_OPEN; + } + } + + /** @hide */ + public void setAugmentWithSubscriptionPlan(boolean augmentWithSubscriptionPlan) { + if (augmentWithSubscriptionPlan) { + mFlags |= FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN; + } else { + mFlags &= ~FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN; + } } /** @@ -136,7 +161,7 @@ public class NetworkStatsManager { } Bucket bucket = null; - NetworkStats stats = new NetworkStats(mContext, template, startTime, endTime); + NetworkStats stats = new NetworkStats(mContext, template, mFlags, startTime, endTime); bucket = stats.getDeviceSummaryForNetwork(); stats.close(); @@ -174,7 +199,7 @@ public class NetworkStatsManager { } NetworkStats stats; - stats = new NetworkStats(mContext, template, startTime, endTime); + stats = new NetworkStats(mContext, template, mFlags, startTime, endTime); stats.startSummaryEnumeration(); stats.close(); @@ -211,7 +236,7 @@ public class NetworkStatsManager { } NetworkStats result; - result = new NetworkStats(mContext, template, startTime, endTime); + result = new NetworkStats(mContext, template, mFlags, startTime, endTime); result.startSummaryEnumeration(); return result; @@ -260,7 +285,7 @@ public class NetworkStatsManager { NetworkStats result; try { - result = new NetworkStats(mContext, template, startTime, endTime); + result = new NetworkStats(mContext, template, mFlags, startTime, endTime); result.startHistoryEnumeration(uid, tag); } catch (RemoteException e) { Log.e(TAG, "Error while querying stats for uid=" + uid + " tag=" + tag, e); @@ -305,7 +330,7 @@ public class NetworkStatsManager { } NetworkStats result; - result = new NetworkStats(mContext, template, startTime, endTime); + result = new NetworkStats(mContext, template, mFlags, startTime, endTime); result.startUserUidEnumeration(); return result; } diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl index e693009c3377..91801127fd4a 100644 --- a/core/java/android/net/INetworkStatsService.aidl +++ b/core/java/android/net/INetworkStatsService.aidl @@ -36,7 +36,7 @@ interface INetworkStatsService { * PACKAGE_USAGE_STATS permission is always checked. If PACKAGE_USAGE_STATS is not granted * READ_NETWORK_USAGE_STATS is checked for. */ - INetworkStatsSession openSessionForUsageStats(String callingPackage); + INetworkStatsSession openSessionForUsageStats(int flags, String callingPackage); /** Return network layer usage total for traffic that matches template. */ long getNetworkTotalBytes(in NetworkTemplate template, long start, long end); diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java index 5f521de63cf1..433f9410cc6e 100644 --- a/core/java/android/net/NetworkStatsHistory.java +++ b/core/java/android/net/NetworkStatsHistory.java @@ -27,6 +27,7 @@ import static android.net.NetworkStatsHistory.Entry.UNKNOWN; import static android.net.NetworkStatsHistory.ParcelUtils.readLongArray; import static android.net.NetworkStatsHistory.ParcelUtils.writeLongArray; import static android.text.format.DateUtils.SECOND_IN_MILLIS; + import static com.android.internal.util.ArrayUtils.total; import android.os.Parcel; @@ -282,6 +283,24 @@ public class NetworkStatsHistory implements Parcelable { return entry; } + public void setValues(int i, Entry entry) { + // Unwind old values + if (rxBytes != null) totalBytes -= rxBytes[i]; + if (txBytes != null) totalBytes -= txBytes[i]; + + bucketStart[i] = entry.bucketStart; + setLong(activeTime, i, entry.activeTime); + setLong(rxBytes, i, entry.rxBytes); + setLong(rxPackets, i, entry.rxPackets); + setLong(txBytes, i, entry.txBytes); + setLong(txPackets, i, entry.txPackets); + setLong(operations, i, entry.operations); + + // Apply new values + if (rxBytes != null) totalBytes += rxBytes[i]; + if (txBytes != null) totalBytes += txBytes[i]; + } + /** * Record that data traffic occurred in the given time range. Will * distribute across internal buckets, creating new buckets as needed. diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java index 0d2fcd074047..b307c5d6fc53 100644 --- a/core/java/android/net/NetworkTemplate.java +++ b/core/java/android/net/NetworkTemplate.java @@ -326,6 +326,10 @@ public class NetworkTemplate implements Parcelable { } } + public boolean matchesSubscriberId(String subscriberId) { + return ArrayUtils.contains(mMatchSubscriberIds, subscriberId); + } + /** * Check if mobile network with matching IMSI. */ diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 1741755c2750..fa523f36a113 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -8015,6 +8015,8 @@ public final class Settings { public static final String NETSTATS_GLOBAL_ALERT_BYTES = "netstats_global_alert_bytes"; /** {@hide} */ public static final String NETSTATS_SAMPLE_ENABLED = "netstats_sample_enabled"; + /** {@hide} */ + public static final String NETSTATS_AUGMENT_ENABLED = "netstats_augment_enabled"; /** {@hide} */ public static final String NETSTATS_DEV_BUCKET_DURATION = "netstats_dev_bucket_duration"; diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 6848fce764bc..b41e892adae9 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -253,6 +253,7 @@ public class SettingsBackupTest { Settings.Global.NETSTATS_GLOBAL_ALERT_BYTES, Settings.Global.NETSTATS_POLL_INTERVAL, Settings.Global.NETSTATS_SAMPLE_ENABLED, + Settings.Global.NETSTATS_AUGMENT_ENABLED, Settings.Global.NETSTATS_TIME_CACHE_MAX_AGE, Settings.Global.NETSTATS_UID_BUCKET_DURATION, Settings.Global.NETSTATS_UID_DELETE_AGE, diff --git a/services/core/java/com/android/server/net/NetworkStatsCollection.java b/services/core/java/com/android/server/net/NetworkStatsCollection.java index 03543007c80e..8837c157aebe 100644 --- a/services/core/java/com/android/server/net/NetworkStatsCollection.java +++ b/services/core/java/com/android/server/net/NetworkStatsCollection.java @@ -28,6 +28,8 @@ import static android.net.NetworkStats.UID_ALL; import static android.net.TrafficStats.UID_REMOVED; import static android.text.format.DateUtils.WEEK_IN_MILLIS; +import static com.android.server.net.NetworkStatsService.TAG; + import android.net.NetworkIdentity; import android.net.NetworkStats; import android.net.NetworkStatsHistory; @@ -37,20 +39,24 @@ import android.os.Binder; import android.service.NetworkStatsCollectionKeyProto; import android.service.NetworkStatsCollectionProto; import android.service.NetworkStatsCollectionStatsProto; +import android.telephony.SubscriptionPlan; import android.util.ArrayMap; import android.util.AtomicFile; import android.util.IntArray; +import android.util.Pair; +import android.util.Slog; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FileRotator; import com.android.internal.util.IndentingPrintWriter; +import libcore.io.IoUtils; + import com.google.android.collect.Lists; import com.google.android.collect.Maps; -import libcore.io.IoUtils; - import java.io.BufferedInputStream; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -60,9 +66,11 @@ import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.net.ProtocolException; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.Objects; /** @@ -140,6 +148,35 @@ public class NetworkStatsCollection implements FileRotator.Reader { return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE; } + @VisibleForTesting + public long roundUp(long time) { + if (time == Long.MIN_VALUE || time == Long.MAX_VALUE + || time == SubscriptionPlan.TIME_UNKNOWN) { + return time; + } else { + final long mod = time % mBucketDuration; + if (mod > 0) { + time -= mod; + time += mBucketDuration; + } + return time; + } + } + + @VisibleForTesting + public long roundDown(long time) { + if (time == Long.MIN_VALUE || time == Long.MAX_VALUE + || time == SubscriptionPlan.TIME_UNKNOWN) { + return time; + } else { + final long mod = time % mBucketDuration; + if (mod > 0) { + time -= mod; + } + return time; + } + } + public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel) { return getRelevantUids(accessLevel, Binder.getCallingUid()); } @@ -165,60 +202,110 @@ public class NetworkStatsCollection implements FileRotator.Reader { * Combine all {@link NetworkStatsHistory} in this collection which match * the requested parameters. */ - public NetworkStatsHistory getHistory( - NetworkTemplate template, int uid, int set, int tag, int fields, - @NetworkStatsAccess.Level int accessLevel) { - return getHistory(template, uid, set, tag, fields, Long.MIN_VALUE, Long.MAX_VALUE, - accessLevel); - } - - /** - * Combine all {@link NetworkStatsHistory} in this collection which match - * the requested parameters. - */ - public NetworkStatsHistory getHistory( - NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end, - @NetworkStatsAccess.Level int accessLevel) { - return getHistory(template, uid, set, tag, fields, start, end, accessLevel, - Binder.getCallingUid()); - } - - /** - * Combine all {@link NetworkStatsHistory} in this collection which match - * the requested parameters. - */ - public NetworkStatsHistory getHistory( - NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end, + public NetworkStatsHistory getHistory(NetworkTemplate template, SubscriptionPlan augmentPlan, + int uid, int set, int tag, int fields, long start, long end, @NetworkStatsAccess.Level int accessLevel, int callerUid) { if (!NetworkStatsAccess.isAccessibleToUser(uid, callerUid, accessLevel)) { throw new SecurityException("Network stats history of uid " + uid + " is forbidden for caller " + callerUid); } + final int bucketEstimate = (int) ((end - start) / mBucketDuration); final NetworkStatsHistory combined = new NetworkStatsHistory( - mBucketDuration, start == end ? 1 : estimateBuckets(), fields); + mBucketDuration, bucketEstimate, fields); // shortcut when we know stats will be empty if (start == end) return combined; + // Figure out the window of time that we should be augmenting (if any) + long augmentStart = SubscriptionPlan.TIME_UNKNOWN; + long augmentEnd = (augmentPlan != null) ? augmentPlan.getDataUsageTime() + : SubscriptionPlan.TIME_UNKNOWN; + // And if augmenting, we might need to collect more data to adjust with + long collectStart = start; + long collectEnd = end; + + if (augmentEnd != SubscriptionPlan.TIME_UNKNOWN) { + final Iterator<Pair<ZonedDateTime, ZonedDateTime>> it = augmentPlan.cycleIterator(); + while (it.hasNext()) { + final Pair<ZonedDateTime, ZonedDateTime> cycle = it.next(); + final long cycleStart = cycle.first.toInstant().toEpochMilli(); + final long cycleEnd = cycle.second.toInstant().toEpochMilli(); + if (cycleStart <= augmentEnd && augmentEnd < cycleEnd) { + augmentStart = cycleStart; + collectStart = Long.min(collectStart, augmentStart); + collectEnd = Long.max(collectEnd, augmentEnd); + break; + } + } + } + + if (augmentStart != SubscriptionPlan.TIME_UNKNOWN) { + // Shrink augmentation window so we don't risk undercounting. + augmentStart = roundUp(augmentStart); + augmentEnd = roundDown(augmentEnd); + // Grow collection window so we get all the stats needed. + collectStart = roundDown(collectStart); + collectEnd = roundUp(collectEnd); + } + for (int i = 0; i < mStats.size(); i++) { final Key key = mStats.keyAt(i); if (key.uid == uid && NetworkStats.setMatches(set, key.set) && key.tag == tag && templateMatches(template, key.ident)) { final NetworkStatsHistory value = mStats.valueAt(i); - combined.recordHistory(value, start, end); + combined.recordHistory(value, collectStart, collectEnd); } } - return combined; - } - /** - * Summarize all {@link NetworkStatsHistory} in this collection which match - * the requested parameters. - */ - public NetworkStats getSummary(NetworkTemplate template, long start, long end, - @NetworkStatsAccess.Level int accessLevel) { - return getSummary(template, start, end, accessLevel, Binder.getCallingUid()); + if (augmentStart != SubscriptionPlan.TIME_UNKNOWN) { + final NetworkStatsHistory.Entry entry = combined.getValues( + augmentStart, augmentEnd, null); + + // If we don't have any recorded data for this time period, give + // ourselves something to scale with. + if (entry.rxBytes == 0 || entry.txBytes == 0) { + combined.recordData(augmentStart, augmentEnd, + new NetworkStats.Entry(1, 0, 1, 0, 0)); + combined.getValues(augmentStart, augmentEnd, entry); + } + + final long rawBytes = entry.rxBytes + entry.txBytes; + final long rawRxBytes = entry.rxBytes; + final long rawTxBytes = entry.txBytes; + final long targetBytes = augmentPlan.getDataUsageBytes(); + final long targetRxBytes = (rawRxBytes * targetBytes) / rawBytes; + final long targetTxBytes = (rawTxBytes * targetBytes) / rawBytes; + + // Scale all matching buckets to reach anchor target + final long beforeTotal = combined.getTotalBytes(); + for (int i = 0; i < combined.size(); i++) { + combined.getValues(i, entry); + if (entry.bucketStart >= augmentStart + && entry.bucketStart + entry.bucketDuration <= augmentEnd) { + entry.rxBytes = (entry.rxBytes * targetRxBytes) / rawRxBytes; + entry.txBytes = (entry.txBytes * targetTxBytes) / rawTxBytes; + // We purposefully clear out packet counters to indicate + // that this data has been augmented. + entry.rxPackets = 0; + entry.txPackets = 0; + combined.setValues(i, entry); + } + } + + final long deltaTotal = combined.getTotalBytes() - beforeTotal; + if (deltaTotal != 0) { + Slog.d(TAG, "Augmented network usage by " + deltaTotal + " bytes"); + } + + // Finally we can slice data as originally requested + final NetworkStatsHistory sliced = new NetworkStatsHistory( + mBucketDuration, bucketEstimate, fields); + sliced.recordHistory(combined, start, end); + return sliced; + } else { + return combined; + } } /** @@ -230,6 +317,7 @@ public class NetworkStatsCollection implements FileRotator.Reader { final long now = System.currentTimeMillis(); final NetworkStats stats = new NetworkStats(end - start, 24); + // shortcut when we know stats will be empty if (start == end) return stats; diff --git a/services/core/java/com/android/server/net/NetworkStatsObservers.java b/services/core/java/com/android/server/net/NetworkStatsObservers.java index a256cbcf9030..741c2062bd57 100644 --- a/services/core/java/com/android/server/net/NetworkStatsObservers.java +++ b/services/core/java/com/android/server/net/NetworkStatsObservers.java @@ -17,28 +17,26 @@ package com.android.server.net; import static android.net.TrafficStats.MB_IN_BYTES; + import static com.android.internal.util.Preconditions.checkArgument; import android.app.usage.NetworkStatsManager; import android.net.DataUsageRequest; import android.net.NetworkStats; -import android.net.NetworkStats.NonMonotonicObserver; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; -import android.os.Binder; import android.os.Bundle; -import android.os.Looper; -import android.os.Message; -import android.os.Messenger; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; import android.os.Process; import android.os.RemoteException; import android.util.ArrayMap; -import android.util.IntArray; -import android.util.SparseArray; import android.util.Slog; +import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.net.VpnInfo; @@ -410,7 +408,7 @@ class NetworkStatsObservers { */ private long getTotalBytesForNetworkUid(NetworkTemplate template, int uid) { try { - NetworkStatsHistory history = mCollection.getHistory(template, uid, + NetworkStatsHistory history = mCollection.getHistory(template, null, uid, NetworkStats.SET_ALL, NetworkStats.TAG_NONE, NetworkStatsHistory.FIELD_ALL, Long.MIN_VALUE /* start */, Long.MAX_VALUE /* end */, diff --git a/services/core/java/com/android/server/net/NetworkStatsRecorder.java b/services/core/java/com/android/server/net/NetworkStatsRecorder.java index 80309e19eb42..4bee55ef8b22 100644 --- a/services/core/java/com/android/server/net/NetworkStatsRecorder.java +++ b/services/core/java/com/android/server/net/NetworkStatsRecorder.java @@ -20,6 +20,7 @@ import static android.net.NetworkStats.TAG_NONE; import static android.net.TrafficStats.KB_IN_BYTES; import static android.net.TrafficStats.MB_IN_BYTES; import static android.text.format.DateUtils.YEAR_IN_MILLIS; + import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.Nullable; @@ -28,6 +29,7 @@ import android.net.NetworkStats.NonMonotonicObserver; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; import android.net.TrafficStats; +import android.os.Binder; import android.os.DropBoxManager; import android.service.NetworkStatsRecorderProto; import android.util.Log; @@ -38,6 +40,9 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.net.VpnInfo; import com.android.internal.util.FileRotator; import com.android.internal.util.IndentingPrintWriter; + +import libcore.io.IoUtils; + import com.google.android.collect.Sets; import java.io.ByteArrayOutputStream; @@ -52,8 +57,6 @@ import java.util.Arrays; import java.util.HashSet; import java.util.Map; -import libcore.io.IoUtils; - /** * Logic to record deltas between periodic {@link NetworkStats} snapshots into * {@link NetworkStatsHistory} that belong to {@link NetworkStatsCollection}. @@ -150,7 +153,7 @@ public class NetworkStatsRecorder { public NetworkStats.Entry getTotalSinceBootLocked(NetworkTemplate template) { return mSinceBoot.getSummary(template, Long.MIN_VALUE, Long.MAX_VALUE, - NetworkStatsAccess.Level.DEVICE).getTotal(null); + NetworkStatsAccess.Level.DEVICE, Binder.getCallingUid()).getTotal(null); } public NetworkStatsCollection getSinceBoot() { diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index b14aa13bfb28..4bd927d4970e 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -26,6 +26,8 @@ import static android.content.Intent.EXTRA_UID; import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED; import static android.net.ConnectivityManager.isNetworkTypeMobile; import static android.net.NetworkStats.IFACE_ALL; +import static android.net.NetworkStats.METERED_ALL; +import static android.net.NetworkStats.ROAMING_ALL; import static android.net.NetworkStats.SET_ALL; import static android.net.NetworkStats.SET_DEFAULT; import static android.net.NetworkStats.SET_FOREGROUND; @@ -33,10 +35,12 @@ import static android.net.NetworkStats.STATS_PER_IFACE; import static android.net.NetworkStats.STATS_PER_UID; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; +import static android.net.NetworkStatsHistory.FIELD_ALL; import static android.net.NetworkTemplate.buildTemplateMobileWildcard; import static android.net.NetworkTemplate.buildTemplateWifiWildcard; import static android.net.TrafficStats.KB_IN_BYTES; import static android.net.TrafficStats.MB_IN_BYTES; +import static android.provider.Settings.Global.NETSTATS_AUGMENT_ENABLED; import static android.provider.Settings.Global.NETSTATS_DEV_BUCKET_DURATION; import static android.provider.Settings.Global.NETSTATS_DEV_DELETE_AGE; import static android.provider.Settings.Global.NETSTATS_DEV_PERSIST_BYTES; @@ -65,6 +69,7 @@ import static com.android.server.NetworkManagementSocketTagger.setKernelCounterS import android.app.AlarmManager; import android.app.PendingIntent; +import android.app.usage.NetworkStatsManager; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -104,6 +109,8 @@ import android.provider.Settings; import android.provider.Settings.Global; import android.service.NetworkInterfaceProto; import android.service.NetworkStatsServiceDumpProto; +import android.telephony.SubscriptionManager; +import android.telephony.SubscriptionPlan; import android.telephony.TelephonyManager; import android.text.format.DateUtils; import android.util.ArrayMap; @@ -139,8 +146,8 @@ import java.util.List; * other system services. */ public class NetworkStatsService extends INetworkStatsService.Stub { - private static final String TAG = "NetworkStats"; - private static final boolean LOGV = false; + static final String TAG = "NetworkStats"; + static final boolean LOGV = false; private static final int MSG_PERFORM_POLL = 1; private static final int MSG_UPDATE_IFACES = 2; @@ -194,6 +201,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { public long getPollInterval(); public long getTimeCacheMaxAge(); public boolean getSampleEnabled(); + public boolean getAugmentEnabled(); public static class Config { public final long bucketDuration; @@ -466,18 +474,20 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public INetworkStatsSession openSession() { - return createSession(null, /* poll on create */ false); + // NOTE: if callers want to get non-augmented data, they should go + // through the public API + return openSessionInternal(NetworkStatsManager.FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN, null); } @Override - public INetworkStatsSession openSessionForUsageStats(final String callingPackage) { - return createSession(callingPackage, /* poll on create */ true); + public INetworkStatsSession openSessionForUsageStats(int flags, String callingPackage) { + return openSessionInternal(flags, callingPackage); } - private INetworkStatsSession createSession(final String callingPackage, boolean pollOnCreate) { + private INetworkStatsSession openSessionInternal(final int flags, final String callingPackage) { assertBandwidthControlEnabled(); - if (pollOnCreate) { + if ((flags & NetworkStatsManager.FLAG_POLL_ON_OPEN) != 0) { final long ident = Binder.clearCallingIdentity(); try { performPoll(FLAG_PERSIST_ALL); @@ -490,9 +500,13 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // for its lifetime; when caller closes only weak references remain. return new INetworkStatsSession.Stub() { + private final int mCallingUid = Binder.getCallingUid(); + private final String mCallingPackage = callingPackage; + private final @NetworkStatsAccess.Level int mAccessLevel = checkAccessLevel( + callingPackage); + private NetworkStatsCollection mUidComplete; private NetworkStatsCollection mUidTagComplete; - private String mCallingPackage = callingPackage; private NetworkStatsCollection getUidComplete() { synchronized (mStatsLock) { @@ -514,55 +528,38 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public int[] getRelevantUids() { - return getUidComplete().getRelevantUids(checkAccessLevel(mCallingPackage)); + return getUidComplete().getRelevantUids(mAccessLevel); } @Override - public NetworkStats getDeviceSummaryForNetwork(NetworkTemplate template, long start, - long end) { - @NetworkStatsAccess.Level int accessLevel = checkAccessLevel(mCallingPackage); - if (accessLevel < NetworkStatsAccess.Level.DEVICESUMMARY) { - throw new SecurityException("Calling package " + mCallingPackage - + " cannot access device summary network stats"); - } - NetworkStats result = new NetworkStats(end - start, 1); - final long ident = Binder.clearCallingIdentity(); - try { - // Using access level higher than the one we checked for above. - // Reason is that we are combining usage data in a way that is not PII - // anymore. - result.combineAllValues( - internalGetSummaryForNetwork(template, start, end, - NetworkStatsAccess.Level.DEVICE)); - } finally { - Binder.restoreCallingIdentity(ident); - } - return result; + public NetworkStats getDeviceSummaryForNetwork( + NetworkTemplate template, long start, long end) { + return internalGetSummaryForNetwork(template, flags, start, end, mAccessLevel, + mCallingUid); } @Override public NetworkStats getSummaryForNetwork( NetworkTemplate template, long start, long end) { - @NetworkStatsAccess.Level int accessLevel = checkAccessLevel(mCallingPackage); - return internalGetSummaryForNetwork(template, start, end, accessLevel); + return internalGetSummaryForNetwork(template, flags, start, end, mAccessLevel, + mCallingUid); } @Override public NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields) { - @NetworkStatsAccess.Level int accessLevel = checkAccessLevel(mCallingPackage); - return internalGetHistoryForNetwork(template, fields, accessLevel); + return internalGetHistoryForNetwork(template, flags, fields, mAccessLevel, + mCallingUid); } @Override public NetworkStats getSummaryForAllUid( NetworkTemplate template, long start, long end, boolean includeTags) { try { - @NetworkStatsAccess.Level int accessLevel = checkAccessLevel(mCallingPackage); - final NetworkStats stats = - getUidComplete().getSummary(template, start, end, accessLevel); + final NetworkStats stats = getUidComplete() + .getSummary(template, start, end, mAccessLevel, mCallingUid); if (includeTags) { final NetworkStats tagStats = getUidTagComplete() - .getSummary(template, start, end, accessLevel); + .getSummary(template, start, end, mAccessLevel, mCallingUid); stats.combineAllValues(tagStats); } return stats; @@ -576,13 +573,13 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public NetworkStatsHistory getHistoryForUid( NetworkTemplate template, int uid, int set, int tag, int fields) { - @NetworkStatsAccess.Level int accessLevel = checkAccessLevel(mCallingPackage); + // NOTE: We don't augment UID-level statistics if (tag == TAG_NONE) { - return getUidComplete().getHistory(template, uid, set, tag, fields, - accessLevel); + return getUidComplete().getHistory(template, null, uid, set, tag, fields, + Long.MIN_VALUE, Long.MAX_VALUE, mAccessLevel, mCallingUid); } else { - return getUidTagComplete().getHistory(template, uid, set, tag, fields, - accessLevel); + return getUidTagComplete().getHistory(template, null, uid, set, tag, fields, + Long.MIN_VALUE, Long.MAX_VALUE, mAccessLevel, mCallingUid); } } @@ -590,13 +587,13 @@ public class NetworkStatsService extends INetworkStatsService.Stub { public NetworkStatsHistory getHistoryIntervalForUid( NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end) { - @NetworkStatsAccess.Level int accessLevel = checkAccessLevel(mCallingPackage); + // NOTE: We don't augment UID-level statistics if (tag == TAG_NONE) { - return getUidComplete().getHistory(template, uid, set, tag, fields, start, end, - accessLevel); + return getUidComplete().getHistory(template, null, uid, set, tag, fields, + start, end, mAccessLevel, mCallingUid); } else if (uid == Binder.getCallingUid()) { - return getUidTagComplete().getHistory(template, uid, set, tag, fields, - start, end, accessLevel); + return getUidTagComplete().getHistory(template, null, uid, set, tag, fields, + start, end, mAccessLevel, mCallingUid); } else { throw new SecurityException("Calling package " + mCallingPackage + " cannot access tag information from a different uid"); @@ -617,36 +614,84 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } /** + * Find the most relevant {@link SubscriptionPlan} for the given + * {@link NetworkTemplate} and flags. This is typically used to augment + * local measurement results to match a known anchor from the carrier. + */ + private SubscriptionPlan resolveSubscriptionPlan(NetworkTemplate template, int flags) { + SubscriptionPlan plan = null; + if ((flags & NetworkStatsManager.FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN) != 0 + && (template.getMatchRule() == NetworkTemplate.MATCH_MOBILE_ALL) + && mSettings.getAugmentEnabled()) { + Slog.d(TAG, "Resolving plan for " + template); + final long token = Binder.clearCallingIdentity(); + try { + final SubscriptionManager sm = mContext.getSystemService(SubscriptionManager.class); + final TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); + for (int subId : sm.getActiveSubscriptionIdList()) { + if (template.matchesSubscriberId(tm.getSubscriberId(subId))) { + Slog.d(TAG, "Found active matching subId " + subId); + final List<SubscriptionPlan> plans = sm.getSubscriptionPlans(subId); + if (!plans.isEmpty()) { + plan = plans.get(0); + } + } + } + } finally { + Binder.restoreCallingIdentity(token); + } + Slog.d(TAG, "Resolved to plan " + plan); + } + return plan; + } + + /** * Return network summary, splicing between DEV and XT stats when * appropriate. */ - private NetworkStats internalGetSummaryForNetwork( - NetworkTemplate template, long start, long end, - @NetworkStatsAccess.Level int accessLevel) { + private NetworkStats internalGetSummaryForNetwork(NetworkTemplate template, int flags, + long start, long end, @NetworkStatsAccess.Level int accessLevel, int callingUid) { // We've been using pure XT stats long enough that we no longer need to // splice DEV and XT together. - return mXtStatsCached.getSummary(template, start, end, accessLevel); + final NetworkStatsHistory history = internalGetHistoryForNetwork(template, flags, FIELD_ALL, + accessLevel, callingUid); + + final long now = System.currentTimeMillis(); + final NetworkStatsHistory.Entry entry = history.getValues(start, end, now, null); + + final NetworkStats stats = new NetworkStats(end - start, 1); + stats.addValues(new NetworkStats.Entry(IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE, METERED_ALL, + ROAMING_ALL, entry.rxBytes, entry.rxPackets, entry.txBytes, entry.txPackets, + entry.operations)); + return stats; } /** * Return network history, splicing between DEV and XT stats when * appropriate. */ - private NetworkStatsHistory internalGetHistoryForNetwork(NetworkTemplate template, int fields, - @NetworkStatsAccess.Level int accessLevel) { + private NetworkStatsHistory internalGetHistoryForNetwork(NetworkTemplate template, + int flags, int fields, @NetworkStatsAccess.Level int accessLevel, int callingUid) { // We've been using pure XT stats long enough that we no longer need to // splice DEV and XT together. - return mXtStatsCached.getHistory(template, UID_ALL, SET_ALL, TAG_NONE, fields, accessLevel); + return mXtStatsCached.getHistory(template, resolveSubscriptionPlan(template, flags), + UID_ALL, SET_ALL, TAG_NONE, fields, Long.MIN_VALUE, Long.MAX_VALUE, + accessLevel, callingUid); } @Override public long getNetworkTotalBytes(NetworkTemplate template, long start, long end) { - // Special case - since this is for internal use only, don't worry about a full access level - // check and just require the signature/privileged permission. + // Special case - since this is for internal use only, don't worry about + // a full access level check and just require the signature/privileged + // permission. mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); assertBandwidthControlEnabled(); - return internalGetSummaryForNetwork(template, start, end, NetworkStatsAccess.Level.DEVICE) - .getTotalBytes(); + + // NOTE: if callers want to get non-augmented data, they should go + // through the public API + return internalGetSummaryForNetwork(template, + NetworkStatsManager.FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN, start, end, + NetworkStatsAccess.Level.DEVICE, Binder.getCallingUid()).getTotalBytes(); } @Override @@ -1530,6 +1575,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return getGlobalBoolean(NETSTATS_SAMPLE_ENABLED, true); } @Override + public boolean getAugmentEnabled() { + return getGlobalBoolean(NETSTATS_AUGMENT_ENABLED, true); + } + @Override public Config getDevConfig() { return new Config(getGlobalLong(NETSTATS_DEV_BUCKET_DURATION, HOUR_IN_MILLIS), getGlobalLong(NETSTATS_DEV_ROTATE_AGE, 15 * DAY_IN_MILLIS), diff --git a/tests/net/java/android/net/NetworkStatsHistoryTest.java b/tests/net/java/android/net/NetworkStatsHistoryTest.java index e7b91b568d74..1c0c14eac08b 100644 --- a/tests/net/java/android/net/NetworkStatsHistoryTest.java +++ b/tests/net/java/android/net/NetworkStatsHistoryTest.java @@ -485,6 +485,21 @@ public class NetworkStatsHistoryTest extends AndroidTestCase { assertTrue(stats.intersects(Long.MIN_VALUE, TEST_START + 1)); } + public void testSetValues() throws Exception { + stats = new NetworkStatsHistory(HOUR_IN_MILLIS); + stats.recordData(TEST_START, TEST_START + 1, + new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L)); + + assertEquals(1024L + 2048L, stats.getTotalBytes()); + + final NetworkStatsHistory.Entry entry = stats.getValues(0, null); + entry.rxBytes /= 2; + entry.txBytes *= 2; + stats.setValues(0, entry); + + assertEquals(512L + 4096L, stats.getTotalBytes()); + } + private static void assertIndexBeforeAfter( NetworkStatsHistory stats, int before, int after, long time) { assertEquals("unexpected before", before, stats.getIndexBefore(time)); diff --git a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java index 2a32b73d56da..0c2ad38a11fe 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java @@ -17,26 +17,35 @@ package com.android.server.net; import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.net.NetworkStats.SET_ALL; import static android.net.NetworkStats.SET_DEFAULT; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; +import static android.net.NetworkStatsHistory.FIELD_ALL; import static android.net.NetworkTemplate.buildTemplateMobileAll; +import static android.os.Process.myUid; import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import android.content.res.Resources; import android.net.NetworkIdentity; import android.net.NetworkStats; +import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; import android.os.Process; import android.os.UserHandle; +import android.telephony.SubscriptionPlan; import android.telephony.TelephonyManager; -import android.support.test.filters.SmallTest; import android.test.AndroidTestCase; import android.test.MoreAsserts; +import android.test.suitebuilder.annotation.SmallTest; +import android.text.format.DateUtils; import com.android.frameworks.tests.net.R; +import libcore.io.IoUtils; +import libcore.io.Streams; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; @@ -44,9 +53,9 @@ import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; - -import libcore.io.IoUtils; -import libcore.io.Streams; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; /** * Tests for {@link NetworkStatsCollection}. @@ -57,6 +66,10 @@ public class NetworkStatsCollectionTest extends AndroidTestCase { private static final String TEST_FILE = "test.bin"; private static final String TEST_IMSI = "310260000000000"; + private static final long TIME_A = 1326088800000L; // UTC: Monday 9th January 2012 06:00:00 AM + private static final long TIME_B = 1326110400000L; // UTC: Monday 9th January 2012 12:00:00 PM + private static final long TIME_C = 1326132000000L; // UTC: Monday 9th January 2012 06:00:00 PM + @Override public void setUp() throws Exception { super.setUp(); @@ -198,11 +211,11 @@ public class NetworkStatsCollectionTest extends AndroidTestCase { collection.getRelevantUids(NetworkStatsAccess.Level.DEVICE)); // Verify security check in getHistory. - assertNotNull(collection.getHistory(buildTemplateMobileAll(TEST_IMSI), myUid, SET_DEFAULT, - TAG_NONE, 0, NetworkStatsAccess.Level.DEFAULT)); + assertNotNull(collection.getHistory(buildTemplateMobileAll(TEST_IMSI), null, myUid, SET_DEFAULT, + TAG_NONE, 0, 0L, 0L, NetworkStatsAccess.Level.DEFAULT, myUid)); try { - collection.getHistory(buildTemplateMobileAll(TEST_IMSI), otherUidInSameUser, - SET_DEFAULT, TAG_NONE, 0, NetworkStatsAccess.Level.DEFAULT); + collection.getHistory(buildTemplateMobileAll(TEST_IMSI), null, otherUidInSameUser, + SET_DEFAULT, TAG_NONE, 0, 0L, 0L, NetworkStatsAccess.Level.DEFAULT, myUid); fail("Should have thrown SecurityException for accessing different UID"); } catch (SecurityException e) { // expected @@ -217,6 +230,213 @@ public class NetworkStatsCollectionTest extends AndroidTestCase { 0, NetworkStatsAccess.Level.DEVICE); } + public void testAugmentPlan() throws Exception { + final File testFile = new File(getContext().getFilesDir(), TEST_FILE); + stageFile(R.raw.netstats_v1, testFile); + + final NetworkStatsCollection emptyCollection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS); + final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS); + collection.readLegacyNetwork(testFile); + + // Test a bunch of plans that should result in no augmentation + final List<SubscriptionPlan> plans = new ArrayList<>(); + + // No plan + plans.add(null); + // No usage anchor + plans.add(SubscriptionPlan.Builder + .createRecurringMonthly(ZonedDateTime.parse("2011-01-14T00:00:00.00Z")).build()); + // Usage anchor far in past + plans.add(SubscriptionPlan.Builder + .createRecurringMonthly(ZonedDateTime.parse("2011-01-14T00:00:00.00Z")) + .setDataUsage(1000L, TIME_A - DateUtils.YEAR_IN_MILLIS).build()); + // Usage anchor far in future + plans.add(SubscriptionPlan.Builder + .createRecurringMonthly(ZonedDateTime.parse("2011-01-14T00:00:00.00Z")) + .setDataUsage(1000L, TIME_A + DateUtils.YEAR_IN_MILLIS).build()); + // Usage anchor near but outside cycle + plans.add(SubscriptionPlan.Builder + .createNonrecurring(ZonedDateTime.parse("2012-01-09T09:00:00.00Z"), + ZonedDateTime.parse("2012-01-09T15:00:00.00Z")) + .setDataUsage(1000L, TIME_C).build()); + + for (SubscriptionPlan plan : plans) { + int i; + NetworkStatsHistory history; + + // Empty collection should be untouched + history = getHistory(emptyCollection, plan, TIME_A, TIME_C); + assertEquals(0L, history.getTotalBytes()); + + // Normal collection should be untouched + history = getHistory(collection, plan, TIME_A, TIME_C); i = 0; + assertEntry(100647, 197, 23649, 185, history.getValues(i++, null)); + assertEntry(100647, 196, 23648, 185, history.getValues(i++, null)); + assertEntry(18323, 76, 15032, 76, history.getValues(i++, null)); + assertEntry(18322, 75, 15031, 75, history.getValues(i++, null)); + assertEntry(527798, 761, 78570, 652, history.getValues(i++, null)); + assertEntry(527797, 760, 78570, 651, history.getValues(i++, null)); + assertEntry(10747, 50, 16838, 55, history.getValues(i++, null)); + assertEntry(10747, 49, 16838, 54, history.getValues(i++, null)); + assertEntry(89191, 151, 18021, 140, history.getValues(i++, null)); + assertEntry(89190, 150, 18020, 139, history.getValues(i++, null)); + assertEntry(3821, 22, 4525, 26, history.getValues(i++, null)); + assertEntry(3820, 22, 4524, 26, history.getValues(i++, null)); + assertEntry(91686, 159, 18575, 146, history.getValues(i++, null)); + assertEntry(91685, 159, 18575, 146, history.getValues(i++, null)); + assertEntry(8289, 35, 6863, 38, history.getValues(i++, null)); + assertEntry(8289, 35, 6863, 38, history.getValues(i++, null)); + assertEntry(113914, 174, 18364, 157, history.getValues(i++, null)); + assertEntry(113913, 173, 18364, 157, history.getValues(i++, null)); + assertEntry(11378, 49, 9261, 49, history.getValues(i++, null)); + assertEntry(11377, 48, 9261, 49, history.getValues(i++, null)); + assertEntry(201765, 328, 41808, 291, history.getValues(i++, null)); + assertEntry(201765, 328, 41807, 290, history.getValues(i++, null)); + assertEntry(106106, 218, 39917, 201, history.getValues(i++, null)); + assertEntry(106105, 217, 39917, 201, history.getValues(i++, null)); + assertEquals(history.size(), i); + + // Slice from middle should be untouched + history = getHistory(collection, plan, TIME_B - HOUR_IN_MILLIS, + TIME_B + HOUR_IN_MILLIS); i = 0; + assertEntry(3821, 22, 4525, 26, history.getValues(i++, null)); + assertEntry(3820, 22, 4524, 26, history.getValues(i++, null)); + assertEntry(91686, 159, 18575, 146, history.getValues(i++, null)); + assertEntry(91685, 159, 18575, 146, history.getValues(i++, null)); + assertEquals(history.size(), i); + } + + // Lower anchor in the middle of plan + { + int i; + NetworkStatsHistory history; + + final SubscriptionPlan plan = SubscriptionPlan.Builder + .createNonrecurring(ZonedDateTime.parse("2012-01-09T09:00:00.00Z"), + ZonedDateTime.parse("2012-01-09T15:00:00.00Z")) + .setDataUsage(200000L, TIME_B).build(); + + // Empty collection should be augmented + history = getHistory(emptyCollection, plan, TIME_A, TIME_C); + assertEquals(200000L, history.getTotalBytes()); + + // Normal collection should be augmented + history = getHistory(collection, plan, TIME_A, TIME_C); i = 0; + assertEntry(100647, 197, 23649, 185, history.getValues(i++, null)); + assertEntry(100647, 196, 23648, 185, history.getValues(i++, null)); + assertEntry(18323, 76, 15032, 76, history.getValues(i++, null)); + assertEntry(18322, 75, 15031, 75, history.getValues(i++, null)); + assertEntry(527798, 761, 78570, 652, history.getValues(i++, null)); + assertEntry(527797, 760, 78570, 651, history.getValues(i++, null)); + // Cycle point; start data normalization + assertEntry(7507, 0, 11763, 0, history.getValues(i++, null)); + assertEntry(7507, 0, 11763, 0, history.getValues(i++, null)); + assertEntry(62309, 0, 12589, 0, history.getValues(i++, null)); + assertEntry(62309, 0, 12588, 0, history.getValues(i++, null)); + assertEntry(2669, 0, 3161, 0, history.getValues(i++, null)); + assertEntry(2668, 0, 3160, 0, history.getValues(i++, null)); + // Anchor point; end data normalization + assertEntry(91686, 159, 18575, 146, history.getValues(i++, null)); + assertEntry(91685, 159, 18575, 146, history.getValues(i++, null)); + assertEntry(8289, 35, 6863, 38, history.getValues(i++, null)); + assertEntry(8289, 35, 6863, 38, history.getValues(i++, null)); + assertEntry(113914, 174, 18364, 157, history.getValues(i++, null)); + assertEntry(113913, 173, 18364, 157, history.getValues(i++, null)); + // Cycle point + assertEntry(11378, 49, 9261, 49, history.getValues(i++, null)); + assertEntry(11377, 48, 9261, 49, history.getValues(i++, null)); + assertEntry(201765, 328, 41808, 291, history.getValues(i++, null)); + assertEntry(201765, 328, 41807, 290, history.getValues(i++, null)); + assertEntry(106106, 218, 39917, 201, history.getValues(i++, null)); + assertEntry(106105, 217, 39917, 201, history.getValues(i++, null)); + assertEquals(history.size(), i); + + // Slice from middle should be augmented + history = getHistory(collection, plan, TIME_B - HOUR_IN_MILLIS, + TIME_B + HOUR_IN_MILLIS); i = 0; + assertEntry(2669, 0, 3161, 0, history.getValues(i++, null)); + assertEntry(2668, 0, 3160, 0, history.getValues(i++, null)); + assertEntry(91686, 159, 18575, 146, history.getValues(i++, null)); + assertEntry(91685, 159, 18575, 146, history.getValues(i++, null)); + assertEquals(history.size(), i); + } + + // Higher anchor in the middle of plan + { + int i; + NetworkStatsHistory history; + + final SubscriptionPlan plan = SubscriptionPlan.Builder + .createNonrecurring(ZonedDateTime.parse("2012-01-09T09:00:00.00Z"), + ZonedDateTime.parse("2012-01-09T15:00:00.00Z")) + .setDataUsage(400000L, TIME_B + MINUTE_IN_MILLIS).build(); + + // Empty collection should be augmented + history = getHistory(emptyCollection, plan, TIME_A, TIME_C); + assertEquals(400000L, history.getTotalBytes()); + + // Normal collection should be augmented + history = getHistory(collection, plan, TIME_A, TIME_C); i = 0; + assertEntry(100647, 197, 23649, 185, history.getValues(i++, null)); + assertEntry(100647, 196, 23648, 185, history.getValues(i++, null)); + assertEntry(18323, 76, 15032, 76, history.getValues(i++, null)); + assertEntry(18322, 75, 15031, 75, history.getValues(i++, null)); + assertEntry(527798, 761, 78570, 652, history.getValues(i++, null)); + assertEntry(527797, 760, 78570, 651, history.getValues(i++, null)); + // Cycle point; start data normalization + assertEntry(15015, 0, 23526, 0, history.getValues(i++, null)); + assertEntry(15015, 0, 23526, 0, history.getValues(i++, null)); + assertEntry(124619, 0, 25179, 0, history.getValues(i++, null)); + assertEntry(124618, 0, 25177, 0, history.getValues(i++, null)); + assertEntry(5338, 0, 6322, 0, history.getValues(i++, null)); + assertEntry(5337, 0, 6320, 0, history.getValues(i++, null)); + // Anchor point; end data normalization + assertEntry(91686, 159, 18575, 146, history.getValues(i++, null)); + assertEntry(91685, 159, 18575, 146, history.getValues(i++, null)); + assertEntry(8289, 35, 6863, 38, history.getValues(i++, null)); + assertEntry(8289, 35, 6863, 38, history.getValues(i++, null)); + assertEntry(113914, 174, 18364, 157, history.getValues(i++, null)); + assertEntry(113913, 173, 18364, 157, history.getValues(i++, null)); + // Cycle point + assertEntry(11378, 49, 9261, 49, history.getValues(i++, null)); + assertEntry(11377, 48, 9261, 49, history.getValues(i++, null)); + assertEntry(201765, 328, 41808, 291, history.getValues(i++, null)); + assertEntry(201765, 328, 41807, 290, history.getValues(i++, null)); + assertEntry(106106, 218, 39917, 201, history.getValues(i++, null)); + assertEntry(106105, 217, 39917, 201, history.getValues(i++, null)); + + // Slice from middle should be augmented + history = getHistory(collection, plan, TIME_B - HOUR_IN_MILLIS, + TIME_B + HOUR_IN_MILLIS); i = 0; + assertEntry(5338, 0, 6322, 0, history.getValues(i++, null)); + assertEntry(5337, 0, 6320, 0, history.getValues(i++, null)); + assertEntry(91686, 159, 18575, 146, history.getValues(i++, null)); + assertEntry(91685, 159, 18575, 146, history.getValues(i++, null)); + assertEquals(history.size(), i); + } + } + + public void testRounding() throws Exception { + final NetworkStatsCollection coll = new NetworkStatsCollection(HOUR_IN_MILLIS); + + // Special values should remain unchanged + for (long time : new long[] { + Long.MIN_VALUE, Long.MAX_VALUE, SubscriptionPlan.TIME_UNKNOWN + }) { + assertEquals(time, coll.roundUp(time)); + assertEquals(time, coll.roundDown(time)); + } + + assertEquals(TIME_A, coll.roundUp(TIME_A)); + assertEquals(TIME_A, coll.roundDown(TIME_A)); + + assertEquals(TIME_A + HOUR_IN_MILLIS, coll.roundUp(TIME_A + 1)); + assertEquals(TIME_A, coll.roundDown(TIME_A + 1)); + + assertEquals(TIME_A, coll.roundUp(TIME_A - 1)); + assertEquals(TIME_A - HOUR_IN_MILLIS, coll.roundDown(TIME_A - 1)); + } + /** * Copy a {@link Resources#openRawResource(int)} into {@link File} for * testing purposes. @@ -235,28 +455,50 @@ public class NetworkStatsCollectionTest extends AndroidTestCase { } } + private static NetworkStatsHistory getHistory(NetworkStatsCollection collection, + SubscriptionPlan augmentPlan, long start, long end) { + return collection.getHistory(buildTemplateMobileAll(TEST_IMSI), augmentPlan, UID_ALL, + SET_ALL, TAG_NONE, FIELD_ALL, start, end, NetworkStatsAccess.Level.DEVICE, myUid()); + } + private static void assertSummaryTotal(NetworkStatsCollection collection, NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets, @NetworkStatsAccess.Level int accessLevel) { - final NetworkStats.Entry entry = collection.getSummary( - template, Long.MIN_VALUE, Long.MAX_VALUE, accessLevel) + final NetworkStats.Entry actual = collection.getSummary( + template, Long.MIN_VALUE, Long.MAX_VALUE, accessLevel, myUid()) .getTotal(null); - assertEntry(entry, rxBytes, rxPackets, txBytes, txPackets); + assertEntry(rxBytes, rxPackets, txBytes, txPackets, actual); } private static void assertSummaryTotalIncludingTags(NetworkStatsCollection collection, NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets) { - final NetworkStats.Entry entry = collection.getSummary( - template, Long.MIN_VALUE, Long.MAX_VALUE, NetworkStatsAccess.Level.DEVICE) + final NetworkStats.Entry actual = collection.getSummary( + template, Long.MIN_VALUE, Long.MAX_VALUE, NetworkStatsAccess.Level.DEVICE, myUid()) .getTotalIncludingTags(null); - assertEntry(entry, rxBytes, rxPackets, txBytes, txPackets); + assertEntry(rxBytes, rxPackets, txBytes, txPackets, actual); + } + + private static void assertEntry(long rxBytes, long rxPackets, long txBytes, long txPackets, + NetworkStats.Entry actual) { + assertEntry(new NetworkStats.Entry(rxBytes, rxPackets, txBytes, txPackets, 0L), actual); + } + + private static void assertEntry(long rxBytes, long rxPackets, long txBytes, long txPackets, + NetworkStatsHistory.Entry actual) { + assertEntry(new NetworkStats.Entry(rxBytes, rxPackets, txBytes, txPackets, 0L), actual); + } + + private static void assertEntry(NetworkStats.Entry expected, + NetworkStatsHistory.Entry actual) { + assertEntry(expected, new NetworkStats.Entry(actual.rxBytes, actual.rxPackets, + actual.txBytes, actual.txPackets, 0L)); } - private static void assertEntry( - NetworkStats.Entry entry, long rxBytes, long rxPackets, long txBytes, long txPackets) { - assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes); - assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets); - assertEquals("unexpected txBytes", txBytes, entry.txBytes); - assertEquals("unexpected txPackets", txPackets, entry.txPackets); + private static void assertEntry(NetworkStats.Entry expected, + NetworkStats.Entry actual) { + assertEquals("unexpected rxBytes", expected.rxBytes, actual.rxBytes); + assertEquals("unexpected rxPackets", expected.rxPackets, actual.rxPackets); + assertEquals("unexpected txBytes", expected.txBytes, actual.txBytes); + assertEquals("unexpected txPackets", expected.txPackets, actual.txPackets); } } diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java index fa997958ba6d..814a62663333 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java @@ -46,19 +46,17 @@ import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import static android.text.format.DateUtils.WEEK_IN_MILLIS; -import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL; import static com.android.internal.util.TestUtils.waitForIdleHandler; +import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Mockito.when; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.AlarmManager; import android.app.usage.NetworkStatsManager; @@ -79,24 +77,21 @@ import android.net.NetworkTemplate; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; -import android.os.INetworkManagementService; import android.os.IBinder; +import android.os.INetworkManagementService; import android.os.Looper; -import android.os.Messenger; -import android.os.MessageQueue; -import android.os.MessageQueue.IdleHandler; import android.os.Message; +import android.os.Messenger; import android.os.PowerManager; import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.telephony.TelephonyManager; -import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Log; import android.util.TrustedTime; import com.android.internal.net.VpnInfo; import com.android.internal.util.test.BroadcastInterceptingContext; -import com.android.server.net.NetworkStatsService; import com.android.server.net.NetworkStatsService.NetworkStatsSettings; import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config; @@ -112,9 +107,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.io.File; -import java.util.ArrayList; import java.util.Objects; -import java.util.List; /** * Tests for {@link NetworkStatsService}. @@ -983,7 +976,7 @@ public class NetworkStatsServiceTest { // verify summary API final NetworkStats stats = mSession.getSummaryForNetwork(template, start, end); - assertValues(stats, IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_NO, + assertValues(stats, IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, rxBytes, rxPackets, txBytes, txPackets, operations); } @@ -1107,28 +1100,25 @@ public class NetworkStatsServiceTest { int tag, int metered, int roaming, long rxBytes, long rxPackets, long txBytes, long txPackets, int operations) { final NetworkStats.Entry entry = new NetworkStats.Entry(); - List<Integer> sets = new ArrayList<>(); - if (set == SET_DEFAULT || set == SET_ALL) { - sets.add(SET_DEFAULT); - } - if (set == SET_FOREGROUND || set == SET_ALL) { - sets.add(SET_FOREGROUND); + final int[] sets; + if (set == SET_ALL) { + sets = new int[] { SET_ALL, SET_DEFAULT, SET_FOREGROUND }; + } else { + sets = new int[] { set }; } - List<Integer> roamings = new ArrayList<>(); - if (roaming == ROAMING_NO || roaming == ROAMING_ALL) { - roamings.add(ROAMING_NO); - } - if (roaming == ROAMING_YES || roaming == ROAMING_ALL) { - roamings.add(ROAMING_YES); + final int[] roamings; + if (roaming == ROAMING_ALL) { + roamings = new int[] { ROAMING_ALL, ROAMING_YES, ROAMING_NO }; + } else { + roamings = new int[] { roaming }; } - List<Integer> meterings = new ArrayList<>(); - if (metered == METERED_NO || metered == METERED_ALL) { - meterings.add(METERED_NO); - } - if (metered == METERED_YES || metered == METERED_ALL) { - meterings.add(METERED_YES); + final int[] meterings; + if (metered == METERED_ALL) { + meterings = new int[] { METERED_ALL, METERED_YES, METERED_NO }; + } else { + meterings = new int[] { metered }; } for (int s : sets) { |