• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net;
18 
19 import static android.net.ConnectivityManager.TYPE_MOBILE;
20 import static android.net.NetworkIdentity.OEM_NONE;
21 import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
22 import static android.net.NetworkStats.IFACE_ALL;
23 import static android.net.NetworkStats.METERED_NO;
24 import static android.net.NetworkStats.ROAMING_NO;
25 import static android.net.NetworkStats.SET_ALL;
26 import static android.net.NetworkStats.SET_DEFAULT;
27 import static android.net.NetworkStats.TAG_NONE;
28 import static android.net.NetworkStats.UID_ALL;
29 import static android.net.NetworkStatsHistory.FIELD_ALL;
30 import static android.net.NetworkTemplate.buildTemplateMobileAll;
31 import static android.os.Process.myUid;
32 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
33 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
34 
35 import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational;
36 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
37 import static com.android.testutils.MiscAsserts.assertThrows;
38 
39 import static org.junit.Assert.assertArrayEquals;
40 import static org.junit.Assert.assertEquals;
41 import static org.junit.Assert.assertNotNull;
42 import static org.junit.Assert.fail;
43 
44 import android.annotation.NonNull;
45 import android.content.res.Resources;
46 import android.net.NetworkStatsCollection.Key;
47 import android.os.Process;
48 import android.os.UserHandle;
49 import android.telephony.SubscriptionPlan;
50 import android.telephony.TelephonyManager;
51 import android.text.format.DateUtils;
52 import android.util.ArrayMap;
53 import android.util.RecurrenceRule;
54 
55 import androidx.test.InstrumentationRegistry;
56 import androidx.test.filters.SmallTest;
57 
58 import com.android.frameworks.tests.net.R;
59 import com.android.testutils.DevSdkIgnoreRule;
60 import com.android.testutils.DevSdkIgnoreRunner;
61 
62 import libcore.io.IoUtils;
63 import libcore.io.Streams;
64 
65 import org.junit.After;
66 import org.junit.Before;
67 import org.junit.Rule;
68 import org.junit.Test;
69 import org.junit.runner.RunWith;
70 import org.mockito.Mockito;
71 
72 import java.io.ByteArrayInputStream;
73 import java.io.ByteArrayOutputStream;
74 import java.io.File;
75 import java.io.FileOutputStream;
76 import java.io.InputStream;
77 import java.io.OutputStream;
78 import java.time.Clock;
79 import java.time.Instant;
80 import java.time.ZoneId;
81 import java.time.ZonedDateTime;
82 import java.util.ArrayList;
83 import java.util.List;
84 import java.util.Map;
85 import java.util.Set;
86 
87 /**
88  * Tests for {@link NetworkStatsCollection}.
89  */
90 @RunWith(DevSdkIgnoreRunner.class)
91 @SmallTest
92 @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
93 public class NetworkStatsCollectionTest {
94     @Rule
95     public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
96     private static final String TEST_FILE = "test.bin";
97     private static final String TEST_IMSI = "310260000000000";
98     private static final int TEST_SUBID = 1;
99 
100     private static final long TIME_A = 1326088800000L; // UTC: Monday 9th January 2012 06:00:00 AM
101     private static final long TIME_B = 1326110400000L; // UTC: Monday 9th January 2012 12:00:00 PM
102     private static final long TIME_C = 1326132000000L; // UTC: Monday 9th January 2012 06:00:00 PM
103 
104     private static Clock sOriginalClock;
105 
106     @Before
setUp()107     public void setUp() throws Exception {
108         sOriginalClock = RecurrenceRule.sClock;
109     }
110 
111     @After
tearDown()112     public void tearDown() throws Exception {
113         RecurrenceRule.sClock = sOriginalClock;
114     }
115 
setClock(Instant instant)116     private void setClock(Instant instant) {
117         RecurrenceRule.sClock = Clock.fixed(instant, ZoneId.systemDefault());
118     }
119 
120     @Test
testReadLegacyNetwork()121     public void testReadLegacyNetwork() throws Exception {
122         final File testFile =
123                 new File(InstrumentationRegistry.getContext().getFilesDir(), TEST_FILE);
124         stageFile(R.raw.netstats_v1, testFile);
125 
126         final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
127         collection.readLegacyNetwork(testFile);
128 
129         // verify that history read correctly
130         assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
131                 636014522L, 709291L, 88037144L, 518820L, NetworkStatsAccess.Level.DEVICE);
132 
133         // now export into a unified format
134         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
135         collection.write(bos);
136 
137         // clear structure completely
138         collection.reset();
139         assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
140                 0L, 0L, 0L, 0L, NetworkStatsAccess.Level.DEVICE);
141 
142         // and read back into structure, verifying that totals are same
143         collection.read(new ByteArrayInputStream(bos.toByteArray()));
144         assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
145                 636014522L, 709291L, 88037144L, 518820L, NetworkStatsAccess.Level.DEVICE);
146     }
147 
148     @Test
testReadLegacyUid()149     public void testReadLegacyUid() throws Exception {
150         final File testFile =
151                 new File(InstrumentationRegistry.getContext().getFilesDir(), TEST_FILE);
152         stageFile(R.raw.netstats_uid_v4, testFile);
153 
154         final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
155         collection.readLegacyUid(testFile, false);
156 
157         // verify that history read correctly
158         assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
159                 637073904L, 711398L, 88342093L, 521006L, NetworkStatsAccess.Level.DEVICE);
160 
161         // now export into a unified format
162         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
163         collection.write(bos);
164 
165         // clear structure completely
166         collection.reset();
167         assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
168                 0L, 0L, 0L, 0L, NetworkStatsAccess.Level.DEVICE);
169 
170         // and read back into structure, verifying that totals are same
171         collection.read(new ByteArrayInputStream(bos.toByteArray()));
172         assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
173                 637073904L, 711398L, 88342093L, 521006L, NetworkStatsAccess.Level.DEVICE);
174     }
175 
176     @Test
testReadLegacyUidTags()177     public void testReadLegacyUidTags() throws Exception {
178         final File testFile =
179                 new File(InstrumentationRegistry.getContext().getFilesDir(), TEST_FILE);
180         stageFile(R.raw.netstats_uid_v4, testFile);
181 
182         final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
183         collection.readLegacyUid(testFile, true);
184 
185         // verify that history read correctly
186         assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI),
187                 77017831L, 100995L, 35436758L, 92344L);
188 
189         // now export into a unified format
190         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
191         collection.write(bos);
192 
193         // clear structure completely
194         collection.reset();
195         assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI),
196                 0L, 0L, 0L, 0L);
197 
198         // and read back into structure, verifying that totals are same
199         collection.read(new ByteArrayInputStream(bos.toByteArray()));
200         assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI),
201                 77017831L, 100995L, 35436758L, 92344L);
202     }
203 
getUidInputStreamFromRes(int uidRes)204     private InputStream getUidInputStreamFromRes(int uidRes) throws Exception {
205         final File testFile =
206                 new File(InstrumentationRegistry.getContext().getFilesDir(), TEST_FILE);
207         stageFile(uidRes, testFile);
208 
209         final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
210         collection.readLegacyUid(testFile, true);
211 
212         // now export into a unified format
213         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
214         collection.write(bos);
215         return new ByteArrayInputStream(bos.toByteArray());
216     }
217 
218     @Test
testFastDataInputRead()219     public void testFastDataInputRead() throws Exception {
220         final NetworkStatsCollection legacyCollection =
221                 new NetworkStatsCollection(30 * MINUTE_IN_MILLIS, false /* useFastDataInput */);
222         final NetworkStatsCollection fastReadCollection =
223                 new NetworkStatsCollection(30 * MINUTE_IN_MILLIS, true /* useFastDataInput */);
224         final InputStream bis = getUidInputStreamFromRes(R.raw.netstats_uid_v4);
225         legacyCollection.read(bis);
226         bis.reset();
227         fastReadCollection.read(bis);
228         assertCollectionEntries(legacyCollection.getEntries(), fastReadCollection);
229     }
230 
231     @Test
testStartEndAtomicBuckets()232     public void testStartEndAtomicBuckets() throws Exception {
233         final NetworkStatsCollection collection = new NetworkStatsCollection(HOUR_IN_MILLIS);
234 
235         // record empty data straddling between buckets
236         final NetworkStats.Entry entry = new NetworkStats.Entry();
237         entry.rxBytes = 32;
238         collection.recordData(Mockito.mock(NetworkIdentitySet.class), UID_ALL, SET_DEFAULT,
239                 TAG_NONE, 30 * MINUTE_IN_MILLIS, 90 * MINUTE_IN_MILLIS, entry);
240 
241         // assert that we report boundary in atomic buckets
242         assertEquals(0, collection.getStartMillis());
243         assertEquals(2 * HOUR_IN_MILLIS, collection.getEndMillis());
244     }
245 
246     @Test
testAccessLevels()247     public void testAccessLevels() throws Exception {
248         final NetworkStatsCollection collection = new NetworkStatsCollection(HOUR_IN_MILLIS);
249         final NetworkStats.Entry entry = new NetworkStats.Entry();
250         final NetworkIdentitySet identSet = new NetworkIdentitySet();
251         identSet.add(new NetworkIdentity(TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
252                 TEST_IMSI, null, false, true, true, OEM_NONE, TEST_SUBID));
253 
254         int myUid = Process.myUid();
255         int otherUidInSameUser = Process.myUid() + 1;
256         int uidInDifferentUser = Process.myUid() + UserHandle.PER_USER_RANGE;
257 
258         // Record one entry for the current UID.
259         entry.rxBytes = 32;
260         collection.recordData(identSet, myUid, SET_DEFAULT, TAG_NONE, 0, 60 * MINUTE_IN_MILLIS,
261                 entry);
262 
263         // Record one entry for another UID in this user.
264         entry.rxBytes = 64;
265         collection.recordData(identSet, otherUidInSameUser, SET_DEFAULT, TAG_NONE, 0,
266                 60 * MINUTE_IN_MILLIS, entry);
267 
268         // Record one entry for the system UID.
269         entry.rxBytes = 128;
270         collection.recordData(identSet, Process.SYSTEM_UID, SET_DEFAULT, TAG_NONE, 0,
271                 60 * MINUTE_IN_MILLIS, entry);
272 
273         // Record one entry for a UID in a different user.
274         entry.rxBytes = 256;
275         collection.recordData(identSet, uidInDifferentUser, SET_DEFAULT, TAG_NONE, 0,
276                 60 * MINUTE_IN_MILLIS, entry);
277 
278         // Verify the set of relevant UIDs for each access level.
279         assertArrayEquals(new int[] { myUid },
280                 collection.getRelevantUids(NetworkStatsAccess.Level.DEFAULT));
281         assertArrayEquals(new int[] { Process.SYSTEM_UID, myUid, otherUidInSameUser },
282                 collection.getRelevantUids(NetworkStatsAccess.Level.USER));
283         assertArrayEquals(
284                 new int[] { Process.SYSTEM_UID, myUid, otherUidInSameUser, uidInDifferentUser },
285                 collection.getRelevantUids(NetworkStatsAccess.Level.DEVICE));
286 
287         // Verify security check in getHistory.
288         assertNotNull(collection.getHistory(buildTemplateMobileAll(TEST_IMSI), null,
289                 myUid, SET_DEFAULT, TAG_NONE, 0, 0L, 0L, NetworkStatsAccess.Level.DEFAULT, myUid));
290         try {
291             collection.getHistory(buildTemplateMobileAll(TEST_IMSI), null, otherUidInSameUser,
292                     SET_DEFAULT, TAG_NONE, 0, 0L, 0L, NetworkStatsAccess.Level.DEFAULT, myUid);
293             fail("Should have thrown SecurityException for accessing different UID");
294         } catch (SecurityException e) {
295             // expected
296         }
297 
298         // Verify appropriate aggregation in getSummary.
299         assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), 32, 0, 0, 0,
300                 NetworkStatsAccess.Level.DEFAULT);
301         assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), 32 + 64 + 128, 0, 0, 0,
302                 NetworkStatsAccess.Level.USER);
303         assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), 32 + 64 + 128 + 256, 0, 0,
304                 0, NetworkStatsAccess.Level.DEVICE);
305     }
306 
307     @Test
testAugmentPlan()308     public void testAugmentPlan() throws Exception {
309         final File testFile =
310                 new File(InstrumentationRegistry.getContext().getFilesDir(), TEST_FILE);
311         stageFile(R.raw.netstats_v1, testFile);
312 
313         final NetworkStatsCollection emptyCollection =
314                 new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
315         final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
316         collection.readLegacyNetwork(testFile);
317 
318         // We're in the future, but not that far off
319         setClock(Instant.parse("2012-06-01T00:00:00.00Z"));
320 
321         // Test a bunch of plans that should result in no augmentation
322         final List<SubscriptionPlan> plans = new ArrayList<>();
323 
324         // No plan
325         plans.add(null);
326         // No usage anchor
327         plans.add(SubscriptionPlan.Builder
328                 .createRecurringMonthly(ZonedDateTime.parse("2011-01-14T00:00:00.00Z")).build());
329         // Usage anchor far in past
330         plans.add(SubscriptionPlan.Builder
331                 .createRecurringMonthly(ZonedDateTime.parse("2011-01-14T00:00:00.00Z"))
332                 .setDataUsage(1000L, TIME_A - DateUtils.YEAR_IN_MILLIS).build());
333         // Usage anchor far in future
334         plans.add(SubscriptionPlan.Builder
335                 .createRecurringMonthly(ZonedDateTime.parse("2011-01-14T00:00:00.00Z"))
336                 .setDataUsage(1000L, TIME_A + DateUtils.YEAR_IN_MILLIS).build());
337         // Usage anchor near but outside cycle
338         plans.add(SubscriptionPlan.Builder
339                 .createNonrecurring(ZonedDateTime.parse("2012-01-09T09:00:00.00Z"),
340                         ZonedDateTime.parse("2012-01-09T15:00:00.00Z"))
341                 .setDataUsage(1000L, TIME_C).build());
342 
343         for (SubscriptionPlan plan : plans) {
344             int i;
345             NetworkStatsHistory history;
346 
347             // Empty collection should be untouched
348             history = getHistory(emptyCollection, plan, TIME_A, TIME_C);
349             assertEquals(0L, history.getTotalBytes());
350 
351             // Normal collection should be untouched
352             history = getHistory(collection, plan, TIME_A, TIME_C);
353             i = 0;
354             assertEntry(100647, 197, 23649, 185, history.getValues(i++, null));
355             assertEntry(100647, 196, 23648, 185, history.getValues(i++, null));
356             assertEntry(18323, 76, 15032, 76, history.getValues(i++, null));
357             assertEntry(18322, 75, 15031, 75, history.getValues(i++, null));
358             assertEntry(527798, 761, 78570, 652, history.getValues(i++, null));
359             assertEntry(527797, 760, 78570, 651, history.getValues(i++, null));
360             assertEntry(10747, 50, 16839, 55, history.getValues(i++, null));
361             assertEntry(10747, 49, 16837, 54, history.getValues(i++, null));
362             assertEntry(89191, 151, 18021, 140, history.getValues(i++, null));
363             assertEntry(89190, 150, 18020, 139, history.getValues(i++, null));
364             assertEntry(3821, 23, 4525, 26, history.getValues(i++, null));
365             assertEntry(3820, 21, 4524, 26, history.getValues(i++, null));
366             assertEntry(91686, 159, 18576, 146, history.getValues(i++, null));
367             assertEntry(91685, 159, 18574, 146, history.getValues(i++, null));
368             assertEntry(8289, 36, 6864, 39, history.getValues(i++, null));
369             assertEntry(8289, 34, 6862, 37, history.getValues(i++, null));
370             assertEntry(113914, 174, 18364, 157, history.getValues(i++, null));
371             assertEntry(113913, 173, 18364, 157, history.getValues(i++, null));
372             assertEntry(11378, 49, 9261, 50, history.getValues(i++, null));
373             assertEntry(11377, 48, 9261, 48, history.getValues(i++, null));
374             assertEntry(201766, 328, 41808, 291, history.getValues(i++, null));
375             assertEntry(201764, 328, 41807, 290, history.getValues(i++, null));
376             assertEntry(106106, 219, 39918, 202, history.getValues(i++, null));
377             assertEntry(106105, 216, 39916, 200, history.getValues(i++, null));
378             assertEquals(history.size(), i);
379 
380             // Slice from middle should be untouched
381             history = getHistory(collection, plan, TIME_B - HOUR_IN_MILLIS,
382                     TIME_B + HOUR_IN_MILLIS);
383             i = 0;
384             assertEntry(3821, 23, 4525, 26, history.getValues(i++, null));
385             assertEntry(3820, 21, 4524, 26, history.getValues(i++, null));
386             assertEntry(91686, 159, 18576, 146, history.getValues(i++, null));
387             assertEntry(91685, 159, 18574, 146, history.getValues(i++, null));
388             assertEquals(history.size(), i);
389         }
390 
391         // Lower anchor in the middle of plan
392         {
393             int i;
394             NetworkStatsHistory history;
395 
396             final SubscriptionPlan plan = SubscriptionPlan.Builder
397                     .createNonrecurring(ZonedDateTime.parse("2012-01-09T09:00:00.00Z"),
398                             ZonedDateTime.parse("2012-01-09T15:00:00.00Z"))
399                     .setDataUsage(200000L, TIME_B).build();
400 
401             // Empty collection should be augmented
402             history = getHistory(emptyCollection, plan, TIME_A, TIME_C);
403             assertEquals(200000L, history.getTotalBytes());
404 
405             // Normal collection should be augmented
406             history = getHistory(collection, plan, TIME_A, TIME_C);
407             i = 0;
408             assertEntry(100647, 197, 23649, 185, history.getValues(i++, null));
409             assertEntry(100647, 196, 23648, 185, history.getValues(i++, null));
410             assertEntry(18323, 76, 15032, 76, history.getValues(i++, null));
411             assertEntry(18322, 75, 15031, 75, history.getValues(i++, null));
412             assertEntry(527798, 761, 78570, 652, history.getValues(i++, null));
413             assertEntry(527797, 760, 78570, 651, history.getValues(i++, null));
414             // Cycle point; start data normalization
415             assertEntry(7507, 0, 11763, 0, history.getValues(i++, null));
416             assertEntry(7507, 0, 11762, 0, history.getValues(i++, null));
417             assertEntry(62309, 0, 12589, 0, history.getValues(i++, null));
418             assertEntry(62309, 0, 12588, 0, history.getValues(i++, null));
419             assertEntry(2669, 0, 3161, 0, history.getValues(i++, null));
420             assertEntry(2668, 0, 3160, 0, history.getValues(i++, null));
421             // Anchor point; end data normalization
422             assertEntry(91686, 159, 18576, 146, history.getValues(i++, null));
423             assertEntry(91685, 159, 18574, 146, history.getValues(i++, null));
424             assertEntry(8289, 36, 6864, 39, history.getValues(i++, null));
425             assertEntry(8289, 34, 6862, 37, history.getValues(i++, null));
426             assertEntry(113914, 174, 18364, 157, history.getValues(i++, null));
427             assertEntry(113913, 173, 18364, 157, history.getValues(i++, null));
428             // Cycle point
429             assertEntry(11378, 49, 9261, 50, history.getValues(i++, null));
430             assertEntry(11377, 48, 9261, 48, history.getValues(i++, null));
431             assertEntry(201766, 328, 41808, 291, history.getValues(i++, null));
432             assertEntry(201764, 328, 41807, 290, history.getValues(i++, null));
433             assertEntry(106106, 219, 39918, 202, history.getValues(i++, null));
434             assertEntry(106105, 216, 39916, 200, history.getValues(i++, null));
435             assertEquals(history.size(), i);
436 
437             // Slice from middle should be augmented
438             history = getHistory(collection, plan, TIME_B - HOUR_IN_MILLIS,
439                     TIME_B + HOUR_IN_MILLIS);
440             i = 0;
441             assertEntry(2669, 0, 3161, 0, history.getValues(i++, null));
442             assertEntry(2668, 0, 3160, 0, history.getValues(i++, null));
443             assertEntry(91686, 159, 18576, 146, history.getValues(i++, null));
444             assertEntry(91685, 159, 18574, 146, history.getValues(i++, null));
445             assertEquals(history.size(), i);
446         }
447 
448         // Higher anchor in the middle of plan
449         {
450             int i;
451             NetworkStatsHistory history;
452 
453             final SubscriptionPlan plan = SubscriptionPlan.Builder
454                     .createNonrecurring(ZonedDateTime.parse("2012-01-09T09:00:00.00Z"),
455                             ZonedDateTime.parse("2012-01-09T15:00:00.00Z"))
456                     .setDataUsage(400000L, TIME_B + MINUTE_IN_MILLIS).build();
457 
458             // Empty collection should be augmented
459             history = getHistory(emptyCollection, plan, TIME_A, TIME_C);
460             assertEquals(400000L, history.getTotalBytes());
461 
462             // Normal collection should be augmented
463             history = getHistory(collection, plan, TIME_A, TIME_C);
464             i = 0;
465             assertEntry(100647, 197, 23649, 185, history.getValues(i++, null));
466             assertEntry(100647, 196, 23648, 185, history.getValues(i++, null));
467             assertEntry(18323, 76, 15032, 76, history.getValues(i++, null));
468             assertEntry(18322, 75, 15031, 75, history.getValues(i++, null));
469             assertEntry(527798, 761, 78570, 652, history.getValues(i++, null));
470             assertEntry(527797, 760, 78570, 651, history.getValues(i++, null));
471             // Cycle point; start data normalization
472             assertEntry(15015, 0, 23527, 0, history.getValues(i++, null));
473             assertEntry(15015, 0, 23524, 0, history.getValues(i++, null));
474             assertEntry(124619, 0, 25179, 0, history.getValues(i++, null));
475             assertEntry(124618, 0, 25177, 0, history.getValues(i++, null));
476             assertEntry(5338, 0, 6322, 0, history.getValues(i++, null));
477             assertEntry(5337, 0, 6320, 0, history.getValues(i++, null));
478             // Anchor point; end data normalization
479             assertEntry(91686, 159, 18576, 146, history.getValues(i++, null));
480             assertEntry(91685, 159, 18574, 146, history.getValues(i++, null));
481             assertEntry(8289, 36, 6864, 39, history.getValues(i++, null));
482             assertEntry(8289, 34, 6862, 37, history.getValues(i++, null));
483             assertEntry(113914, 174, 18364, 157, history.getValues(i++, null));
484             assertEntry(113913, 173, 18364, 157, history.getValues(i++, null));
485             // Cycle point
486             assertEntry(11378, 49, 9261, 50, history.getValues(i++, null));
487             assertEntry(11377, 48, 9261, 48, history.getValues(i++, null));
488             assertEntry(201766, 328, 41808, 291, history.getValues(i++, null));
489             assertEntry(201764, 328, 41807, 290, history.getValues(i++, null));
490             assertEntry(106106, 219, 39918, 202, history.getValues(i++, null));
491             assertEntry(106105, 216, 39916, 200, history.getValues(i++, null));
492 
493             // Slice from middle should be augmented
494             history = getHistory(collection, plan, TIME_B - HOUR_IN_MILLIS,
495                     TIME_B + HOUR_IN_MILLIS);
496             i = 0;
497             assertEntry(5338, 0, 6322, 0, history.getValues(i++, null));
498             assertEntry(5337, 0, 6320, 0, history.getValues(i++, null));
499             assertEntry(91686, 159, 18576, 146, history.getValues(i++, null));
500             assertEntry(91685, 159, 18574, 146, history.getValues(i++, null));
501             assertEquals(history.size(), i);
502         }
503     }
504 
505     @Test
testAugmentPlanGigantic()506     public void testAugmentPlanGigantic() throws Exception {
507         // We're in the future, but not that far off
508         setClock(Instant.parse("2012-06-01T00:00:00.00Z"));
509 
510         // Create a simple history with a ton of measured usage
511         final NetworkStatsCollection large = new NetworkStatsCollection(HOUR_IN_MILLIS);
512         final NetworkIdentitySet ident = new NetworkIdentitySet();
513         ident.add(new NetworkIdentity(ConnectivityManager.TYPE_MOBILE, -1, TEST_IMSI, null,
514                 false, true, true, OEM_NONE, TEST_SUBID));
515         large.recordData(ident, UID_ALL, SET_ALL, TAG_NONE, TIME_A, TIME_B,
516                 new NetworkStats.Entry(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO,
517                 ROAMING_NO, DEFAULT_NETWORK_NO, 12_730_893_164L, 1, 0, 0, 0));
518 
519         // Verify untouched total
520         assertEquals(12_730_893_164L, getHistory(large, null, TIME_A, TIME_C).getTotalBytes());
521 
522         // Verify anchor that might cause overflows
523         final SubscriptionPlan plan = SubscriptionPlan.Builder
524                 .createRecurringMonthly(ZonedDateTime.parse("2012-01-09T00:00:00.00Z"))
525                 .setDataUsage(4_939_212_390L, TIME_B).build();
526         assertEquals(4_939_212_386L, getHistory(large, plan, TIME_A, TIME_C).getTotalBytes());
527     }
528 
529     @Test
testRounding()530     public void testRounding() throws Exception {
531         final NetworkStatsCollection coll = new NetworkStatsCollection(HOUR_IN_MILLIS);
532 
533         // Special values should remain unchanged
534         for (long time : new long[] {
535                 Long.MIN_VALUE, Long.MAX_VALUE, SubscriptionPlan.TIME_UNKNOWN
536         }) {
537             assertEquals(time, coll.roundUp(time));
538             assertEquals(time, coll.roundDown(time));
539         }
540 
541         assertEquals(TIME_A, coll.roundUp(TIME_A));
542         assertEquals(TIME_A, coll.roundDown(TIME_A));
543 
544         assertEquals(TIME_A + HOUR_IN_MILLIS, coll.roundUp(TIME_A + 1));
545         assertEquals(TIME_A, coll.roundDown(TIME_A + 1));
546 
547         assertEquals(TIME_A, coll.roundUp(TIME_A - 1));
548         assertEquals(TIME_A - HOUR_IN_MILLIS, coll.roundDown(TIME_A - 1));
549     }
550 
551     @Test
testMultiplySafeRational()552     public void testMultiplySafeRational() {
553         assertEquals(25, multiplySafeByRational(50, 1, 2));
554         assertEquals(100, multiplySafeByRational(50, 2, 1));
555 
556         assertEquals(-10, multiplySafeByRational(30, -1, 3));
557         assertEquals(0, multiplySafeByRational(30, 0, 3));
558         assertEquals(10, multiplySafeByRational(30, 1, 3));
559         assertEquals(20, multiplySafeByRational(30, 2, 3));
560         assertEquals(30, multiplySafeByRational(30, 3, 3));
561         assertEquals(40, multiplySafeByRational(30, 4, 3));
562 
563         assertEquals(100_000_000_000L,
564                 multiplySafeByRational(300_000_000_000L, 10_000_000_000L, 30_000_000_000L));
565         assertEquals(100_000_000_010L,
566                 multiplySafeByRational(300_000_000_000L, 10_000_000_001L, 30_000_000_000L));
567         assertEquals(823_202_048L,
568                 multiplySafeByRational(4_939_212_288L, 2_121_815_528L, 12_730_893_165L));
569 
570         assertThrows(ArithmeticException.class, () -> multiplySafeByRational(30, 3, 0));
571     }
572 
assertCollectionEntries( @onNull Map<Key, NetworkStatsHistory> expectedEntries, @NonNull NetworkStatsCollection collection)573     private static void assertCollectionEntries(
574             @NonNull Map<Key, NetworkStatsHistory> expectedEntries,
575             @NonNull NetworkStatsCollection collection) {
576         final Map<Key, NetworkStatsHistory> actualEntries = collection.getEntries();
577         assertEquals(expectedEntries.size(), actualEntries.size());
578         for (Key expectedKey : expectedEntries.keySet()) {
579             final NetworkStatsHistory expectedHistory = expectedEntries.get(expectedKey);
580             final NetworkStatsHistory actualHistory = actualEntries.get(expectedKey);
581             assertNotNull(actualHistory);
582             assertEquals(expectedHistory.getEntries(), actualHistory.getEntries());
583             actualEntries.remove(expectedKey);
584         }
585         assertEquals(0, actualEntries.size());
586     }
587 
588     @Test
testRemoveHistoryBefore()589     public void testRemoveHistoryBefore() {
590         final NetworkIdentity testIdent = new NetworkIdentity.Builder()
591                 .setSubscriberId(TEST_IMSI).build();
592         final Key key1 = new Key(Set.of(testIdent), 0, 0, 0);
593         final Key key2 = new Key(Set.of(testIdent), 1, 0, 0);
594         final long bucketDuration = 10;
595 
596         // Prepare entries for testing, with different bucket start timestamps.
597         final NetworkStatsHistory.Entry entry1 = new NetworkStatsHistory.Entry(10, 10, 40,
598                 4, 50, 5, 60);
599         final NetworkStatsHistory.Entry entry2 = new NetworkStatsHistory.Entry(20, 10, 3,
600                 41, 7, 1, 0);
601         final NetworkStatsHistory.Entry entry3 = new NetworkStatsHistory.Entry(30, 10, 1,
602                 21, 70, 4, 1);
603 
604         NetworkStatsHistory history1 = new NetworkStatsHistory.Builder(10, 5)
605                 .addEntry(entry1)
606                 .addEntry(entry2)
607                 .build();
608         NetworkStatsHistory history2 = new NetworkStatsHistory.Builder(10, 5)
609                 .addEntry(entry2)
610                 .addEntry(entry3)
611                 .build();
612         NetworkStatsCollection collection = new NetworkStatsCollection.Builder(bucketDuration)
613                 .addEntry(key1, history1)
614                 .addEntry(key2, history2)
615                 .build();
616 
617         // Verify nothing is removed if the cutoff time is equal to bucketStart.
618         collection.removeHistoryBefore(10);
619         final Map<Key, NetworkStatsHistory> expectedEntries = new ArrayMap<>();
620         expectedEntries.put(key1, history1);
621         expectedEntries.put(key2, history2);
622         assertCollectionEntries(expectedEntries, collection);
623 
624         // Verify entry1 will be removed if its bucket start before to cutoff timestamp.
625         collection.removeHistoryBefore(11);
626         history1 = new NetworkStatsHistory.Builder(10, 5)
627                 .addEntry(entry2)
628                 .build();
629         history2 = new NetworkStatsHistory.Builder(10, 5)
630                 .addEntry(entry2)
631                 .addEntry(entry3)
632                 .build();
633         final Map<Key, NetworkStatsHistory> cutoff1Entries1 = new ArrayMap<>();
634         cutoff1Entries1.put(key1, history1);
635         cutoff1Entries1.put(key2, history2);
636         assertCollectionEntries(cutoff1Entries1, collection);
637 
638         // Verify entry2 will be removed if its bucket start covers by cutoff timestamp.
639         collection.removeHistoryBefore(22);
640         history2 = new NetworkStatsHistory.Builder(10, 5)
641                 .addEntry(entry3)
642                 .build();
643         final Map<Key, NetworkStatsHistory> cutoffEntries2 = new ArrayMap<>();
644         // History1 is not expected since the collection will omit empty entries.
645         cutoffEntries2.put(key2, history2);
646         assertCollectionEntries(cutoffEntries2, collection);
647 
648         // Verify all entries will be removed if cutoff timestamp covers all.
649         collection.removeHistoryBefore(Long.MAX_VALUE);
650         assertEquals(0, collection.getEntries().size());
651     }
652 
653     /**
654      * Copy a {@link Resources#openRawResource(int)} into {@link File} for
655      * testing purposes.
656      */
stageFile(int rawId, File file)657     private void stageFile(int rawId, File file) throws Exception {
658         new File(file.getParent()).mkdirs();
659         InputStream in = null;
660         OutputStream out = null;
661         try {
662             in = InstrumentationRegistry.getContext().getResources().openRawResource(rawId);
663             out = new FileOutputStream(file);
664             Streams.copy(in, out);
665         } finally {
666             IoUtils.closeQuietly(in);
667             IoUtils.closeQuietly(out);
668         }
669     }
670 
getHistory(NetworkStatsCollection collection, SubscriptionPlan augmentPlan, long start, long end)671     private static NetworkStatsHistory getHistory(NetworkStatsCollection collection,
672             SubscriptionPlan augmentPlan, long start, long end) {
673         return collection.getHistory(buildTemplateMobileAll(TEST_IMSI), augmentPlan, UID_ALL,
674                 SET_ALL, TAG_NONE, FIELD_ALL, start, end, NetworkStatsAccess.Level.DEVICE, myUid());
675     }
676 
assertSummaryTotal(NetworkStatsCollection collection, NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets, @NetworkStatsAccess.Level int accessLevel)677     private static void assertSummaryTotal(NetworkStatsCollection collection,
678             NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets,
679             @NetworkStatsAccess.Level int accessLevel) {
680         final NetworkStats.Entry actual = collection.getSummary(
681                 template, Long.MIN_VALUE, Long.MAX_VALUE, accessLevel, myUid())
682                 .getTotal(null);
683         assertEntry(rxBytes, rxPackets, txBytes, txPackets, actual);
684     }
685 
assertSummaryTotalIncludingTags(NetworkStatsCollection collection, NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets)686     private static void assertSummaryTotalIncludingTags(NetworkStatsCollection collection,
687             NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets) {
688         final NetworkStats.Entry actual = collection.getSummary(
689                 template, Long.MIN_VALUE, Long.MAX_VALUE, NetworkStatsAccess.Level.DEVICE, myUid())
690                 .getTotalIncludingTags(null);
691         assertEntry(rxBytes, rxPackets, txBytes, txPackets, actual);
692     }
693 
assertEntry(long rxBytes, long rxPackets, long txBytes, long txPackets, NetworkStats.Entry actual)694     private static void assertEntry(long rxBytes, long rxPackets, long txBytes, long txPackets,
695             NetworkStats.Entry actual) {
696         assertEntry(new NetworkStats.Entry(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO,
697                 ROAMING_NO, DEFAULT_NETWORK_NO, rxBytes, rxPackets, txBytes, txPackets, 0L),
698                 actual);
699     }
700 
assertEntry(long rxBytes, long rxPackets, long txBytes, long txPackets, NetworkStatsHistory.Entry actual)701     private static void assertEntry(long rxBytes, long rxPackets, long txBytes, long txPackets,
702             NetworkStatsHistory.Entry actual) {
703         assertEntry(new NetworkStats.Entry(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO,
704                 ROAMING_NO, DEFAULT_NETWORK_NO, rxBytes, rxPackets, txBytes, txPackets, 0L),
705                 actual);
706     }
707 
assertEntry(NetworkStats.Entry expected, NetworkStatsHistory.Entry actual)708     private static void assertEntry(NetworkStats.Entry expected,
709             NetworkStatsHistory.Entry actual) {
710         assertEntry(expected, new NetworkStats.Entry(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE,
711                 METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, actual.rxBytes, actual.rxPackets,
712                 actual.txBytes, actual.txPackets, 0L));
713     }
714 
assertEntry(NetworkStatsHistory.Entry expected, NetworkStatsHistory.Entry actual)715     private static void assertEntry(NetworkStatsHistory.Entry expected,
716             NetworkStatsHistory.Entry actual) {
717         assertEntry(new NetworkStats.Entry(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO,
718                         ROAMING_NO, DEFAULT_NETWORK_NO, actual.rxBytes, actual.rxPackets,
719                        actual.txBytes, actual.txPackets, 0L),
720                 new NetworkStats.Entry(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO,
721                         ROAMING_NO, DEFAULT_NETWORK_NO, actual.rxBytes, actual.rxPackets,
722                         actual.txBytes, actual.txPackets, 0L));
723     }
724 
assertEntry(NetworkStats.Entry expected, NetworkStats.Entry actual)725     private static void assertEntry(NetworkStats.Entry expected,
726             NetworkStats.Entry actual) {
727         assertEquals("unexpected rxBytes", expected.rxBytes, actual.rxBytes);
728         assertEquals("unexpected rxPackets", expected.rxPackets, actual.rxPackets);
729         assertEquals("unexpected txBytes", expected.txBytes, actual.txBytes);
730         assertEquals("unexpected txPackets", expected.txPackets, actual.txPackets);
731     }
732 }
733