1 /*
2  * Copyright (C) 2011, The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy 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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.bandwidthtest;
18 
19 import android.app.UiAutomation;
20 import android.content.Context;
21 import android.net.ConnectivityManager;
22 import android.net.NetworkInfo.State;
23 import android.net.NetworkStats;
24 import android.net.NetworkStats.Entry;
25 import android.net.TrafficStats;
26 import android.net.wifi.WifiManager;
27 import android.os.Bundle;
28 import android.os.Environment;
29 import android.os.Process;
30 import android.os.SystemClock;
31 import android.telephony.TelephonyManager;
32 import android.test.InstrumentationTestCase;
33 import android.util.Log;
34 
35 import androidx.test.filters.LargeTest;
36 
37 import com.android.bandwidthtest.util.BandwidthTestUtil;
38 import com.android.bandwidthtest.util.ConnectionUtil;
39 
40 import java.io.File;
41 
42 /**
43  * Test that downloads files from a test server and reports the bandwidth metrics collected.
44  */
45 public class BandwidthTest extends InstrumentationTestCase {
46 
47     private static final String LOG_TAG = "BandwidthTest";
48     private final static String PROF_LABEL = "PROF_";
49     private final static String PROC_LABEL = "PROC_";
50     private final static int INSTRUMENTATION_IN_PROGRESS = 2;
51 
52     private final static String BASE_DIR =
53             Environment.getExternalStorageDirectory().getAbsolutePath();
54     private final static String TMP_FILENAME = "tmp.dat";
55     // Download 10.486 * 106 bytes (+ headers) from app engine test server.
56     private final int FILE_SIZE = 10485613;
57     private Context mContext;
58     private ConnectionUtil mConnectionUtil;
59     private TelephonyManager mTManager;
60     private int mUid;
61     private String mSsid;
62     private String mTestServer;
63     private String mDeviceId;
64     private BandwidthTestRunner mRunner;
65 
66 
67     @Override
setUp()68     protected void setUp() throws Exception {
69         super.setUp();
70         mRunner = (BandwidthTestRunner) getInstrumentation();
71         mSsid = mRunner.mSsid;
72         mTestServer = mRunner.mTestServer;
73         mContext = mRunner.getTargetContext();
74         mConnectionUtil = new ConnectionUtil(mContext);
75         mConnectionUtil.initialize();
76         Log.v(LOG_TAG, "Initialized mConnectionUtil");
77         mUid = Process.myUid();
78         mTManager = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
79         final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
80         try {
81             uiAutomation.adoptShellPermissionIdentity();
82             mDeviceId = mTManager.getDeviceId();
83         } finally {
84             uiAutomation.dropShellPermissionIdentity();
85         }
86     }
87 
88     @Override
tearDown()89     protected void tearDown() throws Exception {
90         mConnectionUtil.cleanUp();
91         super.tearDown();
92     }
93 
94     /**
95      * Ensure that downloading on wifi reports reasonable stats.
96      */
97     @LargeTest
testWifiDownload()98     public void testWifiDownload() throws Exception {
99         mConnectionUtil.wifiTestInit();
100         assertTrue("Could not connect to wifi!", setDeviceWifiAndAirplaneMode(mSsid));
101         downloadFile();
102     }
103 
104     /**
105      * Ensure that downloading on mobile reports reasonable stats.
106      */
107     @LargeTest
testMobileDownload()108     public void testMobileDownload() throws Exception {
109         // As part of the setup we disconnected from wifi; make sure we are connected to mobile and
110         // that we have data.
111         assertTrue("Do not have mobile data!", hasMobileData());
112         downloadFile();
113     }
114 
115     /**
116      * Helper method that downloads a file using http connection from a test server and reports the
117      * data usage stats to instrumentation out.
118      */
downloadFile()119     protected void downloadFile() throws Exception {
120         NetworkStats pre_test_stats = fetchDataFromProc(mUid);
121         String ts = Long.toString(System.currentTimeMillis());
122 
123         String targetUrl = BandwidthTestUtil.buildDownloadUrl(
124                 mTestServer, FILE_SIZE, mDeviceId, ts);
125         TrafficStats.startDataProfiling(mContext);
126         File tmpSaveFile = new File(BASE_DIR + File.separator + TMP_FILENAME);
127         assertTrue(BandwidthTestUtil.DownloadFromUrl(targetUrl, tmpSaveFile));
128         NetworkStats prof_stats = TrafficStats.stopDataProfiling(mContext);
129         Log.d(LOG_TAG, prof_stats.toString());
130 
131         NetworkStats post_test_stats = fetchDataFromProc(mUid);
132         NetworkStats proc_stats = post_test_stats.subtract(pre_test_stats);
133 
134         // Output measurements to instrumentation out, so that it can be compared to that of
135         // the server.
136         Bundle results = new Bundle();
137         results.putString("device_id", mDeviceId);
138         results.putString("timestamp", ts);
139         results.putInt("size", FILE_SIZE);
140         addStatsToResults(PROF_LABEL, prof_stats, results, mUid);
141         addStatsToResults(PROC_LABEL, proc_stats, results, mUid);
142         getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, results);
143 
144         // Clean up.
145         assertTrue(cleanUpFile(tmpSaveFile));
146     }
147 
148     /**
149      * Ensure that uploading on wifi reports reasonable stats.
150      */
151     @LargeTest
testWifiUpload()152     public void testWifiUpload() throws Exception {
153         mConnectionUtil.wifiTestInit();
154         assertTrue(setDeviceWifiAndAirplaneMode(mSsid));
155         uploadFile();
156     }
157 
158     /**
159      *  Ensure that uploading on wifi reports reasonable stats.
160      */
161     @LargeTest
testMobileUpload()162     public void testMobileUpload() throws Exception {
163         assertTrue(hasMobileData());
164         uploadFile();
165     }
166 
167     /**
168      * Helper method that downloads a test file to upload. The stats reported to instrumentation out
169      * only include upload stats.
170      */
uploadFile()171     protected void uploadFile() throws Exception {
172         // Download a file from the server.
173         String ts = Long.toString(System.currentTimeMillis());
174         String targetUrl = BandwidthTestUtil.buildDownloadUrl(
175                 mTestServer, FILE_SIZE, mDeviceId, ts);
176         File tmpSaveFile = new File(BASE_DIR + File.separator + TMP_FILENAME);
177         assertTrue(BandwidthTestUtil.DownloadFromUrl(targetUrl, tmpSaveFile));
178 
179         ts = Long.toString(System.currentTimeMillis());
180         NetworkStats pre_test_stats = fetchDataFromProc(mUid);
181         TrafficStats.startDataProfiling(mContext);
182         assertTrue(BandwidthTestUtil.postFileToServer(mTestServer, mDeviceId, ts, tmpSaveFile));
183         NetworkStats prof_stats = TrafficStats.stopDataProfiling(mContext);
184         Log.d(LOG_TAG, prof_stats.toString());
185         NetworkStats post_test_stats = fetchDataFromProc(mUid);
186         NetworkStats proc_stats = post_test_stats.subtract(pre_test_stats);
187 
188         // Output measurements to instrumentation out, so that it can be compared to that of
189         // the server.
190         Bundle results = new Bundle();
191         results.putString("device_id", mDeviceId);
192         results.putString("timestamp", ts);
193         results.putInt("size", FILE_SIZE);
194         addStatsToResults(PROF_LABEL, prof_stats, results, mUid);
195         addStatsToResults(PROC_LABEL, proc_stats, results, mUid);
196         getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, results);
197 
198         // Clean up.
199         assertTrue(cleanUpFile(tmpSaveFile));
200     }
201 
202     /**
203      * We want to make sure that if we use wifi and the  Download Manager to download stuff,
204      * accounting still goes to the app making the call and that the numbers still make sense.
205      */
206     @LargeTest
testWifiDownloadWithDownloadManager()207     public void testWifiDownloadWithDownloadManager() throws Exception {
208         mConnectionUtil.wifiTestInit();
209         assertTrue(setDeviceWifiAndAirplaneMode(mSsid));
210         downloadFileUsingDownloadManager();
211     }
212 
213     /**
214      * We want to make sure that if we use mobile data and the Download Manager to download stuff,
215      * accounting still goes to the app making the call and that the numbers still make sense.
216      */
217     @LargeTest
testMobileDownloadWithDownloadManager()218     public void testMobileDownloadWithDownloadManager() throws Exception {
219         assertTrue(hasMobileData());
220         downloadFileUsingDownloadManager();
221     }
222 
223     /**
224      * Helper method that downloads a file from a test server using the download manager and reports
225      * the stats to instrumentation out.
226      */
downloadFileUsingDownloadManager()227     protected void downloadFileUsingDownloadManager() throws Exception {
228         // If we are using the download manager, then the data that is written to /proc/uid_stat/
229         // is accounted against download manager's uid, since it uses pre-ICS API.
230         int downloadManagerUid = mConnectionUtil.downloadManagerUid();
231         assertTrue(downloadManagerUid >= 0);
232         NetworkStats pre_test_stats = fetchDataFromProc(downloadManagerUid);
233         // start profiling
234         TrafficStats.startDataProfiling(mContext);
235         String ts = Long.toString(System.currentTimeMillis());
236         String targetUrl = BandwidthTestUtil.buildDownloadUrl(
237                 mTestServer, FILE_SIZE, mDeviceId, ts);
238         Log.v(LOG_TAG, "Download url: " + targetUrl);
239         File tmpSaveFile = new File(BASE_DIR + File.separator + TMP_FILENAME);
240         assertTrue(mConnectionUtil.startDownloadAndWait(targetUrl, 500000));
241         NetworkStats prof_stats = TrafficStats.stopDataProfiling(mContext);
242         NetworkStats post_test_stats = fetchDataFromProc(downloadManagerUid);
243         NetworkStats proc_stats = post_test_stats.subtract(pre_test_stats);
244         Log.d(LOG_TAG, prof_stats.toString());
245         // Output measurements to instrumentation out, so that it can be compared to that of
246         // the server.
247         Bundle results = new Bundle();
248         results.putString("device_id", mDeviceId);
249         results.putString("timestamp", ts);
250         results.putInt("size", FILE_SIZE);
251         addStatsToResults(PROF_LABEL, prof_stats, results, mUid);
252         // remember to use download manager uid for proc stats
253         addStatsToResults(PROC_LABEL, proc_stats, results, downloadManagerUid);
254         getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, results);
255 
256         // Clean up.
257         assertTrue(cleanUpFile(tmpSaveFile));
258     }
259 
260     /**
261      * Fetch network data from /proc/uid_stat/uid
262      *
263      * @return populated {@link NetworkStats}
264      */
fetchDataFromProc(int uid)265     public NetworkStats fetchDataFromProc(int uid) {
266         String root_filepath = "/proc/uid_stat/" + uid + "/";
267         File rcv_stat = new File (root_filepath + "tcp_rcv");
268         int rx = BandwidthTestUtil.parseIntValueFromFile(rcv_stat);
269         File snd_stat = new File (root_filepath + "tcp_snd");
270         int tx = BandwidthTestUtil.parseIntValueFromFile(snd_stat);
271         NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
272         stats.insertEntry(NetworkStats.IFACE_ALL, uid, NetworkStats.SET_DEFAULT,
273                 NetworkStats.TAG_NONE, rx, 0, tx, 0, 0);
274         return stats;
275     }
276 
277     /**
278      * Turn on Airplane mode and connect to the wifi.
279      *
280      * @param ssid of the wifi to connect to
281      * @return true if we successfully connected to a given network.
282      */
setDeviceWifiAndAirplaneMode(String ssid)283     public boolean setDeviceWifiAndAirplaneMode(String ssid) {
284         mConnectionUtil.setAirplaneMode(mContext, true);
285         assertTrue(mConnectionUtil.connectToWifi(ssid));
286         assertTrue(mConnectionUtil.waitForWifiState(WifiManager.WIFI_STATE_ENABLED,
287                 ConnectionUtil.LONG_TIMEOUT));
288         assertTrue(mConnectionUtil.waitForNetworkState(ConnectivityManager.TYPE_WIFI,
289                 State.CONNECTED, ConnectionUtil.LONG_TIMEOUT));
290         return mConnectionUtil.hasData();
291     }
292 
293     /**
294      * Helper method to make sure we are connected to mobile data.
295      *
296      * @return true if we successfully connect to mobile data.
297      */
hasMobileData()298     public boolean hasMobileData() {
299         assertTrue(mConnectionUtil.waitForNetworkState(ConnectivityManager.TYPE_MOBILE,
300                 State.CONNECTED, ConnectionUtil.LONG_TIMEOUT));
301         assertTrue("Not connected to mobile", mConnectionUtil.isConnectedToMobile());
302         assertFalse("Still connected to wifi.", mConnectionUtil.isConnectedToWifi());
303         return mConnectionUtil.hasData();
304     }
305 
306     /**
307      * Output the {@link NetworkStats} to Instrumentation out.
308      *
309      * @param label to attach to this given stats.
310      * @param stats {@link NetworkStats} to add.
311      * @param results {@link Bundle} to be added to.
312      * @param uid for which to report the results.
313      */
addStatsToResults(String label, NetworkStats stats, Bundle results, int uid)314     public void addStatsToResults(String label, NetworkStats stats, Bundle results, int uid){
315         if (results == null || results.isEmpty()) {
316             Log.e(LOG_TAG, "Empty bundle provided.");
317             return;
318         }
319         Entry totalStats = null;
320         for (int i = 0; i < stats.size(); ++i) {
321             Entry statsEntry = stats.getValues(i, null);
322             // We are only interested in the all inclusive stats.
323             if (statsEntry.tag != 0) {
324                 continue;
325             }
326             // skip stats for other uids
327             if (statsEntry.uid != uid) {
328                 continue;
329             }
330             if (totalStats == null || statsEntry.set == NetworkStats.SET_ALL) {
331                 totalStats = statsEntry;
332             } else {
333                 totalStats.rxBytes += statsEntry.rxBytes;
334                 totalStats.txBytes += statsEntry.txBytes;
335             }
336         }
337         // Output merged stats to bundle.
338         results.putInt(label + "uid", totalStats.uid);
339         results.putLong(label + "tx", totalStats.txBytes);
340         results.putLong(label + "rx", totalStats.rxBytes);
341     }
342 
343     /**
344      * Remove file if it exists.
345      * @param file {@link File} to delete.
346      * @return true if successfully deleted the file.
347      */
cleanUpFile(File file)348     private boolean cleanUpFile(File file) {
349         if (file.exists()) {
350             return file.delete();
351         }
352         return true;
353     }
354 }
355