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