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