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