1 /* 2 * Copyright (C) 2015 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 package android.net.cts; 17 18 import static android.app.usage.NetworkStats.Bucket.DEFAULT_NETWORK_ALL; 19 import static android.app.usage.NetworkStats.Bucket.DEFAULT_NETWORK_NO; 20 import static android.app.usage.NetworkStats.Bucket.DEFAULT_NETWORK_YES; 21 import static android.app.usage.NetworkStats.Bucket.METERED_ALL; 22 import static android.app.usage.NetworkStats.Bucket.METERED_NO; 23 import static android.app.usage.NetworkStats.Bucket.METERED_YES; 24 import static android.app.usage.NetworkStats.Bucket.ROAMING_ALL; 25 import static android.app.usage.NetworkStats.Bucket.ROAMING_NO; 26 import static android.app.usage.NetworkStats.Bucket.ROAMING_YES; 27 import static android.app.usage.NetworkStats.Bucket.STATE_ALL; 28 import static android.app.usage.NetworkStats.Bucket.STATE_DEFAULT; 29 import static android.app.usage.NetworkStats.Bucket.STATE_FOREGROUND; 30 import static android.app.usage.NetworkStats.Bucket.TAG_NONE; 31 import static android.app.usage.NetworkStats.Bucket.UID_ALL; 32 import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID; 33 import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID_TAG; 34 import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_XT; 35 36 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2; 37 38 import static org.junit.Assert.assertEquals; 39 import static org.junit.Assert.assertFalse; 40 import static org.junit.Assert.assertNotNull; 41 import static org.junit.Assert.assertTrue; 42 import static org.junit.Assert.fail; 43 44 import android.app.AppOpsManager; 45 import android.app.Instrumentation; 46 import android.app.usage.NetworkStats; 47 import android.app.usage.NetworkStatsManager; 48 import android.content.Context; 49 import android.content.pm.PackageManager; 50 import android.net.ConnectivityManager; 51 import android.net.Network; 52 import android.net.NetworkCapabilities; 53 import android.net.NetworkInfo; 54 import android.net.NetworkRequest; 55 import android.net.NetworkStatsCollection; 56 import android.net.NetworkStatsHistory; 57 import android.net.TrafficStats; 58 import android.net.netstats.NetworkStatsDataMigrationUtils; 59 import android.os.Build; 60 import android.os.Handler; 61 import android.os.HandlerThread; 62 import android.os.Process; 63 import android.os.RemoteException; 64 import android.os.SystemClock; 65 import android.platform.test.annotations.AppModeFull; 66 import android.telephony.TelephonyManager; 67 import android.text.TextUtils; 68 import android.util.Log; 69 70 import androidx.test.InstrumentationRegistry; 71 import androidx.test.ext.junit.runners.AndroidJUnit4; 72 73 import com.android.compatibility.common.util.ShellIdentityUtils; 74 import com.android.compatibility.common.util.SystemUtil; 75 import com.android.modules.utils.build.SdkLevel; 76 import com.android.testutils.ConnectivityModuleTest; 77 import com.android.testutils.DevSdkIgnoreRule; 78 79 import org.junit.After; 80 import org.junit.Before; 81 import org.junit.Rule; 82 import org.junit.Test; 83 import org.junit.runner.RunWith; 84 85 import java.io.IOException; 86 import java.io.InputStream; 87 import java.io.InputStreamReader; 88 import java.net.HttpURLConnection; 89 import java.net.URL; 90 import java.net.UnknownHostException; 91 import java.text.MessageFormat; 92 import java.util.ArrayList; 93 import java.util.List; 94 import java.util.Map; 95 import java.util.Set; 96 import java.util.concurrent.TimeUnit; 97 98 @ConnectivityModuleTest 99 @AppModeFull(reason = "instant apps cannot be granted USAGE_STATS") 100 @RunWith(AndroidJUnit4.class) 101 public class NetworkStatsManagerTest { 102 @Rule 103 public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule(Build.VERSION_CODES.Q); 104 105 private static final String LOG_TAG = "NetworkStatsManagerTest"; 106 private static final String APPOPS_SET_SHELL_COMMAND = "appops set {0} {1} {2}"; 107 private static final String APPOPS_GET_SHELL_COMMAND = "appops get {0} {1}"; 108 109 private static final long MINUTE = 1000 * 60; 110 private static final int TIMEOUT_MILLIS = 15000; 111 112 private static final String CHECK_CONNECTIVITY_URL = "http://www.265.com/"; 113 private static final int HOST_RESOLUTION_RETRIES = 4; 114 private static final int HOST_RESOLUTION_INTERVAL_MS = 500; 115 116 private static final int NETWORK_TAG = 0xf00d; 117 private static final long THRESHOLD_BYTES = 2 * 1024 * 1024; // 2 MB 118 119 private abstract class NetworkInterfaceToTest { 120 private boolean mMetered; 121 private boolean mRoaming; 122 private boolean mIsDefault; 123 getNetworkType()124 abstract int getNetworkType(); getTransportType()125 abstract int getTransportType(); 126 getMetered()127 public boolean getMetered() { 128 return mMetered; 129 } 130 setMetered(boolean metered)131 public void setMetered(boolean metered) { 132 this.mMetered = metered; 133 } 134 getRoaming()135 public boolean getRoaming() { 136 return mRoaming; 137 } 138 setRoaming(boolean roaming)139 public void setRoaming(boolean roaming) { 140 this.mRoaming = roaming; 141 } 142 getIsDefault()143 public boolean getIsDefault() { 144 return mIsDefault; 145 } 146 setIsDefault(boolean isDefault)147 public void setIsDefault(boolean isDefault) { 148 mIsDefault = isDefault; 149 } 150 getSystemFeature()151 abstract String getSystemFeature(); getErrorMessage()152 abstract String getErrorMessage(); 153 } 154 155 private final NetworkInterfaceToTest[] mNetworkInterfacesToTest = 156 new NetworkInterfaceToTest[] { 157 new NetworkInterfaceToTest() { 158 @Override 159 public int getNetworkType() { 160 return ConnectivityManager.TYPE_WIFI; 161 } 162 163 @Override 164 public int getTransportType() { 165 return NetworkCapabilities.TRANSPORT_WIFI; 166 } 167 168 @Override 169 public String getSystemFeature() { 170 return PackageManager.FEATURE_WIFI; 171 } 172 173 @Override 174 public String getErrorMessage() { 175 return " Please make sure you are connected to a WiFi access point."; 176 } 177 }, 178 new NetworkInterfaceToTest() { 179 @Override 180 public int getNetworkType() { 181 return ConnectivityManager.TYPE_MOBILE; 182 } 183 184 @Override 185 public int getTransportType() { 186 return NetworkCapabilities.TRANSPORT_CELLULAR; 187 } 188 189 @Override 190 public String getSystemFeature() { 191 return PackageManager.FEATURE_TELEPHONY; 192 } 193 194 @Override 195 public String getErrorMessage() { 196 return " Please make sure you have added a SIM card with data plan to" 197 + " your phone, have enabled data over cellular and in case of" 198 + " dual SIM devices, have selected the right SIM " 199 + "for data connection."; 200 } 201 } 202 }; 203 204 private String mPkg; 205 private Context mContext; 206 private NetworkStatsManager mNsm; 207 private ConnectivityManager mCm; 208 private PackageManager mPm; 209 private Instrumentation mInstrumentation; 210 private long mStartTime; 211 private long mEndTime; 212 213 private long mBytesRead; 214 private String mWriteSettingsMode; 215 private String mUsageStatsMode; 216 exerciseRemoteHost(Network network, URL url)217 private void exerciseRemoteHost(Network network, URL url) throws Exception { 218 NetworkInfo networkInfo = mCm.getNetworkInfo(network); 219 if (networkInfo == null) { 220 Log.w(LOG_TAG, "Network info is null"); 221 } else { 222 Log.w(LOG_TAG, "Network: " + networkInfo.toString()); 223 } 224 InputStreamReader in = null; 225 HttpURLConnection urlc = null; 226 String originalKeepAlive = System.getProperty("http.keepAlive"); 227 System.setProperty("http.keepAlive", "false"); 228 try { 229 TrafficStats.setThreadStatsTag(NETWORK_TAG); 230 urlc = (HttpURLConnection) network.openConnection(url); 231 urlc.setConnectTimeout(TIMEOUT_MILLIS); 232 urlc.setUseCaches(false); 233 // Disable compression so we generate enough traffic that assertWithinPercentage will 234 // not be affected by the small amount of traffic (5-10kB) sent by the test harness. 235 urlc.setRequestProperty("Accept-Encoding", "identity"); 236 urlc.connect(); 237 boolean ping = urlc.getResponseCode() == 200; 238 if (ping) { 239 in = new InputStreamReader( 240 (InputStream) urlc.getContent()); 241 242 mBytesRead = 0; 243 while (in.read() != -1) ++mBytesRead; 244 } 245 } catch (Exception e) { 246 Log.i(LOG_TAG, "Badness during exercising remote server: " + e); 247 } finally { 248 TrafficStats.clearThreadStatsTag(); 249 if (in != null) { 250 try { 251 in.close(); 252 } catch (IOException e) { 253 // don't care 254 } 255 } 256 if (urlc != null) { 257 urlc.disconnect(); 258 } 259 if (originalKeepAlive == null) { 260 System.clearProperty("http.keepAlive"); 261 } else { 262 System.setProperty("http.keepAlive", originalKeepAlive); 263 } 264 } 265 } 266 267 @Before setUp()268 public void setUp() throws Exception { 269 mContext = InstrumentationRegistry.getContext(); 270 mNsm = mContext.getSystemService(NetworkStatsManager.class); 271 mNsm.setPollForce(true); 272 273 mCm = mContext.getSystemService(ConnectivityManager.class); 274 mPm = mContext.getPackageManager(); 275 mPkg = mContext.getPackageName(); 276 277 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 278 mWriteSettingsMode = getAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS); 279 setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, "allow"); 280 mUsageStatsMode = getAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS); 281 } 282 283 @After tearDown()284 public void tearDown() throws Exception { 285 if (mWriteSettingsMode != null) { 286 setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, mWriteSettingsMode); 287 } 288 if (mUsageStatsMode != null) { 289 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, mUsageStatsMode); 290 } 291 } 292 setAppOpsMode(String appop, String mode)293 private void setAppOpsMode(String appop, String mode) throws Exception { 294 final String command = MessageFormat.format(APPOPS_SET_SHELL_COMMAND, mPkg, appop, mode); 295 SystemUtil.runShellCommand(mInstrumentation, command); 296 } 297 getAppOpsMode(String appop)298 private String getAppOpsMode(String appop) throws Exception { 299 final String command = MessageFormat.format(APPOPS_GET_SHELL_COMMAND, mPkg, appop); 300 String result = SystemUtil.runShellCommand(mInstrumentation, command); 301 if (result == null) { 302 Log.w(LOG_TAG, "App op " + appop + " could not be read."); 303 } 304 return result; 305 } 306 isInForeground()307 private boolean isInForeground() throws IOException { 308 String result = SystemUtil.runShellCommand(mInstrumentation, 309 "cmd activity get-uid-state " + Process.myUid()); 310 return result.contains("FOREGROUND"); 311 } 312 313 private class NetworkCallback extends ConnectivityManager.NetworkCallback { 314 private long mTolerance; 315 private URL mUrl; 316 public boolean success; 317 public boolean metered; 318 public boolean roaming; 319 public boolean isDefault; 320 NetworkCallback(long tolerance, URL url)321 NetworkCallback(long tolerance, URL url) { 322 mTolerance = tolerance; 323 mUrl = url; 324 success = false; 325 metered = false; 326 roaming = false; 327 isDefault = false; 328 } 329 330 // The test host only has IPv4. So on a dual-stack network where IPv6 connects before IPv4, 331 // we need to wait until IPv4 is available or the test will spuriously fail. waitForHostResolution(Network network)332 private void waitForHostResolution(Network network) { 333 for (int i = 0; i < HOST_RESOLUTION_RETRIES; i++) { 334 try { 335 network.getAllByName(mUrl.getHost()); 336 return; 337 } catch (UnknownHostException e) { 338 SystemClock.sleep(HOST_RESOLUTION_INTERVAL_MS); 339 } 340 } 341 fail(String.format("%s could not be resolved on network %s (%d attempts %dms apart)", 342 mUrl.getHost(), network, HOST_RESOLUTION_RETRIES, HOST_RESOLUTION_INTERVAL_MS)); 343 } 344 345 @Override onAvailable(Network network)346 public void onAvailable(Network network) { 347 try { 348 mStartTime = System.currentTimeMillis() - mTolerance; 349 isDefault = network.equals(mCm.getActiveNetwork()); 350 waitForHostResolution(network); 351 exerciseRemoteHost(network, mUrl); 352 mEndTime = System.currentTimeMillis() + mTolerance; 353 success = true; 354 metered = !mCm.getNetworkCapabilities(network) 355 .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); 356 roaming = !mCm.getNetworkCapabilities(network) 357 .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING); 358 synchronized (NetworkStatsManagerTest.this) { 359 NetworkStatsManagerTest.this.notify(); 360 } 361 } catch (Exception e) { 362 Log.w(LOG_TAG, "exercising remote host failed.", e); 363 success = false; 364 } 365 } 366 } 367 shouldTestThisNetworkType(int networkTypeIndex, final long tolerance)368 private boolean shouldTestThisNetworkType(int networkTypeIndex, final long tolerance) 369 throws Exception { 370 boolean hasFeature = mPm.hasSystemFeature( 371 mNetworkInterfacesToTest[networkTypeIndex].getSystemFeature()); 372 if (!hasFeature) { 373 return false; 374 } 375 NetworkCallback callback = new NetworkCallback(tolerance, new URL(CHECK_CONNECTIVITY_URL)); 376 mCm.requestNetwork(new NetworkRequest.Builder() 377 .addTransportType(mNetworkInterfacesToTest[networkTypeIndex].getTransportType()) 378 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) 379 .build(), callback); 380 synchronized (this) { 381 try { 382 wait((int) (TIMEOUT_MILLIS * 1.2)); 383 } catch (InterruptedException e) { 384 } 385 } 386 if (callback.success) { 387 mNetworkInterfacesToTest[networkTypeIndex].setMetered(callback.metered); 388 mNetworkInterfacesToTest[networkTypeIndex].setRoaming(callback.roaming); 389 mNetworkInterfacesToTest[networkTypeIndex].setIsDefault(callback.isDefault); 390 return true; 391 } 392 393 // This will always fail at this point as we know 'hasFeature' is true. 394 assertFalse(mNetworkInterfacesToTest[networkTypeIndex].getSystemFeature() 395 + " is a reported system feature, " 396 + "however no corresponding connected network interface was found or the attempt " 397 + "to connect has timed out (timeout = " + TIMEOUT_MILLIS + "ms)." 398 + mNetworkInterfacesToTest[networkTypeIndex].getErrorMessage(), hasFeature); 399 return false; 400 } 401 getSubscriberId(int networkIndex)402 private String getSubscriberId(int networkIndex) { 403 int networkType = mNetworkInterfacesToTest[networkIndex].getNetworkType(); 404 if (ConnectivityManager.TYPE_MOBILE == networkType) { 405 TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); 406 return ShellIdentityUtils.invokeMethodWithShellPermissions(tm, 407 (telephonyManager) -> telephonyManager.getSubscriberId()); 408 } 409 return ""; 410 } 411 412 @Test testDeviceSummary()413 public void testDeviceSummary() throws Exception { 414 for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) { 415 if (!shouldTestThisNetworkType(i, MINUTE / 2)) { 416 continue; 417 } 418 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow"); 419 NetworkStats.Bucket bucket = null; 420 try { 421 bucket = mNsm.querySummaryForDevice( 422 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 423 mStartTime, mEndTime); 424 } catch (RemoteException | SecurityException e) { 425 fail("testDeviceSummary fails with exception: " + e.toString()); 426 } 427 assertNotNull(bucket); 428 assertTimestamps(bucket); 429 assertEquals(bucket.getState(), STATE_ALL); 430 assertEquals(bucket.getUid(), UID_ALL); 431 assertEquals(bucket.getMetered(), METERED_ALL); 432 assertEquals(bucket.getRoaming(), ROAMING_ALL); 433 assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL); 434 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny"); 435 try { 436 bucket = mNsm.querySummaryForDevice( 437 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 438 mStartTime, mEndTime); 439 fail("negative testDeviceSummary fails: no exception thrown."); 440 } catch (RemoteException e) { 441 fail("testDeviceSummary fails with exception: " + e.toString()); 442 } catch (SecurityException e) { 443 // expected outcome 444 } 445 } 446 } 447 448 @Test testUserSummary()449 public void testUserSummary() throws Exception { 450 for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) { 451 if (!shouldTestThisNetworkType(i, MINUTE / 2)) { 452 continue; 453 } 454 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow"); 455 NetworkStats.Bucket bucket = null; 456 try { 457 bucket = mNsm.querySummaryForUser( 458 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 459 mStartTime, mEndTime); 460 } catch (RemoteException | SecurityException e) { 461 fail("testUserSummary fails with exception: " + e.toString()); 462 } 463 assertNotNull(bucket); 464 assertTimestamps(bucket); 465 assertEquals(bucket.getState(), STATE_ALL); 466 assertEquals(bucket.getUid(), UID_ALL); 467 assertEquals(bucket.getMetered(), METERED_ALL); 468 assertEquals(bucket.getRoaming(), ROAMING_ALL); 469 assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL); 470 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny"); 471 try { 472 bucket = mNsm.querySummaryForUser( 473 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 474 mStartTime, mEndTime); 475 fail("negative testUserSummary fails: no exception thrown."); 476 } catch (RemoteException e) { 477 fail("testUserSummary fails with exception: " + e.toString()); 478 } catch (SecurityException e) { 479 // expected outcome 480 } 481 } 482 } 483 484 @Test testAppSummary()485 public void testAppSummary() throws Exception { 486 for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) { 487 // Use tolerance value that large enough to make sure stats of at 488 // least one bucket is included. However, this is possible that 489 // the test will see data of different app but with the same UID 490 // that created before testing. 491 // TODO: Consider query stats before testing and use the difference to verify. 492 if (!shouldTestThisNetworkType(i, MINUTE * 120)) { 493 continue; 494 } 495 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow"); 496 NetworkStats result = null; 497 try { 498 result = mNsm.querySummary( 499 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 500 mStartTime, mEndTime); 501 assertNotNull(result); 502 NetworkStats.Bucket bucket = new NetworkStats.Bucket(); 503 long totalTxPackets = 0; 504 long totalRxPackets = 0; 505 long totalTxBytes = 0; 506 long totalRxBytes = 0; 507 boolean hasCorrectMetering = false; 508 boolean hasCorrectRoaming = false; 509 boolean hasCorrectDefaultStatus = false; 510 int expectedMetering = mNetworkInterfacesToTest[i].getMetered() 511 ? METERED_YES : METERED_NO; 512 int expectedRoaming = mNetworkInterfacesToTest[i].getRoaming() 513 ? ROAMING_YES : ROAMING_NO; 514 int expectedDefaultStatus = mNetworkInterfacesToTest[i].getIsDefault() 515 ? DEFAULT_NETWORK_YES : DEFAULT_NETWORK_NO; 516 while (result.hasNextBucket()) { 517 assertTrue(result.getNextBucket(bucket)); 518 assertTimestamps(bucket); 519 hasCorrectMetering |= bucket.getMetered() == expectedMetering; 520 hasCorrectRoaming |= bucket.getRoaming() == expectedRoaming; 521 if (bucket.getUid() == Process.myUid()) { 522 totalTxPackets += bucket.getTxPackets(); 523 totalRxPackets += bucket.getRxPackets(); 524 totalTxBytes += bucket.getTxBytes(); 525 totalRxBytes += bucket.getRxBytes(); 526 hasCorrectDefaultStatus |= 527 bucket.getDefaultNetworkStatus() == expectedDefaultStatus; 528 } 529 } 530 assertFalse(result.getNextBucket(bucket)); 531 assertTrue("Incorrect metering for NetworkType: " 532 + mNetworkInterfacesToTest[i].getNetworkType(), hasCorrectMetering); 533 assertTrue("Incorrect roaming for NetworkType: " 534 + mNetworkInterfacesToTest[i].getNetworkType(), hasCorrectRoaming); 535 assertTrue("Incorrect isDefault for NetworkType: " 536 + mNetworkInterfacesToTest[i].getNetworkType(), hasCorrectDefaultStatus); 537 assertTrue("No Rx bytes usage for uid " + Process.myUid(), totalRxBytes > 0); 538 assertTrue("No Rx packets usage for uid " + Process.myUid(), totalRxPackets > 0); 539 assertTrue("No Tx bytes usage for uid " + Process.myUid(), totalTxBytes > 0); 540 assertTrue("No Tx packets usage for uid " + Process.myUid(), totalTxPackets > 0); 541 } finally { 542 if (result != null) { 543 result.close(); 544 } 545 } 546 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny"); 547 try { 548 result = mNsm.querySummary( 549 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 550 mStartTime, mEndTime); 551 fail("negative testAppSummary fails: no exception thrown."); 552 } catch (RemoteException e) { 553 fail("testAppSummary fails with exception: " + e.toString()); 554 } catch (SecurityException e) { 555 // expected outcome 556 } 557 } 558 } 559 560 @Test testAppDetails()561 public void testAppDetails() throws Exception { 562 for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) { 563 // Relatively large tolerance to accommodate for history bucket size. 564 if (!shouldTestThisNetworkType(i, MINUTE * 120)) { 565 continue; 566 } 567 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow"); 568 NetworkStats result = null; 569 try { 570 result = mNsm.queryDetails( 571 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 572 mStartTime, mEndTime); 573 long totalBytesWithSubscriberId = getTotalAndAssertNotEmpty(result); 574 575 // Test without filtering by subscriberId 576 result = mNsm.queryDetails( 577 mNetworkInterfacesToTest[i].getNetworkType(), null, 578 mStartTime, mEndTime); 579 580 assertTrue("More bytes with subscriberId filter than without.", 581 getTotalAndAssertNotEmpty(result) >= totalBytesWithSubscriberId); 582 } catch (RemoteException | SecurityException e) { 583 fail("testAppDetails fails with exception: " + e.toString()); 584 } finally { 585 if (result != null) { 586 result.close(); 587 } 588 } 589 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny"); 590 try { 591 result = mNsm.queryDetails( 592 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 593 mStartTime, mEndTime); 594 fail("negative testAppDetails fails: no exception thrown."); 595 } catch (RemoteException e) { 596 fail("testAppDetails fails with exception: " + e.toString()); 597 } catch (SecurityException e) { 598 // expected outcome 599 } 600 } 601 } 602 603 @Test testUidDetails()604 public void testUidDetails() throws Exception { 605 for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) { 606 // Relatively large tolerance to accommodate for history bucket size. 607 if (!shouldTestThisNetworkType(i, MINUTE * 120)) { 608 continue; 609 } 610 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow"); 611 NetworkStats result = null; 612 try { 613 result = mNsm.queryDetailsForUid( 614 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 615 mStartTime, mEndTime, Process.myUid()); 616 assertNotNull(result); 617 NetworkStats.Bucket bucket = new NetworkStats.Bucket(); 618 long totalTxPackets = 0; 619 long totalRxPackets = 0; 620 long totalTxBytes = 0; 621 long totalRxBytes = 0; 622 while (result.hasNextBucket()) { 623 assertTrue(result.getNextBucket(bucket)); 624 assertTimestamps(bucket); 625 assertEquals(bucket.getState(), STATE_ALL); 626 assertEquals(bucket.getMetered(), METERED_ALL); 627 assertEquals(bucket.getRoaming(), ROAMING_ALL); 628 assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL); 629 assertEquals(bucket.getUid(), Process.myUid()); 630 totalTxPackets += bucket.getTxPackets(); 631 totalRxPackets += bucket.getRxPackets(); 632 totalTxBytes += bucket.getTxBytes(); 633 totalRxBytes += bucket.getRxBytes(); 634 } 635 assertFalse(result.getNextBucket(bucket)); 636 assertTrue("No Rx bytes usage for uid " + Process.myUid(), totalRxBytes > 0); 637 assertTrue("No Rx packets usage for uid " + Process.myUid(), totalRxPackets > 0); 638 assertTrue("No Tx bytes usage for uid " + Process.myUid(), totalTxBytes > 0); 639 assertTrue("No Tx packets usage for uid " + Process.myUid(), totalTxPackets > 0); 640 } finally { 641 if (result != null) { 642 result.close(); 643 } 644 } 645 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny"); 646 try { 647 result = mNsm.queryDetailsForUid( 648 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 649 mStartTime, mEndTime, Process.myUid()); 650 fail("negative testUidDetails fails: no exception thrown."); 651 } catch (SecurityException e) { 652 // expected outcome 653 } 654 } 655 } 656 657 @Test testTagDetails()658 public void testTagDetails() throws Exception { 659 for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) { 660 // Relatively large tolerance to accommodate for history bucket size. 661 if (!shouldTestThisNetworkType(i, MINUTE * 120)) { 662 continue; 663 } 664 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow"); 665 NetworkStats result = null; 666 try { 667 result = mNsm.queryDetailsForUidTag( 668 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 669 mStartTime, mEndTime, Process.myUid(), NETWORK_TAG); 670 assertNotNull(result); 671 NetworkStats.Bucket bucket = new NetworkStats.Bucket(); 672 long totalTxPackets = 0; 673 long totalRxPackets = 0; 674 long totalTxBytes = 0; 675 long totalRxBytes = 0; 676 while (result.hasNextBucket()) { 677 assertTrue(result.getNextBucket(bucket)); 678 assertTimestamps(bucket); 679 assertEquals(bucket.getState(), STATE_ALL); 680 assertEquals(bucket.getMetered(), METERED_ALL); 681 assertEquals(bucket.getRoaming(), ROAMING_ALL); 682 assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL); 683 assertEquals(bucket.getUid(), Process.myUid()); 684 if (bucket.getTag() == NETWORK_TAG) { 685 totalTxPackets += bucket.getTxPackets(); 686 totalRxPackets += bucket.getRxPackets(); 687 totalTxBytes += bucket.getTxBytes(); 688 totalRxBytes += bucket.getRxBytes(); 689 } 690 } 691 assertTrue("No Rx bytes tagged with 0x" + Integer.toHexString(NETWORK_TAG) 692 + " for uid " + Process.myUid(), totalRxBytes > 0); 693 assertTrue("No Rx packets tagged with 0x" + Integer.toHexString(NETWORK_TAG) 694 + " for uid " + Process.myUid(), totalRxPackets > 0); 695 assertTrue("No Tx bytes tagged with 0x" + Integer.toHexString(NETWORK_TAG) 696 + " for uid " + Process.myUid(), totalTxBytes > 0); 697 assertTrue("No Tx packets tagged with 0x" + Integer.toHexString(NETWORK_TAG) 698 + " for uid " + Process.myUid(), totalTxPackets > 0); 699 } finally { 700 if (result != null) { 701 result.close(); 702 } 703 } 704 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny"); 705 try { 706 result = mNsm.queryDetailsForUidTag( 707 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 708 mStartTime, mEndTime, Process.myUid(), NETWORK_TAG); 709 fail("negative testUidDetails fails: no exception thrown."); 710 } catch (SecurityException e) { 711 // expected outcome 712 } 713 } 714 } 715 716 class QueryResult { 717 public final int tag; 718 public final int state; 719 public final long total; 720 QueryResult(int tag, int state, NetworkStats stats)721 QueryResult(int tag, int state, NetworkStats stats) { 722 this.tag = tag; 723 this.state = state; 724 total = getTotalAndAssertNotEmpty(stats, tag, state); 725 } 726 toString()727 public String toString() { 728 return String.format("QueryResult(tag=%s state=%s total=%d)", 729 tagToString(tag), stateToString(state), total); 730 } 731 } 732 getNetworkStatsForTagState(int i, int tag, int state)733 private NetworkStats getNetworkStatsForTagState(int i, int tag, int state) { 734 return mNsm.queryDetailsForUidTagState( 735 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 736 mStartTime, mEndTime, Process.myUid(), tag, state); 737 } 738 assertWithinPercentage(String msg, long expected, long actual, int percentage)739 private void assertWithinPercentage(String msg, long expected, long actual, int percentage) { 740 long lowerBound = expected * (100 - percentage) / 100; 741 long upperBound = expected * (100 + percentage) / 100; 742 msg = String.format("%s: %d not within %d%% of %d", msg, actual, percentage, expected); 743 assertTrue(msg, lowerBound <= actual); 744 assertTrue(msg, upperBound >= actual); 745 } 746 assertAlmostNoUnexpectedTraffic(NetworkStats result, int expectedTag, int expectedState, long maxUnexpected)747 private void assertAlmostNoUnexpectedTraffic(NetworkStats result, int expectedTag, 748 int expectedState, long maxUnexpected) { 749 long total = 0; 750 NetworkStats.Bucket bucket = new NetworkStats.Bucket(); 751 while (result.hasNextBucket()) { 752 assertTrue(result.getNextBucket(bucket)); 753 total += bucket.getRxBytes() + bucket.getTxBytes(); 754 } 755 if (total <= maxUnexpected) return; 756 757 fail(String.format("More than %d bytes of traffic when querying for " 758 + "tag %s state %s. Last bucket: uid=%d tag=%s state=%s bytes=%d/%d", 759 maxUnexpected, tagToString(expectedTag), stateToString(expectedState), 760 bucket.getUid(), tagToString(bucket.getTag()), stateToString(bucket.getState()), 761 bucket.getRxBytes(), bucket.getTxBytes())); 762 } 763 764 @Test testUidTagStateDetails()765 public void testUidTagStateDetails() throws Exception { 766 for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) { 767 // Relatively large tolerance to accommodate for history bucket size. 768 if (!shouldTestThisNetworkType(i, MINUTE * 120)) { 769 continue; 770 } 771 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow"); 772 NetworkStats result = null; 773 try { 774 int currentState = isInForeground() ? STATE_FOREGROUND : STATE_DEFAULT; 775 int otherState = (currentState == STATE_DEFAULT) ? STATE_FOREGROUND : STATE_DEFAULT; 776 777 int[] tagsWithTraffic = {NETWORK_TAG, TAG_NONE}; 778 int[] statesWithTraffic = {currentState, STATE_ALL}; 779 ArrayList<QueryResult> resultsWithTraffic = new ArrayList<>(); 780 781 int[] statesWithNoTraffic = {otherState}; 782 int[] tagsWithNoTraffic = {NETWORK_TAG + 1}; 783 ArrayList<QueryResult> resultsWithNoTraffic = new ArrayList<>(); 784 785 // Expect to see traffic when querying for any combination of a tag in 786 // tagsWithTraffic and a state in statesWithTraffic. 787 for (int tag : tagsWithTraffic) { 788 for (int state : statesWithTraffic) { 789 result = getNetworkStatsForTagState(i, tag, state); 790 resultsWithTraffic.add(new QueryResult(tag, state, result)); 791 result.close(); 792 result = null; 793 } 794 } 795 796 // Expect that the results are within a few percentage points of each other. 797 // This is ensures that FIN retransmits after the transfer is complete don't cause 798 // the test to be flaky. The test URL currently returns just over 100k so this 799 // should not be too noisy. It also ensures that the traffic sent by the test 800 // harness, which is untagged, won't cause a failure. 801 long firstTotal = resultsWithTraffic.get(0).total; 802 for (QueryResult queryResult : resultsWithTraffic) { 803 assertWithinPercentage(queryResult + "", firstTotal, queryResult.total, 10); 804 } 805 806 // Expect to see no traffic when querying for any tag in tagsWithNoTraffic or any 807 // state in statesWithNoTraffic. 808 for (int tag : tagsWithNoTraffic) { 809 for (int state : statesWithTraffic) { 810 result = getNetworkStatsForTagState(i, tag, state); 811 assertAlmostNoUnexpectedTraffic(result, tag, state, firstTotal / 100); 812 result.close(); 813 result = null; 814 } 815 } 816 for (int tag : tagsWithTraffic) { 817 for (int state : statesWithNoTraffic) { 818 result = getNetworkStatsForTagState(i, tag, state); 819 assertAlmostNoUnexpectedTraffic(result, tag, state, firstTotal / 100); 820 result.close(); 821 result = null; 822 } 823 } 824 } finally { 825 if (result != null) { 826 result.close(); 827 } 828 } 829 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny"); 830 try { 831 result = mNsm.queryDetailsForUidTag( 832 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 833 mStartTime, mEndTime, Process.myUid(), NETWORK_TAG); 834 fail("negative testUidDetails fails: no exception thrown."); 835 } catch (SecurityException e) { 836 // expected outcome 837 } 838 } 839 } 840 841 @Test testCallback()842 public void testCallback() throws Exception { 843 for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) { 844 // Relatively large tolerance to accommodate for history bucket size. 845 if (!shouldTestThisNetworkType(i, MINUTE / 2)) { 846 continue; 847 } 848 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow"); 849 850 TestUsageCallback usageCallback = new TestUsageCallback(); 851 HandlerThread thread = new HandlerThread("callback-thread"); 852 thread.start(); 853 Handler handler = new Handler(thread.getLooper()); 854 mNsm.registerUsageCallback(mNetworkInterfacesToTest[i].getNetworkType(), 855 getSubscriberId(i), THRESHOLD_BYTES, usageCallback, handler); 856 857 // TODO: Force traffic and check whether the callback is invoked. 858 // Right now the test only covers whether the callback can be registered, but not 859 // whether it is invoked upon data usage since we don't have a scalable way of 860 // storing files of >2MB in CTS. 861 862 mNsm.unregisterUsageCallback(usageCallback); 863 864 // For T- devices, the registerUsageCallback invocation below will need a looper 865 // from the thread that calls into the API, which is not available in the test. 866 if (SdkLevel.isAtLeastT()) { 867 mNsm.registerUsageCallback(mNetworkInterfacesToTest[i].getNetworkType(), 868 getSubscriberId(i), THRESHOLD_BYTES, usageCallback); 869 mNsm.unregisterUsageCallback(usageCallback); 870 } 871 } 872 } 873 874 @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) 875 @Test testDataMigrationUtils()876 public void testDataMigrationUtils() throws Exception { 877 final List<String> prefixes = List.of(PREFIX_UID, PREFIX_XT, PREFIX_UID_TAG); 878 for (final String prefix : prefixes) { 879 final long duration = TextUtils.equals(PREFIX_XT, prefix) ? TimeUnit.HOURS.toMillis(1) 880 : TimeUnit.HOURS.toMillis(2); 881 882 final NetworkStatsCollection collection = 883 NetworkStatsDataMigrationUtils.readPlatformCollection(prefix, duration); 884 885 final long now = System.currentTimeMillis(); 886 final Set<Map.Entry<NetworkStatsCollection.Key, NetworkStatsHistory>> entries = 887 collection.getEntries().entrySet(); 888 for (final Map.Entry<NetworkStatsCollection.Key, NetworkStatsHistory> entry : entries) { 889 for (final NetworkStatsHistory.Entry historyEntry : entry.getValue().getEntries()) { 890 // Verify all value fields are reasonable. 891 assertTrue(historyEntry.getBucketStart() <= now); 892 assertTrue(historyEntry.getActiveTime() <= duration); 893 assertTrue(historyEntry.getRxBytes() >= 0); 894 assertTrue(historyEntry.getRxPackets() >= 0); 895 assertTrue(historyEntry.getTxBytes() >= 0); 896 assertTrue(historyEntry.getTxPackets() >= 0); 897 assertTrue(historyEntry.getOperations() >= 0); 898 } 899 } 900 } 901 } 902 tagToString(Integer tag)903 private String tagToString(Integer tag) { 904 if (tag == null) return "null"; 905 switch (tag) { 906 case TAG_NONE: 907 return "TAG_NONE"; 908 default: 909 return "0x" + Integer.toHexString(tag); 910 } 911 } 912 stateToString(Integer state)913 private String stateToString(Integer state) { 914 if (state == null) return "null"; 915 switch (state) { 916 case STATE_ALL: 917 return "STATE_ALL"; 918 case STATE_DEFAULT: 919 return "STATE_DEFAULT"; 920 case STATE_FOREGROUND: 921 return "STATE_FOREGROUND"; 922 } 923 throw new IllegalArgumentException("Unknown state " + state); 924 } 925 getTotalAndAssertNotEmpty(NetworkStats result, Integer expectedTag, Integer expectedState)926 private long getTotalAndAssertNotEmpty(NetworkStats result, Integer expectedTag, 927 Integer expectedState) { 928 assertTrue(result != null); 929 NetworkStats.Bucket bucket = new NetworkStats.Bucket(); 930 long totalTxPackets = 0; 931 long totalRxPackets = 0; 932 long totalTxBytes = 0; 933 long totalRxBytes = 0; 934 while (result.hasNextBucket()) { 935 assertTrue(result.getNextBucket(bucket)); 936 assertTimestamps(bucket); 937 if (expectedTag != null) assertEquals(bucket.getTag(), (int) expectedTag); 938 if (expectedState != null) assertEquals(bucket.getState(), (int) expectedState); 939 assertEquals(bucket.getMetered(), METERED_ALL); 940 assertEquals(bucket.getRoaming(), ROAMING_ALL); 941 assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL); 942 if (bucket.getUid() == Process.myUid()) { 943 totalTxPackets += bucket.getTxPackets(); 944 totalRxPackets += bucket.getRxPackets(); 945 totalTxBytes += bucket.getTxBytes(); 946 totalRxBytes += bucket.getRxBytes(); 947 } 948 } 949 assertFalse(result.getNextBucket(bucket)); 950 String msg = String.format("uid %d tag %s state %s", 951 Process.myUid(), tagToString(expectedTag), stateToString(expectedState)); 952 assertTrue("No Rx bytes usage for " + msg, totalRxBytes > 0); 953 assertTrue("No Rx packets usage for " + msg, totalRxPackets > 0); 954 assertTrue("No Tx bytes usage for " + msg, totalTxBytes > 0); 955 assertTrue("No Tx packets usage for " + msg, totalTxPackets > 0); 956 957 return totalRxBytes + totalTxBytes; 958 } 959 getTotalAndAssertNotEmpty(NetworkStats result)960 private long getTotalAndAssertNotEmpty(NetworkStats result) { 961 return getTotalAndAssertNotEmpty(result, null, STATE_ALL); 962 } 963 assertTimestamps(final NetworkStats.Bucket bucket)964 private void assertTimestamps(final NetworkStats.Bucket bucket) { 965 assertTrue("Start timestamp " + bucket.getStartTimeStamp() + " is less than " 966 + mStartTime, bucket.getStartTimeStamp() >= mStartTime); 967 assertTrue("End timestamp " + bucket.getEndTimeStamp() + " is greater than " 968 + mEndTime, bucket.getEndTimeStamp() <= mEndTime); 969 } 970 971 private static class TestUsageCallback extends NetworkStatsManager.UsageCallback { 972 @Override onThresholdReached(int networkType, String subscriberId)973 public void onThresholdReached(int networkType, String subscriberId) { 974 Log.v(LOG_TAG, "Called onThresholdReached for networkType=" + networkType 975 + " subscriberId=" + subscriberId); 976 } 977 } 978 } 979