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