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