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