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