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