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
17package com.android.bandwidthtest;
18
19import android.content.Context;
20import android.net.ConnectivityManager;
21import android.net.NetworkInfo.State;
22import android.net.NetworkStats;
23import android.net.NetworkStats.Entry;
24import android.net.TrafficStats;
25import android.net.wifi.WifiManager;
26import android.os.Bundle;
27import android.os.Environment;
28import android.os.Process;
29import android.os.SystemClock;
30import android.telephony.TelephonyManager;
31import android.test.InstrumentationTestCase;
32import android.test.suitebuilder.annotation.LargeTest;
33import android.util.Log;
34
35import com.android.bandwidthtest.util.BandwidthTestUtil;
36import com.android.bandwidthtest.util.ConnectionUtil;
37
38import java.io.File;
39
40/**
41 * Test that downloads files from a test server and reports the bandwidth metrics collected.
42 */
43public 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
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
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
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
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     */
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
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
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     */
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
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
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     */
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     */
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     */
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     */
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     */
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     */
340    private boolean cleanUpFile(File file) {
341        if (file.exists()) {
342            return file.delete();
343        }
344        return true;
345    }
346}
347