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.annotation.NonNull; 45 import android.app.AppOpsManager; 46 import android.app.Instrumentation; 47 import android.app.usage.NetworkStats; 48 import android.app.usage.NetworkStatsManager; 49 import android.content.Context; 50 import android.content.pm.PackageManager; 51 import android.net.ConnectivityManager; 52 import android.net.Network; 53 import android.net.NetworkCapabilities; 54 import android.net.NetworkInfo; 55 import android.net.NetworkRequest; 56 import android.net.NetworkStatsCollection; 57 import android.net.NetworkStatsHistory; 58 import android.net.TrafficStats; 59 import android.net.netstats.NetworkStatsDataMigrationUtils; 60 import android.os.Build; 61 import android.os.Handler; 62 import android.os.HandlerThread; 63 import android.os.Process; 64 import android.os.RemoteException; 65 import android.os.SystemClock; 66 import android.os.UserHandle; 67 import android.platform.test.annotations.AppModeFull; 68 import android.telephony.TelephonyManager; 69 import android.text.TextUtils; 70 import android.util.Log; 71 72 import androidx.test.InstrumentationRegistry; 73 74 import com.android.compatibility.common.util.ShellIdentityUtils; 75 import com.android.compatibility.common.util.SystemUtil; 76 import com.android.modules.utils.build.SdkLevel; 77 import com.android.testutils.AutoReleaseNetworkCallbackRule; 78 import com.android.testutils.ConnectivityModuleTest; 79 import com.android.testutils.DevSdkIgnoreRule; 80 import com.android.testutils.DevSdkIgnoreRunner; 81 import com.android.testutils.RecorderCallback.CallbackEntry; 82 import com.android.testutils.TestableNetworkCallback; 83 84 import org.junit.After; 85 import org.junit.Before; 86 import org.junit.Rule; 87 import org.junit.Test; 88 import org.junit.runner.RunWith; 89 90 import java.io.BufferedInputStream; 91 import java.io.IOException; 92 import java.io.InputStream; 93 import java.net.HttpURLConnection; 94 import java.net.URL; 95 import java.net.UnknownHostException; 96 import java.text.MessageFormat; 97 import java.util.ArrayList; 98 import java.util.List; 99 import java.util.Map; 100 import java.util.Set; 101 import java.util.concurrent.TimeUnit; 102 103 // TODO: Fix thread leaks in testCallback and annotating with @MonitorThreadLeak. 104 @AppModeFull(reason = "instant apps cannot be granted USAGE_STATS") 105 @ConnectivityModuleTest 106 @DevSdkIgnoreRunner.RestoreDefaultNetwork 107 @RunWith(DevSdkIgnoreRunner.class) 108 public class NetworkStatsManagerTest { 109 @Rule(order = 1) 110 public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule(Build.VERSION_CODES.Q); 111 @Rule(order = 2) 112 public final AutoReleaseNetworkCallbackRule 113 networkCallbackRule = new AutoReleaseNetworkCallbackRule(); 114 115 116 private static final String LOG_TAG = "NetworkStatsManagerTest"; 117 private static final String APPOPS_SET_SHELL_COMMAND = "appops set --user {0} {1} {2} {3}"; 118 private static final String APPOPS_GET_SHELL_COMMAND = "appops get --user {0} {1} {2}"; 119 120 private static final long MINUTE = 1000 * 60; 121 private static final int TIMEOUT_MILLIS = 15000; 122 123 private static final String CHECK_CONNECTIVITY_URL = "http://www.265.com/"; 124 private static final int HOST_RESOLUTION_RETRIES = 4; 125 private static final int HOST_RESOLUTION_INTERVAL_MS = 500; 126 127 private static final int NETWORK_TAG = 0xf00d; 128 private static final long THRESHOLD_BYTES = 2 * 1024 * 1024; // 2 MB 129 private static final long SHORT_TOLERANCE = MINUTE / 2; 130 private static final long LONG_TOLERANCE = MINUTE * 120; 131 132 private abstract class NetworkInterfaceToTest { 133 134 final TestableNetworkCallback mRequestNetworkCb = new TestableNetworkCallback(); 135 private boolean mMetered; 136 private boolean mRoaming; 137 private boolean mIsDefault; 138 getNetworkType()139 abstract int getNetworkType(); 140 requestNetwork()141 abstract Network requestNetwork(); 142 unrequestNetwork()143 void unrequestNetwork() { 144 networkCallbackRule.unregisterNetworkCallback(mRequestNetworkCb); 145 } 146 getMetered()147 public boolean getMetered() { 148 return mMetered; 149 } 150 setMetered(boolean metered)151 public void setMetered(boolean metered) { 152 this.mMetered = metered; 153 } 154 getRoaming()155 public boolean getRoaming() { 156 return mRoaming; 157 } 158 setRoaming(boolean roaming)159 public void setRoaming(boolean roaming) { 160 this.mRoaming = roaming; 161 } 162 getIsDefault()163 public boolean getIsDefault() { 164 return mIsDefault; 165 } 166 setIsDefault(boolean isDefault)167 public void setIsDefault(boolean isDefault) { 168 mIsDefault = isDefault; 169 } 170 getSystemFeature()171 abstract String getSystemFeature(); 172 buildRequestForTransport(int transport)173 @NonNull NetworkRequest buildRequestForTransport(int transport) { 174 return new NetworkRequest.Builder() 175 .addTransportType(transport) 176 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) 177 .build(); 178 } 179 } 180 181 private final NetworkInterfaceToTest[] mNetworkInterfacesToTest = 182 new NetworkInterfaceToTest[] { 183 new NetworkInterfaceToTest() { 184 @Override 185 public int getNetworkType() { 186 return ConnectivityManager.TYPE_WIFI; 187 } 188 189 @Override 190 public Network requestNetwork() { 191 networkCallbackRule.requestNetwork(buildRequestForTransport( 192 NetworkCapabilities.TRANSPORT_WIFI), 193 mRequestNetworkCb, TIMEOUT_MILLIS); 194 return mRequestNetworkCb.expect(CallbackEntry.AVAILABLE, 195 "Wifi network not available. " 196 + "Please ensure the device has working wifi." 197 ).getNetwork(); 198 } 199 200 @Override 201 public String getSystemFeature() { 202 return PackageManager.FEATURE_WIFI; 203 } 204 }, 205 new NetworkInterfaceToTest() { 206 @Override 207 public int getNetworkType() { 208 return ConnectivityManager.TYPE_MOBILE; 209 } 210 211 @Override 212 public Network requestNetwork() { 213 networkCallbackRule.requestNetwork(buildRequestForTransport( 214 NetworkCapabilities.TRANSPORT_CELLULAR), 215 mRequestNetworkCb, TIMEOUT_MILLIS); 216 return mRequestNetworkCb.expect(CallbackEntry.AVAILABLE, 217 "Cell network not available. " 218 + "Please ensure the device has working mobile data." 219 ).getNetwork(); 220 } 221 222 @Override 223 public String getSystemFeature() { 224 return PackageManager.FEATURE_TELEPHONY; 225 } 226 } 227 }; 228 229 private String mPkg; 230 private Context mContext; 231 private NetworkStatsManager mNsm; 232 private ConnectivityManager mCm; 233 private PackageManager mPm; 234 private Instrumentation mInstrumentation; 235 private long mStartTime; 236 private long mEndTime; 237 238 private String mWriteSettingsMode; 239 private String mUsageStatsMode; 240 241 // The test host only has IPv4. So on a dual-stack network where IPv6 connects before IPv4, 242 // we need to wait until IPv4 is available or the test will spuriously fail. waitForHostResolution(@onNull Network network, @NonNull URL url)243 private static void waitForHostResolution(@NonNull Network network, @NonNull URL url) { 244 for (int i = 0; i < HOST_RESOLUTION_RETRIES; i++) { 245 try { 246 network.getAllByName(url.getHost()); 247 return; 248 } catch (UnknownHostException e) { 249 SystemClock.sleep(HOST_RESOLUTION_INTERVAL_MS); 250 } 251 } 252 fail(String.format("%s could not be resolved on network %s (%d attempts %dms apart)", 253 url.getHost(), network, HOST_RESOLUTION_RETRIES, HOST_RESOLUTION_INTERVAL_MS)); 254 } 255 exerciseRemoteHost(@onNull Network network, @NonNull URL url)256 private void exerciseRemoteHost(@NonNull Network network, @NonNull URL url) throws Exception { 257 NetworkInfo networkInfo = mCm.getNetworkInfo(network); 258 if (networkInfo == null) { 259 Log.w(LOG_TAG, "Network info is null"); 260 } else { 261 Log.w(LOG_TAG, "Network: " + networkInfo.toString()); 262 } 263 BufferedInputStream in = null; 264 HttpURLConnection urlc = null; 265 String originalKeepAlive = System.getProperty("http.keepAlive"); 266 System.setProperty("http.keepAlive", "false"); 267 try { 268 TrafficStats.setThreadStatsTag(NETWORK_TAG); 269 urlc = (HttpURLConnection) network.openConnection(url); 270 urlc.setConnectTimeout(TIMEOUT_MILLIS); 271 urlc.setReadTimeout(TIMEOUT_MILLIS); 272 urlc.setUseCaches(false); 273 // Disable compression so we generate enough traffic that assertWithinPercentage will 274 // not be affected by the small amount of traffic (5-10kB) sent by the test harness. 275 urlc.setRequestProperty("Accept-Encoding", "identity"); 276 urlc.connect(); 277 boolean ping = urlc.getResponseCode() == 200; 278 if (ping) { 279 in = new BufferedInputStream((InputStream) urlc.getContent()); 280 while (in.read() != -1) { 281 // Comments to suppress lint error. 282 } 283 } 284 } catch (Exception e) { 285 Log.i(LOG_TAG, "Badness during exercising remote server: " + e); 286 } finally { 287 TrafficStats.clearThreadStatsTag(); 288 if (in != null) { 289 try { 290 in.close(); 291 } catch (IOException e) { 292 // don't care 293 } 294 } 295 if (urlc != null) { 296 urlc.disconnect(); 297 } 298 if (originalKeepAlive == null) { 299 System.clearProperty("http.keepAlive"); 300 } else { 301 System.setProperty("http.keepAlive", originalKeepAlive); 302 } 303 } 304 } 305 306 @Before setUp()307 public void setUp() throws Exception { 308 mContext = InstrumentationRegistry.getContext(); 309 mNsm = mContext.getSystemService(NetworkStatsManager.class); 310 mNsm.setPollForce(true); 311 312 mCm = mContext.getSystemService(ConnectivityManager.class); 313 mPm = mContext.getPackageManager(); 314 mPkg = mContext.getPackageName(); 315 316 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 317 mWriteSettingsMode = getAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS); 318 setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, "allow"); 319 mUsageStatsMode = getAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS); 320 } 321 322 @After tearDown()323 public void tearDown() throws Exception { 324 if (mWriteSettingsMode != null) { 325 setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, mWriteSettingsMode); 326 } 327 if (mUsageStatsMode != null) { 328 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, mUsageStatsMode); 329 } 330 } 331 setAppOpsMode(String appop, String mode)332 private void setAppOpsMode(String appop, String mode) throws Exception { 333 final String command = MessageFormat.format(APPOPS_SET_SHELL_COMMAND, 334 UserHandle.myUserId(), mPkg, appop, mode); 335 SystemUtil.runShellCommand(mInstrumentation, command); 336 } 337 getAppOpsMode(String appop)338 private String getAppOpsMode(String appop) throws Exception { 339 final String command = MessageFormat.format(APPOPS_GET_SHELL_COMMAND, 340 UserHandle.myUserId(), mPkg, appop); 341 String result = SystemUtil.runShellCommand(mInstrumentation, command); 342 if (result == null) { 343 Log.w(LOG_TAG, "App op " + appop + " could not be read."); 344 } 345 return result; 346 } 347 isInForeground()348 private boolean isInForeground() throws IOException { 349 String result = SystemUtil.runShellCommand(mInstrumentation, 350 "cmd activity get-uid-state " + Process.myUid()); 351 return result.contains("FOREGROUND"); 352 } 353 shouldTestThisNetworkType(int networkTypeIndex)354 private boolean shouldTestThisNetworkType(int networkTypeIndex) { 355 return mPm.hasSystemFeature(mNetworkInterfacesToTest[networkTypeIndex].getSystemFeature()); 356 } 357 358 @NonNull requestNetworkAndSetAttributes( @onNull NetworkInterfaceToTest networkInterface)359 private Network requestNetworkAndSetAttributes( 360 @NonNull NetworkInterfaceToTest networkInterface) { 361 final Network network = networkInterface.requestNetwork(); 362 363 // These attributes are needed when performing NetworkStats queries. 364 // Fetch caps from the first capabilities changed event since the 365 // interested attributes are not mutable, and not expected to be 366 // changed during the test. 367 final NetworkCapabilities caps = networkInterface.mRequestNetworkCb.expect( 368 CallbackEntry.NETWORK_CAPS_UPDATED, network).getCaps(); 369 networkInterface.setMetered(!caps.hasCapability( 370 NetworkCapabilities.NET_CAPABILITY_NOT_METERED)); 371 networkInterface.setRoaming(!caps.hasCapability( 372 NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)); 373 networkInterface.setIsDefault(network.equals(mCm.getActiveNetwork())); 374 375 return network; 376 } 377 requestNetworkAndGenerateTraffic(int networkTypeIndex, final long tolerance)378 private void requestNetworkAndGenerateTraffic(int networkTypeIndex, final long tolerance) 379 throws Exception { 380 final NetworkInterfaceToTest networkInterface = mNetworkInterfacesToTest[networkTypeIndex]; 381 final Network network = requestNetworkAndSetAttributes(networkInterface); 382 383 mStartTime = System.currentTimeMillis() - tolerance; 384 waitForHostResolution(network, new URL(CHECK_CONNECTIVITY_URL)); 385 exerciseRemoteHost(network, new URL(CHECK_CONNECTIVITY_URL)); 386 mEndTime = System.currentTimeMillis() + tolerance; 387 388 // It is fine if the test fails and this line is not reached. 389 // The AutoReleaseNetworkCallbackRule will eventually release 390 // all unwanted callbacks. 391 networkInterface.unrequestNetwork(); 392 } 393 getSubscriberId(int networkIndex)394 private String getSubscriberId(int networkIndex) { 395 int networkType = mNetworkInterfacesToTest[networkIndex].getNetworkType(); 396 if (ConnectivityManager.TYPE_MOBILE == networkType) { 397 TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); 398 return ShellIdentityUtils.invokeMethodWithShellPermissions(tm, 399 (telephonyManager) -> telephonyManager.getSubscriberId()); 400 } 401 return ""; 402 } 403 404 @Test testDeviceSummary()405 public void testDeviceSummary() throws Exception { 406 for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) { 407 if (!shouldTestThisNetworkType(i)) { 408 continue; 409 } 410 requestNetworkAndGenerateTraffic(i, SHORT_TOLERANCE); 411 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow"); 412 NetworkStats.Bucket bucket = null; 413 try { 414 bucket = mNsm.querySummaryForDevice( 415 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 416 mStartTime, mEndTime); 417 } catch (RemoteException | SecurityException e) { 418 fail("testDeviceSummary fails with exception: " + e.toString()); 419 } 420 assertNotNull(bucket); 421 assertTimestamps(bucket); 422 assertEquals(bucket.getState(), STATE_ALL); 423 assertEquals(bucket.getUid(), UID_ALL); 424 assertEquals(bucket.getMetered(), METERED_ALL); 425 assertEquals(bucket.getRoaming(), ROAMING_ALL); 426 assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL); 427 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny"); 428 try { 429 bucket = mNsm.querySummaryForDevice( 430 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 431 mStartTime, mEndTime); 432 fail("negative testDeviceSummary fails: no exception thrown."); 433 } catch (RemoteException e) { 434 fail("testDeviceSummary fails with exception: " + e.toString()); 435 } catch (SecurityException e) { 436 // expected outcome 437 } 438 } 439 } 440 441 @Test testUserSummary()442 public void testUserSummary() throws Exception { 443 for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) { 444 if (!shouldTestThisNetworkType(i)) { 445 continue; 446 } 447 requestNetworkAndGenerateTraffic(i, SHORT_TOLERANCE); 448 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow"); 449 NetworkStats.Bucket bucket = null; 450 try { 451 bucket = mNsm.querySummaryForUser( 452 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 453 mStartTime, mEndTime); 454 } catch (RemoteException | SecurityException e) { 455 fail("testUserSummary fails with exception: " + e.toString()); 456 } 457 assertNotNull(bucket); 458 assertTimestamps(bucket); 459 assertEquals(bucket.getState(), STATE_ALL); 460 assertEquals(bucket.getUid(), UID_ALL); 461 assertEquals(bucket.getMetered(), METERED_ALL); 462 assertEquals(bucket.getRoaming(), ROAMING_ALL); 463 assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL); 464 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny"); 465 try { 466 bucket = mNsm.querySummaryForUser( 467 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 468 mStartTime, mEndTime); 469 fail("negative testUserSummary fails: no exception thrown."); 470 } catch (RemoteException e) { 471 fail("testUserSummary fails with exception: " + e.toString()); 472 } catch (SecurityException e) { 473 // expected outcome 474 } 475 } 476 } 477 478 @Test testAppSummary()479 public void testAppSummary() throws Exception { 480 for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) { 481 if (!shouldTestThisNetworkType(i)) { 482 continue; 483 } 484 // Use tolerance value that large enough to make sure stats of at 485 // least one bucket is included. However, this is possible that 486 // the test will see data of different app but with the same UID 487 // that created before testing. 488 // TODO: Consider query stats before testing and use the difference to verify. 489 requestNetworkAndGenerateTraffic(i, LONG_TOLERANCE); 490 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow"); 491 NetworkStats result = null; 492 try { 493 result = mNsm.querySummary( 494 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 495 mStartTime, mEndTime); 496 assertNotNull(result); 497 NetworkStats.Bucket bucket = new NetworkStats.Bucket(); 498 long totalTxPackets = 0; 499 long totalRxPackets = 0; 500 long totalTxBytes = 0; 501 long totalRxBytes = 0; 502 boolean hasCorrectMetering = false; 503 boolean hasCorrectRoaming = false; 504 boolean hasCorrectDefaultStatus = false; 505 int expectedMetering = mNetworkInterfacesToTest[i].getMetered() 506 ? METERED_YES : METERED_NO; 507 int expectedRoaming = mNetworkInterfacesToTest[i].getRoaming() 508 ? ROAMING_YES : ROAMING_NO; 509 int expectedDefaultStatus = mNetworkInterfacesToTest[i].getIsDefault() 510 ? DEFAULT_NETWORK_YES : DEFAULT_NETWORK_NO; 511 while (result.hasNextBucket()) { 512 assertTrue(result.getNextBucket(bucket)); 513 assertTimestamps(bucket); 514 hasCorrectMetering |= bucket.getMetered() == expectedMetering; 515 hasCorrectRoaming |= bucket.getRoaming() == expectedRoaming; 516 if (bucket.getUid() == Process.myUid()) { 517 totalTxPackets += bucket.getTxPackets(); 518 totalRxPackets += bucket.getRxPackets(); 519 totalTxBytes += bucket.getTxBytes(); 520 totalRxBytes += bucket.getRxBytes(); 521 hasCorrectDefaultStatus |= 522 bucket.getDefaultNetworkStatus() == expectedDefaultStatus; 523 } 524 } 525 assertFalse(result.getNextBucket(bucket)); 526 assertTrue("Incorrect metering for NetworkType: " 527 + mNetworkInterfacesToTest[i].getNetworkType(), hasCorrectMetering); 528 assertTrue("Incorrect roaming for NetworkType: " 529 + mNetworkInterfacesToTest[i].getNetworkType(), hasCorrectRoaming); 530 assertTrue("Incorrect isDefault for NetworkType: " 531 + mNetworkInterfacesToTest[i].getNetworkType(), hasCorrectDefaultStatus); 532 assertTrue("No Rx bytes usage for uid " + Process.myUid(), totalRxBytes > 0); 533 assertTrue("No Rx packets usage for uid " + Process.myUid(), totalRxPackets > 0); 534 assertTrue("No Tx bytes usage for uid " + Process.myUid(), totalTxBytes > 0); 535 assertTrue("No Tx packets usage for uid " + Process.myUid(), totalTxPackets > 0); 536 } finally { 537 if (result != null) { 538 result.close(); 539 } 540 } 541 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny"); 542 try { 543 result = mNsm.querySummary( 544 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 545 mStartTime, mEndTime); 546 fail("negative testAppSummary fails: no exception thrown."); 547 } catch (RemoteException e) { 548 fail("testAppSummary fails with exception: " + e.toString()); 549 } catch (SecurityException e) { 550 // expected outcome 551 } 552 } 553 } 554 555 @Test testAppDetails()556 public void testAppDetails() throws Exception { 557 for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) { 558 if (!shouldTestThisNetworkType(i)) { 559 continue; 560 } 561 // Relatively large tolerance to accommodate for history bucket size. 562 requestNetworkAndGenerateTraffic(i, LONG_TOLERANCE); 563 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow"); 564 NetworkStats result = null; 565 try { 566 result = mNsm.queryDetails( 567 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 568 mStartTime, mEndTime); 569 long totalBytesWithSubscriberId = getTotalAndAssertNotEmpty(result); 570 571 // Test without filtering by subscriberId 572 result = mNsm.queryDetails( 573 mNetworkInterfacesToTest[i].getNetworkType(), null, 574 mStartTime, mEndTime); 575 576 assertTrue("More bytes with subscriberId filter than without.", 577 getTotalAndAssertNotEmpty(result) >= totalBytesWithSubscriberId); 578 } catch (RemoteException | SecurityException e) { 579 fail("testAppDetails fails with exception: " + e.toString()); 580 } finally { 581 if (result != null) { 582 result.close(); 583 } 584 } 585 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny"); 586 try { 587 result = mNsm.queryDetails( 588 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 589 mStartTime, mEndTime); 590 fail("negative testAppDetails fails: no exception thrown."); 591 } catch (RemoteException e) { 592 fail("testAppDetails fails with exception: " + e.toString()); 593 } catch (SecurityException e) { 594 // expected outcome 595 } 596 } 597 } 598 599 @Test testUidDetails()600 public void testUidDetails() throws Exception { 601 for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) { 602 // Relatively large tolerance to accommodate for history bucket size. 603 if (!shouldTestThisNetworkType(i)) { 604 continue; 605 } 606 requestNetworkAndGenerateTraffic(i, LONG_TOLERANCE); 607 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow"); 608 NetworkStats result = null; 609 try { 610 result = mNsm.queryDetailsForUid( 611 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 612 mStartTime, mEndTime, Process.myUid()); 613 assertNotNull(result); 614 NetworkStats.Bucket bucket = new NetworkStats.Bucket(); 615 long totalTxPackets = 0; 616 long totalRxPackets = 0; 617 long totalTxBytes = 0; 618 long totalRxBytes = 0; 619 while (result.hasNextBucket()) { 620 assertTrue(result.getNextBucket(bucket)); 621 assertTimestamps(bucket); 622 assertEquals(bucket.getState(), STATE_ALL); 623 assertEquals(bucket.getMetered(), METERED_ALL); 624 assertEquals(bucket.getRoaming(), ROAMING_ALL); 625 assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL); 626 assertEquals(bucket.getUid(), Process.myUid()); 627 totalTxPackets += bucket.getTxPackets(); 628 totalRxPackets += bucket.getRxPackets(); 629 totalTxBytes += bucket.getTxBytes(); 630 totalRxBytes += bucket.getRxBytes(); 631 } 632 assertFalse(result.getNextBucket(bucket)); 633 assertTrue("No Rx bytes usage for uid " + Process.myUid(), totalRxBytes > 0); 634 assertTrue("No Rx packets usage for uid " + Process.myUid(), totalRxPackets > 0); 635 assertTrue("No Tx bytes usage for uid " + Process.myUid(), totalTxBytes > 0); 636 assertTrue("No Tx packets usage for uid " + Process.myUid(), totalTxPackets > 0); 637 } finally { 638 if (result != null) { 639 result.close(); 640 } 641 } 642 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny"); 643 try { 644 result = mNsm.queryDetailsForUid( 645 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 646 mStartTime, mEndTime, Process.myUid()); 647 fail("negative testUidDetails fails: no exception thrown."); 648 } catch (SecurityException e) { 649 // expected outcome 650 } 651 } 652 } 653 654 @Test testTagDetails()655 public void testTagDetails() throws Exception { 656 for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) { 657 // Relatively large tolerance to accommodate for history bucket size. 658 if (!shouldTestThisNetworkType(i)) { 659 continue; 660 } 661 requestNetworkAndGenerateTraffic(i, LONG_TOLERANCE); 662 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow"); 663 NetworkStats result = null; 664 try { 665 result = mNsm.queryDetailsForUidTag( 666 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 667 mStartTime, mEndTime, Process.myUid(), NETWORK_TAG); 668 assertNotNull(result); 669 NetworkStats.Bucket bucket = new NetworkStats.Bucket(); 670 long totalTxPackets = 0; 671 long totalRxPackets = 0; 672 long totalTxBytes = 0; 673 long totalRxBytes = 0; 674 while (result.hasNextBucket()) { 675 assertTrue(result.getNextBucket(bucket)); 676 assertTimestamps(bucket); 677 assertEquals(bucket.getState(), STATE_ALL); 678 assertEquals(bucket.getMetered(), METERED_ALL); 679 assertEquals(bucket.getRoaming(), ROAMING_ALL); 680 assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL); 681 assertEquals(bucket.getUid(), Process.myUid()); 682 if (bucket.getTag() == NETWORK_TAG) { 683 totalTxPackets += bucket.getTxPackets(); 684 totalRxPackets += bucket.getRxPackets(); 685 totalTxBytes += bucket.getTxBytes(); 686 totalRxBytes += bucket.getRxBytes(); 687 } 688 } 689 assertTrue("No Rx bytes tagged with 0x" + Integer.toHexString(NETWORK_TAG) 690 + " for uid " + Process.myUid(), totalRxBytes > 0); 691 assertTrue("No Rx packets tagged with 0x" + Integer.toHexString(NETWORK_TAG) 692 + " for uid " + Process.myUid(), totalRxPackets > 0); 693 assertTrue("No Tx bytes tagged with 0x" + Integer.toHexString(NETWORK_TAG) 694 + " for uid " + Process.myUid(), totalTxBytes > 0); 695 assertTrue("No Tx packets tagged with 0x" + Integer.toHexString(NETWORK_TAG) 696 + " for uid " + Process.myUid(), totalTxPackets > 0); 697 } finally { 698 if (result != null) { 699 result.close(); 700 } 701 } 702 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny"); 703 try { 704 result = mNsm.queryDetailsForUidTag( 705 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 706 mStartTime, mEndTime, Process.myUid(), NETWORK_TAG); 707 fail("negative testUidDetails fails: no exception thrown."); 708 } catch (SecurityException e) { 709 // expected outcome 710 } 711 } 712 } 713 714 class QueryResult { 715 public final int tag; 716 public final int state; 717 public final long total; 718 QueryResult(int tag, int state, NetworkStats stats)719 QueryResult(int tag, int state, NetworkStats stats) { 720 this.tag = tag; 721 this.state = state; 722 total = getTotalAndAssertNotEmpty(stats, tag, state); 723 } 724 toString()725 public String toString() { 726 return String.format("QueryResult(tag=%s state=%s total=%d)", 727 tagToString(tag), stateToString(state), total); 728 } 729 } 730 getNetworkStatsForTagState(int i, int tag, int state)731 private NetworkStats getNetworkStatsForTagState(int i, int tag, int state) { 732 return mNsm.queryDetailsForUidTagState( 733 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 734 mStartTime, mEndTime, Process.myUid(), tag, state); 735 } 736 assertWithinPercentage(String msg, long expected, long actual, int percentage)737 private void assertWithinPercentage(String msg, long expected, long actual, int percentage) { 738 long lowerBound = expected * (100 - percentage) / 100; 739 long upperBound = expected * (100 + percentage) / 100; 740 msg = String.format("%s: %d not within %d%% of %d", msg, actual, percentage, expected); 741 assertTrue(msg, lowerBound <= actual); 742 assertTrue(msg, upperBound >= actual); 743 } 744 assertAlmostNoUnexpectedTraffic(NetworkStats result, int expectedTag, int expectedState, long maxUnexpected)745 private void assertAlmostNoUnexpectedTraffic(NetworkStats result, int expectedTag, 746 int expectedState, long maxUnexpected) { 747 long total = 0; 748 NetworkStats.Bucket bucket = new NetworkStats.Bucket(); 749 while (result.hasNextBucket()) { 750 assertTrue(result.getNextBucket(bucket)); 751 total += bucket.getRxBytes() + bucket.getTxBytes(); 752 } 753 if (total <= maxUnexpected) return; 754 755 fail(String.format("More than %d bytes of traffic when querying for " 756 + "tag %s state %s. Last bucket: uid=%d tag=%s state=%s bytes=%d/%d", 757 maxUnexpected, tagToString(expectedTag), stateToString(expectedState), 758 bucket.getUid(), tagToString(bucket.getTag()), stateToString(bucket.getState()), 759 bucket.getRxBytes(), bucket.getTxBytes())); 760 } 761 762 @Test testUidTagStateDetails()763 public void testUidTagStateDetails() throws Exception { 764 for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) { 765 if (!shouldTestThisNetworkType(i)) { 766 continue; 767 } 768 // Relatively large tolerance to accommodate for history bucket size. 769 requestNetworkAndGenerateTraffic(i, LONG_TOLERANCE); 770 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow"); 771 NetworkStats result = null; 772 try { 773 int currentState = isInForeground() ? STATE_FOREGROUND : STATE_DEFAULT; 774 int otherState = (currentState == STATE_DEFAULT) ? STATE_FOREGROUND : STATE_DEFAULT; 775 776 int[] tagsWithTraffic = {NETWORK_TAG, TAG_NONE}; 777 int[] statesWithTraffic = {currentState, STATE_ALL}; 778 ArrayList<QueryResult> resultsWithTraffic = new ArrayList<>(); 779 780 int[] statesWithNoTraffic = {otherState}; 781 int[] tagsWithNoTraffic = {NETWORK_TAG + 1}; 782 ArrayList<QueryResult> resultsWithNoTraffic = new ArrayList<>(); 783 784 // Expect to see traffic when querying for any combination of a tag in 785 // tagsWithTraffic and a state in statesWithTraffic. 786 for (int tag : tagsWithTraffic) { 787 for (int state : statesWithTraffic) { 788 result = getNetworkStatsForTagState(i, tag, state); 789 resultsWithTraffic.add(new QueryResult(tag, state, result)); 790 result.close(); 791 result = null; 792 } 793 } 794 795 // Expect that the results are within a few percentage points of each other. 796 // This is ensures that FIN retransmits after the transfer is complete don't cause 797 // the test to be flaky. The test URL currently returns just over 100k so this 798 // should not be too noisy. It also ensures that the traffic sent by the test 799 // harness, which is untagged, won't cause a failure. 800 long firstTotal = resultsWithTraffic.get(0).total; 801 for (QueryResult queryResult : resultsWithTraffic) { 802 assertWithinPercentage(queryResult + "", firstTotal, queryResult.total, 16); 803 } 804 805 // Expect to see no traffic when querying for any tag in tagsWithNoTraffic or any 806 // state in statesWithNoTraffic. 807 for (int tag : tagsWithNoTraffic) { 808 for (int state : statesWithTraffic) { 809 result = getNetworkStatsForTagState(i, tag, state); 810 assertAlmostNoUnexpectedTraffic(result, tag, state, firstTotal / 100); 811 result.close(); 812 result = null; 813 } 814 } 815 for (int tag : tagsWithTraffic) { 816 for (int state : statesWithNoTraffic) { 817 result = getNetworkStatsForTagState(i, tag, state); 818 assertAlmostNoUnexpectedTraffic(result, tag, state, firstTotal / 100); 819 result.close(); 820 result = null; 821 } 822 } 823 } finally { 824 if (result != null) { 825 result.close(); 826 } 827 } 828 setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny"); 829 try { 830 result = mNsm.queryDetailsForUidTag( 831 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i), 832 mStartTime, mEndTime, Process.myUid(), NETWORK_TAG); 833 fail("negative testUidDetails fails: no exception thrown."); 834 } catch (SecurityException e) { 835 // expected outcome 836 } 837 } 838 } 839 840 @Test testCallback()841 public void testCallback() throws Exception { 842 for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) { 843 // Relatively large tolerance to accommodate for history bucket size. 844 if (!shouldTestThisNetworkType(i)) { 845 continue; 846 } 847 requestNetworkAndGenerateTraffic(i, SHORT_TOLERANCE); 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