/*
* Copyright (C) 2011, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.bandwidthtest;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo.State;
import android.net.NetworkStats;
import android.net.NetworkStats.Entry;
import android.net.TrafficStats;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.Process;
import android.os.SystemClock;
import android.telephony.TelephonyManager;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
import com.android.bandwidthtest.util.BandwidthTestUtil;
import com.android.bandwidthtest.util.ConnectionUtil;
import java.io.File;
/**
* Test that downloads files from a test server and reports the bandwidth metrics collected.
*/
public class BandwidthTest extends InstrumentationTestCase {
private static final String LOG_TAG = "BandwidthTest";
private final static String PROF_LABEL = "PROF_";
private final static String PROC_LABEL = "PROC_";
private final static int INSTRUMENTATION_IN_PROGRESS = 2;
private final static String BASE_DIR =
Environment.getExternalStorageDirectory().getAbsolutePath();
private final static String TMP_FILENAME = "tmp.dat";
// Download 10.486 * 106 bytes (+ headers) from app engine test server.
private final int FILE_SIZE = 10485613;
private Context mContext;
private ConnectionUtil mConnectionUtil;
private TelephonyManager mTManager;
private int mUid;
private String mSsid;
private String mTestServer;
private String mDeviceId;
private BandwidthTestRunner mRunner;
@Override
protected void setUp() throws Exception {
super.setUp();
mRunner = (BandwidthTestRunner) getInstrumentation();
mSsid = mRunner.mSsid;
mTestServer = mRunner.mTestServer;
mContext = mRunner.getTargetContext();
mConnectionUtil = new ConnectionUtil(mContext);
mConnectionUtil.initialize();
Log.v(LOG_TAG, "Initialized mConnectionUtil");
mUid = Process.myUid();
mTManager = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
mDeviceId = mTManager.getDeviceId();
}
@Override
protected void tearDown() throws Exception {
mConnectionUtil.cleanUp();
super.tearDown();
}
/**
* Ensure that downloading on wifi reports reasonable stats.
*/
@LargeTest
public void testWifiDownload() throws Exception {
mConnectionUtil.wifiTestInit();
assertTrue("Could not connect to wifi!", setDeviceWifiAndAirplaneMode(mSsid));
downloadFile();
}
/**
* Ensure that downloading on mobile reports reasonable stats.
*/
@LargeTest
public void testMobileDownload() throws Exception {
// As part of the setup we disconnected from wifi; make sure we are connected to mobile and
// that we have data.
assertTrue("Do not have mobile data!", hasMobileData());
downloadFile();
}
/**
* Helper method that downloads a file using http connection from a test server and reports the
* data usage stats to instrumentation out.
*/
protected void downloadFile() throws Exception {
NetworkStats pre_test_stats = fetchDataFromProc(mUid);
String ts = Long.toString(System.currentTimeMillis());
String targetUrl = BandwidthTestUtil.buildDownloadUrl(
mTestServer, FILE_SIZE, mDeviceId, ts);
TrafficStats.startDataProfiling(mContext);
File tmpSaveFile = new File(BASE_DIR + File.separator + TMP_FILENAME);
assertTrue(BandwidthTestUtil.DownloadFromUrl(targetUrl, tmpSaveFile));
NetworkStats prof_stats = TrafficStats.stopDataProfiling(mContext);
Log.d(LOG_TAG, prof_stats.toString());
NetworkStats post_test_stats = fetchDataFromProc(mUid);
NetworkStats proc_stats = post_test_stats.subtract(pre_test_stats);
// Output measurements to instrumentation out, so that it can be compared to that of
// the server.
Bundle results = new Bundle();
results.putString("device_id", mDeviceId);
results.putString("timestamp", ts);
results.putInt("size", FILE_SIZE);
addStatsToResults(PROF_LABEL, prof_stats, results, mUid);
addStatsToResults(PROC_LABEL, proc_stats, results, mUid);
getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, results);
// Clean up.
assertTrue(cleanUpFile(tmpSaveFile));
}
/**
* Ensure that uploading on wifi reports reasonable stats.
*/
@LargeTest
public void testWifiUpload() throws Exception {
mConnectionUtil.wifiTestInit();
assertTrue(setDeviceWifiAndAirplaneMode(mSsid));
uploadFile();
}
/**
* Ensure that uploading on wifi reports reasonable stats.
*/
@LargeTest
public void testMobileUpload() throws Exception {
assertTrue(hasMobileData());
uploadFile();
}
/**
* Helper method that downloads a test file to upload. The stats reported to instrumentation out
* only include upload stats.
*/
protected void uploadFile() throws Exception {
// Download a file from the server.
String ts = Long.toString(System.currentTimeMillis());
String targetUrl = BandwidthTestUtil.buildDownloadUrl(
mTestServer, FILE_SIZE, mDeviceId, ts);
File tmpSaveFile = new File(BASE_DIR + File.separator + TMP_FILENAME);
assertTrue(BandwidthTestUtil.DownloadFromUrl(targetUrl, tmpSaveFile));
ts = Long.toString(System.currentTimeMillis());
NetworkStats pre_test_stats = fetchDataFromProc(mUid);
TrafficStats.startDataProfiling(mContext);
assertTrue(BandwidthTestUtil.postFileToServer(mTestServer, mDeviceId, ts, tmpSaveFile));
NetworkStats prof_stats = TrafficStats.stopDataProfiling(mContext);
Log.d(LOG_TAG, prof_stats.toString());
NetworkStats post_test_stats = fetchDataFromProc(mUid);
NetworkStats proc_stats = post_test_stats.subtract(pre_test_stats);
// Output measurements to instrumentation out, so that it can be compared to that of
// the server.
Bundle results = new Bundle();
results.putString("device_id", mDeviceId);
results.putString("timestamp", ts);
results.putInt("size", FILE_SIZE);
addStatsToResults(PROF_LABEL, prof_stats, results, mUid);
addStatsToResults(PROC_LABEL, proc_stats, results, mUid);
getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, results);
// Clean up.
assertTrue(cleanUpFile(tmpSaveFile));
}
/**
* We want to make sure that if we use wifi and the Download Manager to download stuff,
* accounting still goes to the app making the call and that the numbers still make sense.
*/
@LargeTest
public void testWifiDownloadWithDownloadManager() throws Exception {
mConnectionUtil.wifiTestInit();
assertTrue(setDeviceWifiAndAirplaneMode(mSsid));
downloadFileUsingDownloadManager();
}
/**
* We want to make sure that if we use mobile data and the Download Manager to download stuff,
* accounting still goes to the app making the call and that the numbers still make sense.
*/
@LargeTest
public void testMobileDownloadWithDownloadManager() throws Exception {
assertTrue(hasMobileData());
downloadFileUsingDownloadManager();
}
/**
* Helper method that downloads a file from a test server using the download manager and reports
* the stats to instrumentation out.
*/
protected void downloadFileUsingDownloadManager() throws Exception {
// If we are using the download manager, then the data that is written to /proc/uid_stat/
// is accounted against download manager's uid, since it uses pre-ICS API.
int downloadManagerUid = mConnectionUtil.downloadManagerUid();
assertTrue(downloadManagerUid >= 0);
NetworkStats pre_test_stats = fetchDataFromProc(downloadManagerUid);
// start profiling
TrafficStats.startDataProfiling(mContext);
String ts = Long.toString(System.currentTimeMillis());
String targetUrl = BandwidthTestUtil.buildDownloadUrl(
mTestServer, FILE_SIZE, mDeviceId, ts);
Log.v(LOG_TAG, "Download url: " + targetUrl);
File tmpSaveFile = new File(BASE_DIR + File.separator + TMP_FILENAME);
assertTrue(mConnectionUtil.startDownloadAndWait(targetUrl, 500000));
NetworkStats prof_stats = TrafficStats.stopDataProfiling(mContext);
NetworkStats post_test_stats = fetchDataFromProc(downloadManagerUid);
NetworkStats proc_stats = post_test_stats.subtract(pre_test_stats);
Log.d(LOG_TAG, prof_stats.toString());
// Output measurements to instrumentation out, so that it can be compared to that of
// the server.
Bundle results = new Bundle();
results.putString("device_id", mDeviceId);
results.putString("timestamp", ts);
results.putInt("size", FILE_SIZE);
addStatsToResults(PROF_LABEL, prof_stats, results, mUid);
// remember to use download manager uid for proc stats
addStatsToResults(PROC_LABEL, proc_stats, results, downloadManagerUid);
getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, results);
// Clean up.
assertTrue(cleanUpFile(tmpSaveFile));
}
/**
* Fetch network data from /proc/uid_stat/uid
*
* @return populated {@link NetworkStats}
*/
public NetworkStats fetchDataFromProc(int uid) {
String root_filepath = "/proc/uid_stat/" + uid + "/";
File rcv_stat = new File (root_filepath + "tcp_rcv");
int rx = BandwidthTestUtil.parseIntValueFromFile(rcv_stat);
File snd_stat = new File (root_filepath + "tcp_snd");
int tx = BandwidthTestUtil.parseIntValueFromFile(snd_stat);
NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
stats.addValues(NetworkStats.IFACE_ALL, uid, NetworkStats.SET_DEFAULT,
NetworkStats.TAG_NONE, rx, 0, tx, 0, 0);
return stats;
}
/**
* Turn on Airplane mode and connect to the wifi.
*
* @param ssid of the wifi to connect to
* @return true if we successfully connected to a given network.
*/
public boolean setDeviceWifiAndAirplaneMode(String ssid) {
mConnectionUtil.setAirplaneMode(mContext, true);
assertTrue(mConnectionUtil.connectToWifi(ssid));
assertTrue(mConnectionUtil.waitForWifiState(WifiManager.WIFI_STATE_ENABLED,
ConnectionUtil.LONG_TIMEOUT));
assertTrue(mConnectionUtil.waitForNetworkState(ConnectivityManager.TYPE_WIFI,
State.CONNECTED, ConnectionUtil.LONG_TIMEOUT));
return mConnectionUtil.hasData();
}
/**
* Helper method to make sure we are connected to mobile data.
*
* @return true if we successfully connect to mobile data.
*/
public boolean hasMobileData() {
assertTrue(mConnectionUtil.waitForNetworkState(ConnectivityManager.TYPE_MOBILE,
State.CONNECTED, ConnectionUtil.LONG_TIMEOUT));
assertTrue("Not connected to mobile", mConnectionUtil.isConnectedToMobile());
assertFalse("Still connected to wifi.", mConnectionUtil.isConnectedToWifi());
return mConnectionUtil.hasData();
}
/**
* Output the {@link NetworkStats} to Instrumentation out.
*
* @param label to attach to this given stats.
* @param stats {@link NetworkStats} to add.
* @param results {@link Bundle} to be added to.
* @param uid for which to report the results.
*/
public void addStatsToResults(String label, NetworkStats stats, Bundle results, int uid){
if (results == null || results.isEmpty()) {
Log.e(LOG_TAG, "Empty bundle provided.");
return;
}
Entry totalStats = null;
for (int i = 0; i < stats.size(); ++i) {
Entry statsEntry = stats.getValues(i, null);
// We are only interested in the all inclusive stats.
if (statsEntry.tag != 0) {
continue;
}
// skip stats for other uids
if (statsEntry.uid != uid) {
continue;
}
if (totalStats == null || statsEntry.set == NetworkStats.SET_ALL) {
totalStats = statsEntry;
} else {
totalStats.rxBytes += statsEntry.rxBytes;
totalStats.txBytes += statsEntry.txBytes;
}
}
// Output merged stats to bundle.
results.putInt(label + "uid", totalStats.uid);
results.putLong(label + "tx", totalStats.txBytes);
results.putLong(label + "rx", totalStats.rxBytes);
}
/**
* Remove file if it exists.
* @param file {@link File} to delete.
* @return true if successfully deleted the file.
*/
private boolean cleanUpFile(File file) {
if (file.exists()) {
return file.delete();
}
return true;
}
}