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