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