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