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