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