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.os.ParcelFileDescriptor;
30 import android.os.Process;
31 import android.os.RemoteException;
32 import android.telephony.TelephonyManager;
33 import android.test.InstrumentationTestCase;
34 import android.util.Log;
35 
36 import java.io.FileInputStream;
37 import java.io.IOException;
38 import java.io.InputStream;
39 import java.io.InputStreamReader;
40 import java.net.URL;
41 import java.text.MessageFormat;
42 import java.util.Scanner;
43 import java.net.HttpURLConnection;
44 
45 import libcore.io.IoUtils;
46 import libcore.io.Streams;
47 
48 public class NetworkUsageStatsTest extends InstrumentationTestCase {
49     private static final String LOG_TAG = "NetworkUsageStatsTest";
50     private static final String APPOPS_SET_SHELL_COMMAND = "appops set {0} {1} {2}";
51     private static final String APPOPS_GET_SHELL_COMMAND = "appops get {0} {1}";
52 
53     private static final long MINUTE = 1000 * 60;
54     private static final int TIMEOUT_MILLIS = 15000;
55 
56     private interface NetworkInterfaceToTest {
getNetworkType()57         int getNetworkType();
getTransportType()58         int getTransportType();
getSystemFeature()59         String getSystemFeature();
getErrorMessage()60         String getErrorMessage();
61     }
62 
63     private static final NetworkInterfaceToTest[] sNetworkInterfacesToTest =
64             new NetworkInterfaceToTest[] {
65                     new NetworkInterfaceToTest() {
66                         @Override
67                         public int getNetworkType() {
68                             return ConnectivityManager.TYPE_WIFI;
69                         }
70 
71                         @Override
72                         public int getTransportType() {
73                             return NetworkCapabilities.TRANSPORT_WIFI;
74                         }
75 
76                         @Override
77                         public String getSystemFeature() {
78                             return PackageManager.FEATURE_WIFI;
79                         }
80 
81                         @Override
82                         public String getErrorMessage() {
83                             return " Please make sure you are connected to a WiFi access point.";
84                         }
85                     },
86                     new NetworkInterfaceToTest() {
87                         @Override
88                         public int getNetworkType() {
89                             return ConnectivityManager.TYPE_MOBILE;
90                         }
91 
92                         @Override
93                         public int getTransportType() {
94                             return NetworkCapabilities.TRANSPORT_CELLULAR;
95                         }
96 
97                         @Override
98                         public String getSystemFeature() {
99                             return PackageManager.FEATURE_TELEPHONY;
100                         }
101 
102                         @Override
103                         public String getErrorMessage() {
104                             return " Please make sure you have added a SIM card with data plan to" +
105                                     " your phone, have enabled data over cellular and in case of" +
106                                     " dual SIM devices, have selected the right SIM " +
107                                     "for data connection.";
108                         }
109                     }
110     };
111 
112     private NetworkStatsManager mNsm;
113     private ConnectivityManager mCm;
114     private PackageManager mPm;
115     private long mStartTime;
116     private long mEndTime;
117 
118     private long mBytesRead;
119     private String mWriteSettingsMode;
120     private String mUsageStatsMode;
121 
exerciseRemoteHost(Network network)122     private void exerciseRemoteHost(Network network) throws Exception {
123         NetworkInfo networkInfo = mCm.getNetworkInfo(network);
124         if (networkInfo == null) {
125             Log.w(LOG_TAG, "Network info is null");
126         } else {
127             Log.w(LOG_TAG, "Network: " + networkInfo.toString());
128         }
129         InputStreamReader in = null;
130         HttpURLConnection urlc = null;
131         String originalKeepAlive = System.getProperty("http.keepAlive");
132         System.setProperty("http.keepAlive", "false");
133         try {
134             urlc = (HttpURLConnection) network.openConnection(new URL(
135                     "http://www.265.com/"));
136             urlc.setConnectTimeout(TIMEOUT_MILLIS);
137             urlc.setUseCaches(false);
138             urlc.connect();
139             boolean ping = urlc.getResponseCode() == 200;
140             if (ping) {
141                 in = new InputStreamReader(
142                         (InputStream) urlc.getContent());
143 
144                 mBytesRead = 0;
145                 while (in.read() != -1) ++mBytesRead;
146             }
147         } catch (Exception e) {
148             Log.i(LOG_TAG, "Badness during exercising remote server: " + e);
149         } finally {
150             if (in != null) {
151                 try {
152                     in.close();
153                 } catch (IOException e) {
154                     // don't care
155                 }
156             }
157             if (urlc != null) {
158                 urlc.disconnect();
159             }
160             if (originalKeepAlive == null) {
161                 System.clearProperty("http.keepAlive");
162             } else {
163                 System.setProperty("http.keepAlive", originalKeepAlive);
164             }
165         }
166     }
167 
168     @Override
setUp()169     protected void setUp() throws Exception {
170         super.setUp();
171         mNsm = (NetworkStatsManager) getInstrumentation().getContext()
172                 .getSystemService(Context.NETWORK_STATS_SERVICE);
173 
174         mCm = (ConnectivityManager) getInstrumentation().getContext()
175                 .getSystemService(Context.CONNECTIVITY_SERVICE);
176 
177         mPm = getInstrumentation().getContext().getPackageManager();
178 
179         mWriteSettingsMode = getAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS);
180         setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, "allow");
181         mUsageStatsMode = getAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS);
182     }
183 
184     @Override
tearDown()185     protected void tearDown() throws Exception {
186         if (mWriteSettingsMode != null) {
187             setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, mWriteSettingsMode);
188         }
189         if (mUsageStatsMode != null) {
190             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, mUsageStatsMode);
191         }
192         super.tearDown();
193     }
194 
setAppOpsMode(String appop, String mode)195     private void setAppOpsMode(String appop, String mode) throws Exception {
196         final String command = MessageFormat.format(APPOPS_SET_SHELL_COMMAND,
197                 getInstrumentation().getContext().getPackageName(), appop, mode);
198         ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
199                 .executeShellCommand(command);
200         try {
201             Streams.readFully(new FileInputStream(pfd.getFileDescriptor()));
202         } finally {
203             IoUtils.closeQuietly(pfd.getFileDescriptor());
204         }
205     }
206 
getAppOpsMode(String appop)207     private String getAppOpsMode(String appop) throws Exception {
208         String result;
209         final String command = MessageFormat.format(APPOPS_GET_SHELL_COMMAND,
210                 getInstrumentation().getContext().getPackageName(), appop);
211         ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
212                 .executeShellCommand(command);
213         try {
214             result = convertStreamToString(new FileInputStream(pfd.getFileDescriptor()));
215         } finally {
216             IoUtils.closeQuietly(pfd.getFileDescriptor());
217         }
218         if (result == null) {
219             Log.w(LOG_TAG, "App op " + appop + " could not be read.");
220         }
221         return result;
222     }
223 
convertStreamToString(InputStream is)224     private static String convertStreamToString(InputStream is) {
225         try (Scanner scanner = new Scanner(is).useDelimiter("\\A")) {
226             return scanner.hasNext() ? scanner.next() : null;
227         }
228     }
229 
230     private class NetworkCallback extends ConnectivityManager.NetworkCallback {
231         private long mTolerance;
232         public boolean success;
233 
NetworkCallback(long tolerance)234         NetworkCallback(long tolerance) {
235             mTolerance = tolerance;
236             success = false;
237         }
238 
239         @Override
onAvailable(Network network)240         public void onAvailable(Network network) {
241             try {
242                 mStartTime = System.currentTimeMillis() - mTolerance;
243                 exerciseRemoteHost(network);
244                 mEndTime = System.currentTimeMillis() + mTolerance;
245                 success = true;
246                 synchronized(NetworkUsageStatsTest.this) {
247                     NetworkUsageStatsTest.this.notify();
248                 }
249             } catch (Exception e) {
250                 Log.w(LOG_TAG, "exercising remote host failed.", e);
251                 success = false;
252             }
253         }
254     }
255 
shouldTestThisNetworkType(int networkTypeIndex, final long tolerance)256     private boolean shouldTestThisNetworkType(int networkTypeIndex, final long tolerance)
257             throws Exception {
258         boolean hasFeature = mPm.hasSystemFeature(
259                 sNetworkInterfacesToTest[networkTypeIndex].getSystemFeature());
260         if (!hasFeature) {
261             return false;
262         }
263         NetworkCallback callback = new NetworkCallback(tolerance);
264         mCm.requestNetwork(new NetworkRequest.Builder()
265                 .addTransportType(sNetworkInterfacesToTest[networkTypeIndex].getTransportType())
266                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
267                 .build(), callback);
268         synchronized(this) {
269             try {
270                 wait((int)(TIMEOUT_MILLIS * 1.2));
271             } catch (InterruptedException e) {
272             }
273         }
274         if (callback.success) {
275             return true;
276         }
277 
278         // This will always fail at this point as we know 'hasFeature' is true.
279         assertFalse (sNetworkInterfacesToTest[networkTypeIndex].getSystemFeature() +
280                 " is a reported system feature, " +
281                 "however no corresponding connected network interface was found or the attempt " +
282                 "to connect has timed out (timeout = " + TIMEOUT_MILLIS + "ms)." +
283                 sNetworkInterfacesToTest[networkTypeIndex].getErrorMessage(), hasFeature);
284         return false;
285     }
286 
getSubscriberId(int networkIndex)287     private String getSubscriberId(int networkIndex) {
288         int networkType = sNetworkInterfacesToTest[networkIndex].getNetworkType();
289         if (ConnectivityManager.TYPE_MOBILE == networkType) {
290             TelephonyManager tm = (TelephonyManager) getInstrumentation().getContext()
291                     .getSystemService(Context.TELEPHONY_SERVICE);
292             return tm.getSubscriberId();
293         }
294         return "";
295     }
296 
testDeviceSummary()297     public void testDeviceSummary() throws Exception {
298         for (int i = 0; i < sNetworkInterfacesToTest.length; ++i) {
299             if (!shouldTestThisNetworkType(i, MINUTE/2)) {
300                 continue;
301             }
302             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
303             NetworkStats.Bucket bucket = null;
304             try {
305                 bucket = mNsm.querySummaryForDevice(
306                         sNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
307                         mStartTime, mEndTime);
308             } catch (RemoteException | SecurityException e) {
309                 fail("testDeviceSummary fails with exception: " + e.toString());
310             }
311             assertNotNull(bucket);
312             assertTimestamps(bucket);
313             assertEquals(bucket.getState(), NetworkStats.Bucket.STATE_ALL);
314             assertEquals(bucket.getUid(), NetworkStats.Bucket.UID_ALL);
315             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
316             try {
317                 bucket = mNsm.querySummaryForDevice(
318                         sNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
319                         mStartTime, mEndTime);
320                 fail("negative testDeviceSummary fails: no exception thrown.");
321             } catch (RemoteException e) {
322                 fail("testDeviceSummary fails with exception: " + e.toString());
323             } catch (SecurityException e) {
324                 // expected outcome
325             }
326         }
327     }
328 
testUserSummary()329     public void testUserSummary() throws Exception {
330         for (int i = 0; i < sNetworkInterfacesToTest.length; ++i) {
331             if (!shouldTestThisNetworkType(i, MINUTE/2)) {
332                 continue;
333             }
334             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
335             NetworkStats.Bucket bucket = null;
336             try {
337                 bucket = mNsm.querySummaryForUser(
338                         sNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
339                         mStartTime, mEndTime);
340             } catch (RemoteException | SecurityException e) {
341                 fail("testUserSummary fails with exception: " + e.toString());
342             }
343             assertNotNull(bucket);
344             assertTimestamps(bucket);
345             assertEquals(bucket.getState(), NetworkStats.Bucket.STATE_ALL);
346             assertEquals(bucket.getUid(), NetworkStats.Bucket.UID_ALL);
347             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
348             try {
349                 bucket = mNsm.querySummaryForUser(
350                         sNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
351                         mStartTime, mEndTime);
352                 fail("negative testUserSummary fails: no exception thrown.");
353             } catch (RemoteException e) {
354                 fail("testUserSummary fails with exception: " + e.toString());
355             } catch (SecurityException e) {
356                 // expected outcome
357             }
358         }
359     }
360 
testAppSummary()361     public void testAppSummary() throws Exception {
362         for (int i = 0; i < sNetworkInterfacesToTest.length; ++i) {
363             if (!shouldTestThisNetworkType(i, MINUTE/2)) {
364                 continue;
365             }
366             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
367             NetworkStats result = null;
368             try {
369                 result = mNsm.querySummary(
370                         sNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
371                         mStartTime, mEndTime);
372                 assertTrue(result != null);
373                 NetworkStats.Bucket bucket = new NetworkStats.Bucket();
374                 long totalTxPackets = 0;
375                 long totalRxPackets = 0;
376                 long totalTxBytes = 0;
377                 long totalRxBytes = 0;
378                 while (result.hasNextBucket()) {
379                     assertTrue(result.getNextBucket(bucket));
380                     assertTimestamps(bucket);
381                     if (bucket.getUid() == Process.myUid()) {
382                         totalTxPackets += bucket.getTxPackets();
383                         totalRxPackets += bucket.getRxPackets();
384                         totalTxBytes += bucket.getTxBytes();
385                         totalRxBytes += bucket.getRxBytes();
386                     }
387                 }
388                 assertFalse(result.getNextBucket(bucket));
389                 assertTrue("No Rx bytes usage for uid " + Process.myUid(), totalRxBytes > 0);
390                 assertTrue("No Rx packets usage for uid " + Process.myUid(), totalRxPackets > 0);
391                 assertTrue("No Tx bytes usage for uid " + Process.myUid(), totalTxBytes > 0);
392                 assertTrue("No Tx packets usage for uid " + Process.myUid(), totalTxPackets > 0);
393             } catch (RemoteException | SecurityException e) {
394                 fail("testAppSummary fails with exception: " + e.toString());
395             } finally {
396                 if (result != null) {
397                     result.close();
398                 }
399             }
400             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
401             try {
402                 result = mNsm.querySummary(
403                         sNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
404                         mStartTime, mEndTime);
405                 fail("negative testAppSummary fails: no exception thrown.");
406             } catch (RemoteException e) {
407                 fail("testAppSummary fails with exception: " + e.toString());
408             } catch (SecurityException e) {
409                 // expected outcome
410             }
411         }
412     }
413 
testAppDetails()414     public void testAppDetails() throws Exception {
415         for (int i = 0; i < sNetworkInterfacesToTest.length; ++i) {
416             // Relatively large tolerance to accommodate for history bucket size.
417             if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
418                 continue;
419             }
420             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
421             NetworkStats result = null;
422             try {
423                 result = mNsm.queryDetails(
424                         sNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
425                         mStartTime, mEndTime);
426                 assertTrue(result != null);
427                 NetworkStats.Bucket bucket = new NetworkStats.Bucket();
428                 long totalTxPackets = 0;
429                 long totalRxPackets = 0;
430                 long totalTxBytes = 0;
431                 long totalRxBytes = 0;
432                 while (result.hasNextBucket()) {
433                     assertTrue(result.getNextBucket(bucket));
434                     assertTimestamps(bucket);
435                     assertEquals(bucket.getState(), NetworkStats.Bucket.STATE_ALL);
436                     if (bucket.getUid() == Process.myUid()) {
437                         totalTxPackets += bucket.getTxPackets();
438                         totalRxPackets += bucket.getRxPackets();
439                         totalTxBytes += bucket.getTxBytes();
440                         totalRxBytes += bucket.getRxBytes();
441                     }
442                 }
443                 assertFalse(result.getNextBucket(bucket));
444                 assertTrue("No Rx bytes usage for uid " + Process.myUid(), totalRxBytes > 0);
445                 assertTrue("No Rx packets usage for uid " + Process.myUid(), totalRxPackets > 0);
446                 assertTrue("No Tx bytes usage for uid " + Process.myUid(), totalTxBytes > 0);
447                 assertTrue("No Tx packets usage for uid " + Process.myUid(), totalTxPackets > 0);
448             } catch (RemoteException | SecurityException e) {
449                 fail("testAppDetails fails with exception: " + e.toString());
450             } finally {
451                 if (result != null) {
452                     result.close();
453                 }
454             }
455             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
456             try {
457                 result = mNsm.queryDetails(
458                         sNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
459                         mStartTime, mEndTime);
460                 fail("negative testAppDetails fails: no exception thrown.");
461             } catch (RemoteException e) {
462                 fail("testAppDetails fails with exception: " + e.toString());
463             } catch (SecurityException e) {
464                 // expected outcome
465             }
466         }
467     }
468 
testUidDetails()469     public void testUidDetails() throws Exception {
470         for (int i = 0; i < sNetworkInterfacesToTest.length; ++i) {
471             // Relatively large tolerance to accommodate for history bucket size.
472             if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
473                 continue;
474             }
475             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
476             NetworkStats result = null;
477             try {
478                 result = mNsm.queryDetailsForUid(
479                         sNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
480                         mStartTime, mEndTime, Process.myUid());
481                 assertTrue(result != null);
482                 NetworkStats.Bucket bucket = new NetworkStats.Bucket();
483                 long totalTxPackets = 0;
484                 long totalRxPackets = 0;
485                 long totalTxBytes = 0;
486                 long totalRxBytes = 0;
487                 while (result.hasNextBucket()) {
488                     assertTrue(result.getNextBucket(bucket));
489                     assertTimestamps(bucket);
490                     assertEquals(bucket.getState(), NetworkStats.Bucket.STATE_ALL);
491                     assertEquals(bucket.getUid(), Process.myUid());
492                     totalTxPackets += bucket.getTxPackets();
493                     totalRxPackets += bucket.getRxPackets();
494                     totalTxBytes += bucket.getTxBytes();
495                     totalRxBytes += bucket.getRxBytes();
496                 }
497                 assertFalse(result.getNextBucket(bucket));
498                 assertTrue("No Rx bytes usage for uid " + Process.myUid(), totalRxBytes > 0);
499                 assertTrue("No Rx packets usage for uid " + Process.myUid(), totalRxPackets > 0);
500                 assertTrue("No Tx bytes usage for uid " + Process.myUid(), totalTxBytes > 0);
501                 assertTrue("No Tx packets usage for uid " + Process.myUid(), totalTxPackets > 0);
502             } catch (RemoteException | SecurityException e) {
503                 fail("testUidDetails 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.queryDetailsForUid(
512                         sNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
513                         mStartTime, mEndTime, Process.myUid());
514                 fail("negative testUidDetails fails: no exception thrown.");
515             } catch (RemoteException e) {
516                 fail("testUidDetails fails with exception: " + e.toString());
517             } catch (SecurityException e) {
518                 // expected outcome
519             }
520         }
521     }
522 
assertTimestamps(final NetworkStats.Bucket bucket)523     private void assertTimestamps(final NetworkStats.Bucket bucket) {
524         assertTrue("Start timestamp " + bucket.getStartTimeStamp() + " is less than " +
525                 mStartTime, bucket.getStartTimeStamp() >= mStartTime);
526         assertTrue("End timestamp " + bucket.getEndTimeStamp() + " is greater than " +
527                 mEndTime, bucket.getEndTimeStamp() <= mEndTime);
528     }
529 }
530