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