• 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.BufferedInputStream;
86 import java.io.IOException;
87 import java.io.InputStream;
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 String mWriteSettingsMode;
214     private String mUsageStatsMode;
215 
exerciseRemoteHost(Network network, URL url)216     private void exerciseRemoteHost(Network network, URL url) throws Exception {
217         NetworkInfo networkInfo = mCm.getNetworkInfo(network);
218         if (networkInfo == null) {
219             Log.w(LOG_TAG, "Network info is null");
220         } else {
221             Log.w(LOG_TAG, "Network: " + networkInfo.toString());
222         }
223         BufferedInputStream in = null;
224         HttpURLConnection urlc = null;
225         String originalKeepAlive = System.getProperty("http.keepAlive");
226         System.setProperty("http.keepAlive", "false");
227         try {
228             TrafficStats.setThreadStatsTag(NETWORK_TAG);
229             urlc = (HttpURLConnection) network.openConnection(url);
230             urlc.setConnectTimeout(TIMEOUT_MILLIS);
231             urlc.setReadTimeout(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 BufferedInputStream((InputStream) urlc.getContent());
240                 while (in.read() != -1) {
241                     // Comments to suppress lint error.
242                 }
243             }
244         } catch (Exception e) {
245             Log.i(LOG_TAG, "Badness during exercising remote server: " + e);
246         } finally {
247             TrafficStats.clearThreadStatsTag();
248             if (in != null) {
249                 try {
250                     in.close();
251                 } catch (IOException e) {
252                     // don't care
253                 }
254             }
255             if (urlc != null) {
256                 urlc.disconnect();
257             }
258             if (originalKeepAlive == null) {
259                 System.clearProperty("http.keepAlive");
260             } else {
261                 System.setProperty("http.keepAlive", originalKeepAlive);
262             }
263         }
264     }
265 
266     @Before
setUp()267     public void setUp() throws Exception {
268         mContext = InstrumentationRegistry.getContext();
269         mNsm = mContext.getSystemService(NetworkStatsManager.class);
270         mNsm.setPollForce(true);
271 
272         mCm = mContext.getSystemService(ConnectivityManager.class);
273         mPm = mContext.getPackageManager();
274         mPkg = mContext.getPackageName();
275 
276         mInstrumentation = InstrumentationRegistry.getInstrumentation();
277         mWriteSettingsMode = getAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS);
278         setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, "allow");
279         mUsageStatsMode = getAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS);
280     }
281 
282     @After
tearDown()283     public void tearDown() throws Exception {
284         if (mWriteSettingsMode != null) {
285             setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, mWriteSettingsMode);
286         }
287         if (mUsageStatsMode != null) {
288             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, mUsageStatsMode);
289         }
290     }
291 
setAppOpsMode(String appop, String mode)292     private void setAppOpsMode(String appop, String mode) throws Exception {
293         final String command = MessageFormat.format(APPOPS_SET_SHELL_COMMAND, mPkg, appop, mode);
294         SystemUtil.runShellCommand(mInstrumentation, command);
295     }
296 
getAppOpsMode(String appop)297     private String getAppOpsMode(String appop) throws Exception {
298         final String command = MessageFormat.format(APPOPS_GET_SHELL_COMMAND, mPkg, appop);
299         String result = SystemUtil.runShellCommand(mInstrumentation, command);
300         if (result == null) {
301             Log.w(LOG_TAG, "App op " + appop + " could not be read.");
302         }
303         return result;
304     }
305 
isInForeground()306     private boolean isInForeground() throws IOException {
307         String result = SystemUtil.runShellCommand(mInstrumentation,
308                 "cmd activity get-uid-state " + Process.myUid());
309         return result.contains("FOREGROUND");
310     }
311 
312     private class NetworkCallback extends ConnectivityManager.NetworkCallback {
313         private long mTolerance;
314         private URL mUrl;
315         public boolean success;
316         public boolean metered;
317         public boolean roaming;
318         public boolean isDefault;
319 
NetworkCallback(long tolerance, URL url)320         NetworkCallback(long tolerance, URL url) {
321             mTolerance = tolerance;
322             mUrl = url;
323             success = false;
324             metered = false;
325             roaming = false;
326             isDefault = false;
327         }
328 
329         // The test host only has IPv4. So on a dual-stack network where IPv6 connects before IPv4,
330         // we need to wait until IPv4 is available or the test will spuriously fail.
waitForHostResolution(Network network)331         private void waitForHostResolution(Network network) {
332             for (int i = 0; i < HOST_RESOLUTION_RETRIES; i++) {
333                 try {
334                     network.getAllByName(mUrl.getHost());
335                     return;
336                 } catch (UnknownHostException e) {
337                     SystemClock.sleep(HOST_RESOLUTION_INTERVAL_MS);
338                 }
339             }
340             fail(String.format("%s could not be resolved on network %s (%d attempts %dms apart)",
341                     mUrl.getHost(), network, HOST_RESOLUTION_RETRIES, HOST_RESOLUTION_INTERVAL_MS));
342         }
343 
344         @Override
onAvailable(Network network)345         public void onAvailable(Network network) {
346             try {
347                 mStartTime = System.currentTimeMillis() - mTolerance;
348                 isDefault = network.equals(mCm.getActiveNetwork());
349                 waitForHostResolution(network);
350                 exerciseRemoteHost(network, mUrl);
351                 mEndTime = System.currentTimeMillis() + mTolerance;
352                 success = true;
353                 metered = !mCm.getNetworkCapabilities(network)
354                         .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
355                 roaming = !mCm.getNetworkCapabilities(network)
356                         .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
357                 synchronized (NetworkStatsManagerTest.this) {
358                     NetworkStatsManagerTest.this.notify();
359                 }
360             } catch (Exception e) {
361                 Log.w(LOG_TAG, "exercising remote host failed.", e);
362                 success = false;
363             }
364         }
365     }
366 
shouldTestThisNetworkType(int networkTypeIndex, final long tolerance)367     private boolean shouldTestThisNetworkType(int networkTypeIndex, final long tolerance)
368             throws Exception {
369         boolean hasFeature = mPm.hasSystemFeature(
370                 mNetworkInterfacesToTest[networkTypeIndex].getSystemFeature());
371         if (!hasFeature) {
372             return false;
373         }
374         NetworkCallback callback = new NetworkCallback(tolerance, new URL(CHECK_CONNECTIVITY_URL));
375         mCm.requestNetwork(new NetworkRequest.Builder()
376                 .addTransportType(mNetworkInterfacesToTest[networkTypeIndex].getTransportType())
377                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
378                 .build(), callback);
379         synchronized (this) {
380             long now = System.currentTimeMillis();
381             final long deadline = (long) (now + TIMEOUT_MILLIS * 2.4);
382             while (!callback.success && now < deadline) {
383                 try {
384                     wait(deadline - now);
385                 } catch (InterruptedException e) {
386                 }
387                 now = System.currentTimeMillis();
388             }
389         }
390         mCm.unregisterNetworkCallback(callback);
391         if (callback.success) {
392             mNetworkInterfacesToTest[networkTypeIndex].setMetered(callback.metered);
393             mNetworkInterfacesToTest[networkTypeIndex].setRoaming(callback.roaming);
394             mNetworkInterfacesToTest[networkTypeIndex].setIsDefault(callback.isDefault);
395             return true;
396         }
397 
398         // This will always fail at this point as we know 'hasFeature' is true.
399         assertFalse(mNetworkInterfacesToTest[networkTypeIndex].getSystemFeature()
400                 + " is a reported system feature, "
401                 + "however no corresponding connected network interface was found or the attempt "
402                 + "to connect and read has timed out (timeout = " + (TIMEOUT_MILLIS * 2) + "ms)."
403                 + mNetworkInterfacesToTest[networkTypeIndex].getErrorMessage(), hasFeature);
404         return false;
405     }
406 
getSubscriberId(int networkIndex)407     private String getSubscriberId(int networkIndex) {
408         int networkType = mNetworkInterfacesToTest[networkIndex].getNetworkType();
409         if (ConnectivityManager.TYPE_MOBILE == networkType) {
410             TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
411             return ShellIdentityUtils.invokeMethodWithShellPermissions(tm,
412                     (telephonyManager) -> telephonyManager.getSubscriberId());
413         }
414         return "";
415     }
416 
417     @Test
testDeviceSummary()418     public void testDeviceSummary() throws Exception {
419         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
420             if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
421                 continue;
422             }
423             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
424             NetworkStats.Bucket bucket = null;
425             try {
426                 bucket = mNsm.querySummaryForDevice(
427                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
428                         mStartTime, mEndTime);
429             } catch (RemoteException | SecurityException e) {
430                 fail("testDeviceSummary fails with exception: " + e.toString());
431             }
432             assertNotNull(bucket);
433             assertTimestamps(bucket);
434             assertEquals(bucket.getState(), STATE_ALL);
435             assertEquals(bucket.getUid(), UID_ALL);
436             assertEquals(bucket.getMetered(), METERED_ALL);
437             assertEquals(bucket.getRoaming(), ROAMING_ALL);
438             assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
439             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
440             try {
441                 bucket = mNsm.querySummaryForDevice(
442                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
443                         mStartTime, mEndTime);
444                 fail("negative testDeviceSummary fails: no exception thrown.");
445             } catch (RemoteException e) {
446                 fail("testDeviceSummary fails with exception: " + e.toString());
447             } catch (SecurityException e) {
448                 // expected outcome
449             }
450         }
451     }
452 
453     @Test
testUserSummary()454     public void testUserSummary() throws Exception {
455         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
456             if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
457                 continue;
458             }
459             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
460             NetworkStats.Bucket bucket = null;
461             try {
462                 bucket = mNsm.querySummaryForUser(
463                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
464                         mStartTime, mEndTime);
465             } catch (RemoteException | SecurityException e) {
466                 fail("testUserSummary fails with exception: " + e.toString());
467             }
468             assertNotNull(bucket);
469             assertTimestamps(bucket);
470             assertEquals(bucket.getState(), STATE_ALL);
471             assertEquals(bucket.getUid(), UID_ALL);
472             assertEquals(bucket.getMetered(), METERED_ALL);
473             assertEquals(bucket.getRoaming(), ROAMING_ALL);
474             assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
475             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
476             try {
477                 bucket = mNsm.querySummaryForUser(
478                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
479                         mStartTime, mEndTime);
480                 fail("negative testUserSummary fails: no exception thrown.");
481             } catch (RemoteException e) {
482                 fail("testUserSummary fails with exception: " + e.toString());
483             } catch (SecurityException e) {
484                 // expected outcome
485             }
486         }
487     }
488 
489     @Test
testAppSummary()490     public void testAppSummary() throws Exception {
491         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
492             // Use tolerance value that large enough to make sure stats of at
493             // least one bucket is included. However, this is possible that
494             // the test will see data of different app but with the same UID
495             // that created before testing.
496             // TODO: Consider query stats before testing and use the difference to verify.
497             if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
498                 continue;
499             }
500             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
501             NetworkStats result = null;
502             try {
503                 result = mNsm.querySummary(
504                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
505                         mStartTime, mEndTime);
506                 assertNotNull(result);
507                 NetworkStats.Bucket bucket = new NetworkStats.Bucket();
508                 long totalTxPackets = 0;
509                 long totalRxPackets = 0;
510                 long totalTxBytes = 0;
511                 long totalRxBytes = 0;
512                 boolean hasCorrectMetering = false;
513                 boolean hasCorrectRoaming = false;
514                 boolean hasCorrectDefaultStatus = false;
515                 int expectedMetering = mNetworkInterfacesToTest[i].getMetered()
516                         ? METERED_YES : METERED_NO;
517                 int expectedRoaming = mNetworkInterfacesToTest[i].getRoaming()
518                         ? ROAMING_YES : ROAMING_NO;
519                 int expectedDefaultStatus = mNetworkInterfacesToTest[i].getIsDefault()
520                         ? DEFAULT_NETWORK_YES : DEFAULT_NETWORK_NO;
521                 while (result.hasNextBucket()) {
522                     assertTrue(result.getNextBucket(bucket));
523                     assertTimestamps(bucket);
524                     hasCorrectMetering |= bucket.getMetered() == expectedMetering;
525                     hasCorrectRoaming |= bucket.getRoaming() == expectedRoaming;
526                     if (bucket.getUid() == Process.myUid()) {
527                         totalTxPackets += bucket.getTxPackets();
528                         totalRxPackets += bucket.getRxPackets();
529                         totalTxBytes += bucket.getTxBytes();
530                         totalRxBytes += bucket.getRxBytes();
531                         hasCorrectDefaultStatus |=
532                                 bucket.getDefaultNetworkStatus() == expectedDefaultStatus;
533                     }
534                 }
535                 assertFalse(result.getNextBucket(bucket));
536                 assertTrue("Incorrect metering for NetworkType: "
537                         + mNetworkInterfacesToTest[i].getNetworkType(), hasCorrectMetering);
538                 assertTrue("Incorrect roaming for NetworkType: "
539                         + mNetworkInterfacesToTest[i].getNetworkType(), hasCorrectRoaming);
540                 assertTrue("Incorrect isDefault for NetworkType: "
541                         + mNetworkInterfacesToTest[i].getNetworkType(), hasCorrectDefaultStatus);
542                 assertTrue("No Rx bytes usage for uid " + Process.myUid(), totalRxBytes > 0);
543                 assertTrue("No Rx packets usage for uid " + Process.myUid(), totalRxPackets > 0);
544                 assertTrue("No Tx bytes usage for uid " + Process.myUid(), totalTxBytes > 0);
545                 assertTrue("No Tx packets usage for uid " + Process.myUid(), totalTxPackets > 0);
546             } finally {
547                 if (result != null) {
548                     result.close();
549                 }
550             }
551             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
552             try {
553                 result = mNsm.querySummary(
554                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
555                         mStartTime, mEndTime);
556                 fail("negative testAppSummary fails: no exception thrown.");
557             } catch (RemoteException e) {
558                 fail("testAppSummary fails with exception: " + e.toString());
559             } catch (SecurityException e) {
560                 // expected outcome
561             }
562         }
563     }
564 
565     @Test
testAppDetails()566     public void testAppDetails() throws Exception {
567         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
568             // Relatively large tolerance to accommodate for history bucket size.
569             if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
570                 continue;
571             }
572             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
573             NetworkStats result = null;
574             try {
575                 result = mNsm.queryDetails(
576                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
577                         mStartTime, mEndTime);
578                 long totalBytesWithSubscriberId = getTotalAndAssertNotEmpty(result);
579 
580                 // Test without filtering by subscriberId
581                 result = mNsm.queryDetails(
582                         mNetworkInterfacesToTest[i].getNetworkType(), null,
583                         mStartTime, mEndTime);
584 
585                 assertTrue("More bytes with subscriberId filter than without.",
586                         getTotalAndAssertNotEmpty(result) >= totalBytesWithSubscriberId);
587             } catch (RemoteException | SecurityException e) {
588                 fail("testAppDetails fails with exception: " + e.toString());
589             } finally {
590                 if (result != null) {
591                     result.close();
592                 }
593             }
594             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
595             try {
596                 result = mNsm.queryDetails(
597                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
598                         mStartTime, mEndTime);
599                 fail("negative testAppDetails fails: no exception thrown.");
600             } catch (RemoteException e) {
601                 fail("testAppDetails fails with exception: " + e.toString());
602             } catch (SecurityException e) {
603                 // expected outcome
604             }
605         }
606     }
607 
608     @Test
testUidDetails()609     public void testUidDetails() throws Exception {
610         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
611             // Relatively large tolerance to accommodate for history bucket size.
612             if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
613                 continue;
614             }
615             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
616             NetworkStats result = null;
617             try {
618                 result = mNsm.queryDetailsForUid(
619                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
620                         mStartTime, mEndTime, Process.myUid());
621                 assertNotNull(result);
622                 NetworkStats.Bucket bucket = new NetworkStats.Bucket();
623                 long totalTxPackets = 0;
624                 long totalRxPackets = 0;
625                 long totalTxBytes = 0;
626                 long totalRxBytes = 0;
627                 while (result.hasNextBucket()) {
628                     assertTrue(result.getNextBucket(bucket));
629                     assertTimestamps(bucket);
630                     assertEquals(bucket.getState(), STATE_ALL);
631                     assertEquals(bucket.getMetered(), METERED_ALL);
632                     assertEquals(bucket.getRoaming(), ROAMING_ALL);
633                     assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
634                     assertEquals(bucket.getUid(), Process.myUid());
635                     totalTxPackets += bucket.getTxPackets();
636                     totalRxPackets += bucket.getRxPackets();
637                     totalTxBytes += bucket.getTxBytes();
638                     totalRxBytes += bucket.getRxBytes();
639                 }
640                 assertFalse(result.getNextBucket(bucket));
641                 assertTrue("No Rx bytes usage for uid " + Process.myUid(), totalRxBytes > 0);
642                 assertTrue("No Rx packets usage for uid " + Process.myUid(), totalRxPackets > 0);
643                 assertTrue("No Tx bytes usage for uid " + Process.myUid(), totalTxBytes > 0);
644                 assertTrue("No Tx packets usage for uid " + Process.myUid(), totalTxPackets > 0);
645             } finally {
646                 if (result != null) {
647                     result.close();
648                 }
649             }
650             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
651             try {
652                 result = mNsm.queryDetailsForUid(
653                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
654                         mStartTime, mEndTime, Process.myUid());
655                 fail("negative testUidDetails fails: no exception thrown.");
656             } catch (SecurityException e) {
657                 // expected outcome
658             }
659         }
660     }
661 
662     @Test
testTagDetails()663     public void testTagDetails() throws Exception {
664         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
665             // Relatively large tolerance to accommodate for history bucket size.
666             if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
667                 continue;
668             }
669             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
670             NetworkStats result = null;
671             try {
672                 result = mNsm.queryDetailsForUidTag(
673                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
674                         mStartTime, mEndTime, Process.myUid(), NETWORK_TAG);
675                 assertNotNull(result);
676                 NetworkStats.Bucket bucket = new NetworkStats.Bucket();
677                 long totalTxPackets = 0;
678                 long totalRxPackets = 0;
679                 long totalTxBytes = 0;
680                 long totalRxBytes = 0;
681                 while (result.hasNextBucket()) {
682                     assertTrue(result.getNextBucket(bucket));
683                     assertTimestamps(bucket);
684                     assertEquals(bucket.getState(), STATE_ALL);
685                     assertEquals(bucket.getMetered(), METERED_ALL);
686                     assertEquals(bucket.getRoaming(), ROAMING_ALL);
687                     assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
688                     assertEquals(bucket.getUid(), Process.myUid());
689                     if (bucket.getTag() == NETWORK_TAG) {
690                         totalTxPackets += bucket.getTxPackets();
691                         totalRxPackets += bucket.getRxPackets();
692                         totalTxBytes += bucket.getTxBytes();
693                         totalRxBytes += bucket.getRxBytes();
694                     }
695                 }
696                 assertTrue("No Rx bytes tagged with 0x" + Integer.toHexString(NETWORK_TAG)
697                         + " for uid " + Process.myUid(), totalRxBytes > 0);
698                 assertTrue("No Rx packets tagged with 0x" + Integer.toHexString(NETWORK_TAG)
699                         + " for uid " + Process.myUid(), totalRxPackets > 0);
700                 assertTrue("No Tx bytes tagged with 0x" + Integer.toHexString(NETWORK_TAG)
701                         + " for uid " + Process.myUid(), totalTxBytes > 0);
702                 assertTrue("No Tx packets tagged with 0x" + Integer.toHexString(NETWORK_TAG)
703                         + " for uid " + Process.myUid(), totalTxPackets > 0);
704             } finally {
705                 if (result != null) {
706                     result.close();
707                 }
708             }
709             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
710             try {
711                 result = mNsm.queryDetailsForUidTag(
712                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
713                         mStartTime, mEndTime, Process.myUid(), NETWORK_TAG);
714                 fail("negative testUidDetails fails: no exception thrown.");
715             } catch (SecurityException e) {
716                 // expected outcome
717             }
718         }
719     }
720 
721     class QueryResult {
722         public final int tag;
723         public final int state;
724         public final long total;
725 
QueryResult(int tag, int state, NetworkStats stats)726         QueryResult(int tag, int state, NetworkStats stats) {
727             this.tag = tag;
728             this.state = state;
729             total = getTotalAndAssertNotEmpty(stats, tag, state);
730         }
731 
toString()732         public String toString() {
733             return String.format("QueryResult(tag=%s state=%s total=%d)",
734                     tagToString(tag), stateToString(state), total);
735         }
736     }
737 
getNetworkStatsForTagState(int i, int tag, int state)738     private NetworkStats getNetworkStatsForTagState(int i, int tag, int state) {
739         return mNsm.queryDetailsForUidTagState(
740                 mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
741                 mStartTime, mEndTime, Process.myUid(), tag, state);
742     }
743 
assertWithinPercentage(String msg, long expected, long actual, int percentage)744     private void assertWithinPercentage(String msg, long expected, long actual, int percentage) {
745         long lowerBound = expected * (100 - percentage) / 100;
746         long upperBound = expected * (100 + percentage) / 100;
747         msg = String.format("%s: %d not within %d%% of %d", msg, actual, percentage, expected);
748         assertTrue(msg, lowerBound <= actual);
749         assertTrue(msg, upperBound >= actual);
750     }
751 
assertAlmostNoUnexpectedTraffic(NetworkStats result, int expectedTag, int expectedState, long maxUnexpected)752     private void assertAlmostNoUnexpectedTraffic(NetworkStats result, int expectedTag,
753             int expectedState, long maxUnexpected) {
754         long total = 0;
755         NetworkStats.Bucket bucket = new NetworkStats.Bucket();
756         while (result.hasNextBucket()) {
757             assertTrue(result.getNextBucket(bucket));
758             total += bucket.getRxBytes() + bucket.getTxBytes();
759         }
760         if (total <= maxUnexpected) return;
761 
762         fail(String.format("More than %d bytes of traffic when querying for "
763                 + "tag %s state %s. Last bucket: uid=%d tag=%s state=%s bytes=%d/%d",
764                 maxUnexpected, tagToString(expectedTag), stateToString(expectedState),
765                 bucket.getUid(), tagToString(bucket.getTag()), stateToString(bucket.getState()),
766                 bucket.getRxBytes(), bucket.getTxBytes()));
767     }
768 
769     @Test
testUidTagStateDetails()770     public void testUidTagStateDetails() throws Exception {
771         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
772             // Relatively large tolerance to accommodate for history bucket size.
773             if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
774                 continue;
775             }
776             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
777             NetworkStats result = null;
778             try {
779                 int currentState = isInForeground() ? STATE_FOREGROUND : STATE_DEFAULT;
780                 int otherState = (currentState == STATE_DEFAULT) ? STATE_FOREGROUND : STATE_DEFAULT;
781 
782                 int[] tagsWithTraffic = {NETWORK_TAG, TAG_NONE};
783                 int[] statesWithTraffic = {currentState, STATE_ALL};
784                 ArrayList<QueryResult> resultsWithTraffic = new ArrayList<>();
785 
786                 int[] statesWithNoTraffic = {otherState};
787                 int[] tagsWithNoTraffic = {NETWORK_TAG + 1};
788                 ArrayList<QueryResult> resultsWithNoTraffic = new ArrayList<>();
789 
790                 // Expect to see traffic when querying for any combination of a tag in
791                 // tagsWithTraffic and a state in statesWithTraffic.
792                 for (int tag : tagsWithTraffic) {
793                     for (int state : statesWithTraffic) {
794                         result = getNetworkStatsForTagState(i, tag, state);
795                         resultsWithTraffic.add(new QueryResult(tag, state, result));
796                         result.close();
797                         result = null;
798                     }
799                 }
800 
801                 // Expect that the results are within a few percentage points of each other.
802                 // This is ensures that FIN retransmits after the transfer is complete don't cause
803                 // the test to be flaky. The test URL currently returns just over 100k so this
804                 // should not be too noisy. It also ensures that the traffic sent by the test
805                 // harness, which is untagged, won't cause a failure.
806                 long firstTotal = resultsWithTraffic.get(0).total;
807                 for (QueryResult queryResult : resultsWithTraffic) {
808                     assertWithinPercentage(queryResult + "", firstTotal, queryResult.total, 16);
809                 }
810 
811                 // Expect to see no traffic when querying for any tag in tagsWithNoTraffic or any
812                 // state in statesWithNoTraffic.
813                 for (int tag : tagsWithNoTraffic) {
814                     for (int state : statesWithTraffic) {
815                         result = getNetworkStatsForTagState(i, tag, state);
816                         assertAlmostNoUnexpectedTraffic(result, tag, state, firstTotal / 100);
817                         result.close();
818                         result = null;
819                     }
820                 }
821                 for (int tag : tagsWithTraffic) {
822                     for (int state : statesWithNoTraffic) {
823                         result = getNetworkStatsForTagState(i, tag, state);
824                         assertAlmostNoUnexpectedTraffic(result, tag, state, firstTotal / 100);
825                         result.close();
826                         result = null;
827                     }
828                 }
829             } finally {
830                 if (result != null) {
831                     result.close();
832                 }
833             }
834             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
835             try {
836                 result = mNsm.queryDetailsForUidTag(
837                         mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
838                         mStartTime, mEndTime, Process.myUid(), NETWORK_TAG);
839                 fail("negative testUidDetails fails: no exception thrown.");
840             } catch (SecurityException e) {
841                 // expected outcome
842             }
843         }
844     }
845 
846     @Test
testCallback()847     public void testCallback() throws Exception {
848         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
849             // Relatively large tolerance to accommodate for history bucket size.
850             if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
851                 continue;
852             }
853             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
854 
855             TestUsageCallback usageCallback = new TestUsageCallback();
856             HandlerThread thread = new HandlerThread("callback-thread");
857             thread.start();
858             Handler handler = new Handler(thread.getLooper());
859             mNsm.registerUsageCallback(mNetworkInterfacesToTest[i].getNetworkType(),
860                     getSubscriberId(i), THRESHOLD_BYTES, usageCallback, handler);
861 
862             // TODO: Force traffic and check whether the callback is invoked.
863             // Right now the test only covers whether the callback can be registered, but not
864             // whether it is invoked upon data usage since we don't have a scalable way of
865             // storing files of >2MB in CTS.
866 
867             mNsm.unregisterUsageCallback(usageCallback);
868 
869             // For T- devices, the registerUsageCallback invocation below will need a looper
870             // from the thread that calls into the API, which is not available in the test.
871             if (SdkLevel.isAtLeastT()) {
872                 mNsm.registerUsageCallback(mNetworkInterfacesToTest[i].getNetworkType(),
873                         getSubscriberId(i), THRESHOLD_BYTES, usageCallback);
874                 mNsm.unregisterUsageCallback(usageCallback);
875             }
876         }
877     }
878 
879     @DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
880     @Test
testDataMigrationUtils()881     public void testDataMigrationUtils() throws Exception {
882         final List<String> prefixes = List.of(PREFIX_UID, PREFIX_XT, PREFIX_UID_TAG);
883         for (final String prefix : prefixes) {
884             final long duration = TextUtils.equals(PREFIX_XT, prefix) ? TimeUnit.HOURS.toMillis(1)
885                     : TimeUnit.HOURS.toMillis(2);
886 
887             final NetworkStatsCollection collection =
888                     NetworkStatsDataMigrationUtils.readPlatformCollection(prefix, duration);
889 
890             final long now = System.currentTimeMillis();
891             final Set<Map.Entry<NetworkStatsCollection.Key, NetworkStatsHistory>> entries =
892                     collection.getEntries().entrySet();
893             for (final Map.Entry<NetworkStatsCollection.Key, NetworkStatsHistory> entry : entries) {
894                 for (final NetworkStatsHistory.Entry historyEntry : entry.getValue().getEntries()) {
895                     // Verify all value fields are reasonable.
896                     assertTrue(historyEntry.getBucketStart() <= now);
897                     assertTrue(historyEntry.getActiveTime() <= duration);
898                     assertTrue(historyEntry.getRxBytes() >= 0);
899                     assertTrue(historyEntry.getRxPackets() >= 0);
900                     assertTrue(historyEntry.getTxBytes() >= 0);
901                     assertTrue(historyEntry.getTxPackets() >= 0);
902                     assertTrue(historyEntry.getOperations() >= 0);
903                 }
904             }
905         }
906     }
907 
tagToString(Integer tag)908     private String tagToString(Integer tag) {
909         if (tag == null) return "null";
910         switch (tag) {
911             case TAG_NONE:
912                 return "TAG_NONE";
913             default:
914                 return "0x" + Integer.toHexString(tag);
915         }
916     }
917 
stateToString(Integer state)918     private String stateToString(Integer state) {
919         if (state == null) return "null";
920         switch (state) {
921             case STATE_ALL:
922                 return "STATE_ALL";
923             case STATE_DEFAULT:
924                 return "STATE_DEFAULT";
925             case STATE_FOREGROUND:
926                 return "STATE_FOREGROUND";
927         }
928         throw new IllegalArgumentException("Unknown state " + state);
929     }
930 
getTotalAndAssertNotEmpty(NetworkStats result, Integer expectedTag, Integer expectedState)931     private long getTotalAndAssertNotEmpty(NetworkStats result, Integer expectedTag,
932             Integer expectedState) {
933         assertTrue(result != null);
934         NetworkStats.Bucket bucket = new NetworkStats.Bucket();
935         long totalTxPackets = 0;
936         long totalRxPackets = 0;
937         long totalTxBytes = 0;
938         long totalRxBytes = 0;
939         while (result.hasNextBucket()) {
940             assertTrue(result.getNextBucket(bucket));
941             assertTimestamps(bucket);
942             if (expectedTag != null) assertEquals(bucket.getTag(), (int) expectedTag);
943             if (expectedState != null) assertEquals(bucket.getState(), (int) expectedState);
944             assertEquals(bucket.getMetered(), METERED_ALL);
945             assertEquals(bucket.getRoaming(), ROAMING_ALL);
946             assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
947             if (bucket.getUid() == Process.myUid()) {
948                 totalTxPackets += bucket.getTxPackets();
949                 totalRxPackets += bucket.getRxPackets();
950                 totalTxBytes += bucket.getTxBytes();
951                 totalRxBytes += bucket.getRxBytes();
952             }
953         }
954         assertFalse(result.getNextBucket(bucket));
955         String msg = String.format("uid %d tag %s state %s",
956                 Process.myUid(), tagToString(expectedTag), stateToString(expectedState));
957         assertTrue("No Rx bytes usage for " + msg, totalRxBytes > 0);
958         assertTrue("No Rx packets usage for " + msg, totalRxPackets > 0);
959         assertTrue("No Tx bytes usage for " + msg, totalTxBytes > 0);
960         assertTrue("No Tx packets usage for " + msg, totalTxPackets > 0);
961 
962         return totalRxBytes + totalTxBytes;
963     }
964 
getTotalAndAssertNotEmpty(NetworkStats result)965     private long getTotalAndAssertNotEmpty(NetworkStats result) {
966         return getTotalAndAssertNotEmpty(result, null, STATE_ALL);
967     }
968 
assertTimestamps(final NetworkStats.Bucket bucket)969     private void assertTimestamps(final NetworkStats.Bucket bucket) {
970         assertTrue("Start timestamp " + bucket.getStartTimeStamp() + " is less than "
971                 + mStartTime, bucket.getStartTimeStamp() >= mStartTime);
972         assertTrue("End timestamp " + bucket.getEndTimeStamp() + " is greater than "
973                 + mEndTime, bucket.getEndTimeStamp() <= mEndTime);
974     }
975 
976     private static class TestUsageCallback extends NetworkStatsManager.UsageCallback {
977         @Override
onThresholdReached(int networkType, String subscriberId)978         public void onThresholdReached(int networkType, String subscriberId) {
979             Log.v(LOG_TAG, "Called onThresholdReached for networkType=" + networkType
980                     + " subscriberId=" + subscriberId);
981         }
982     }
983 }
984