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