Augment network stats based on SubscriptionPlan.
When a carrier provides an "anchor" of data usage at a specific
moment in time, augment the network statistics used by warning/limit
thresholds and Settings UI. For example, if the OS measured 500MB
of usage, but the carrier says only 400MB has been used, we "squish"
down the OS measured usage to match that anchor.
Callers using the hidden API will have their data augmented by
default, and the public API offers a way to opt-into augmentation.
Thorough testing to verify behavior.
Test: bit FrameworksNetTests:android.net.,com.android.server.net.
Test: cts-tradefed run commandAndExit cts-dev -m CtsUsageStatsTestCases -t android.app.usage.cts.NetworkUsageStatsTest
Bug: 64534190
Change-Id: Id3d4d7625bbf04f57643e51dbf376e3fa0ea8eca
diff --git a/core/java/android/app/usage/NetworkStats.java b/core/java/android/app/usage/NetworkStats.java
index 3670b91..222e9a0 100644
--- a/core/java/android/app/usage/NetworkStats.java
+++ b/core/java/android/app/usage/NetworkStats.java
@@ -97,12 +97,12 @@
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 ef262e0..853b003 100644
--- a/core/java/android/app/usage/NetworkStatsManager.java
+++ b/core/java/android/app/usage/NetworkStatsManager.java
@@ -24,15 +24,14 @@
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 @@
* 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 @@
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 @@
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 @@
}
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 @@
}
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 @@
}
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 @@
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 @@
}
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 e693009..9180112 100644
--- a/core/java/android/net/INetworkStatsService.aidl
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -36,7 +36,7 @@
* 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 5f521de..433f941 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -27,6 +27,7 @@
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 @@
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 0d2fcd0..b307c5d 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -326,6 +326,10 @@
}
}
+ 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 dc9a26e..ada7b2b 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8004,6 +8004,8 @@
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 e292c9d..24a4bae 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -253,6 +253,7 @@
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 0354300..8837c15 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.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.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.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 @@
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 @@
* 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 @@
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 a256cbc..741c206 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 @@
*/
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 80309e1..4bee55e 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.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.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 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.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 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 b14aa13..4bd927d 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.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_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 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.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 @@
* 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 long getPollInterval();
public long getTimeCacheMaxAge();
public boolean getSampleEnabled();
+ public boolean getAugmentEnabled();
public static class Config {
public final long bucketDuration;
@@ -466,18 +474,20 @@
@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 @@
// 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 @@
@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 @@
@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 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 @@
}
/**
+ * 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 @@
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 e7b91b5..1c0c14e 100644
--- a/tests/net/java/android/net/NetworkStatsHistoryTest.java
+++ b/tests/net/java/android/net/NetworkStatsHistoryTest.java
@@ -485,6 +485,21 @@
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 2a32b73..0c2ad38 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.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 @@
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 @@
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 @@
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 @@
}
}
+ 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(
- 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(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 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 fa99795..814a626 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.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.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.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 @@
// 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 @@
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) {