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