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