Merge "Bandwidth microbenchmark test app for ICS."

This commit is contained in:
Tsu Chiang Chuang
2011-08-04 18:34:53 -07:00
committed by Android (Google) Code Review
7 changed files with 1373 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
# 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.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# We only want this apk build for tests.
LOCAL_MODULE_TAGS := tests
# Include all test java files.
LOCAL_SRC_FILES := \
$(call all-java-files-under, src)
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_PACKAGE_NAME := BandwidthTests
include $(BUILD_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.bandwidth.tests" >
<application >
<uses-library android:name="android.test.runner" />
</application>
<instrumentation
android:name="com.android.bandwidthtest.BandwidthTestRunner"
android:targetPackage="com.android.bandwidth.tests"
android:label="Bandwidth Tests" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.DEVICE_POWER" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
</manifest>

View File

@@ -0,0 +1,217 @@
/*
* 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.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() {
assertTrue(setDeviceWifiAndAirplaneMode(mSsid));
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);
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);
AddStatsToResults(PROC_LABEL, proc_stats, results);
getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, results);
// Clean up.
assertTrue(cleanUpFile(tmpSaveFile));
}
/**
* We want to make sure that if we use 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() {
assertTrue(setDeviceWifiAndAirplaneMode(mSsid));
// 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);
// 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);
AddStatsToResults(PROC_LABEL, proc_stats, results);
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.TAG_NONE, rx, 0, tx, 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));
return mConnectionUtil.waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.CONNECTED,
ConnectionUtil.LONG_TIMEOUT);
}
/**
* 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.
*/
public void AddStatsToResults(String label, NetworkStats stats, Bundle results){
if (results == null || results.isEmpty()) {
Log.e(LOG_TAG, "Empty bundle provided.");
return;
}
for (int i = 0; i < stats.size(); ++i) {
android.net.NetworkStats.Entry entry = stats.getValues(i, null);
results.putInt(label + "uid", entry.uid);
results.putString(label + "iface", entry.iface);
results.putInt(label + "tag", entry.tag);
results.putLong(label + "tx", entry.txBytes);
results.putLong(label + "rx", entry.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;
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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.os.Bundle;
import android.test.InstrumentationTestRunner;
public class BandwidthTestRunner extends InstrumentationTestRunner {
public String mSsid = "wifi-ssid";
public String mTestServer = "http://www.sometestserver.com";
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
String ssidStr= (String) icicle.get("ssid");
if (ssidStr != null) {
mSsid = ssidStr;
}
String serverStr = (String) icicle.get("server");
if (serverStr != null) {
mTestServer = serverStr;
}
}
}

View File

@@ -0,0 +1,257 @@
/*
* 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.net.NetworkInfo.State;
import android.util.Log;
import java.util.List;
import java.util.ArrayList;
/**
* Data structure to keep track of the network state transitions.
*/
public class NetworkState {
/**
* Desired direction of state transition.
*/
public enum StateTransitionDirection {
TO_DISCONNECTION, TO_CONNECTION, DO_NOTHING
}
private final String LOG_TAG = "NetworkState";
private List<State> mStateDepository;
private State mTransitionTarget;
private StateTransitionDirection mTransitionDirection;
private String mReason = null; // record mReason of state transition failure
public NetworkState() {
mStateDepository = new ArrayList<State>();
mTransitionDirection = StateTransitionDirection.DO_NOTHING;
mTransitionTarget = State.UNKNOWN;
}
public NetworkState(State currentState) {
mStateDepository = new ArrayList<State>();
mStateDepository.add(currentState);
mTransitionDirection = StateTransitionDirection.DO_NOTHING;
mTransitionTarget = State.UNKNOWN;
}
/**
* Reinitialize the network state
*/
public void resetNetworkState() {
mStateDepository.clear();
mTransitionDirection = StateTransitionDirection.DO_NOTHING;
mTransitionTarget = State.UNKNOWN;
}
/**
* Set the transition criteria
* @param initState initial {@link State}
* @param transitionDir explicit {@link StateTransitionDirection}
* @param targetState desired {@link State}
*/
public void setStateTransitionCriteria(State initState, StateTransitionDirection transitionDir,
State targetState) {
if (!mStateDepository.isEmpty()) {
mStateDepository.clear();
}
mStateDepository.add(initState);
mTransitionDirection = transitionDir;
mTransitionTarget = targetState;
Log.v(LOG_TAG, "setStateTransitionCriteria: " + printStates());
}
/**
* Record the current state of the network
* @param currentState the current {@link State}
*/
public void recordState(State currentState) {
mStateDepository.add(currentState);
}
/**
* Verify the state transition
* @return true if the requested transition completed successfully.
*/
public boolean validateStateTransition() {
Log.v(LOG_TAG, String.format("Print state depository: %s", printStates()));
switch (mTransitionDirection) {
case DO_NOTHING:
Log.v(LOG_TAG, "No direction requested, verifying network states");
return validateNetworkStates();
case TO_CONNECTION:
Log.v(LOG_TAG, "Transition to CONNECTED");
return validateNetworkConnection();
case TO_DISCONNECTION:
Log.v(LOG_TAG, "Transition to DISCONNECTED");
return validateNetworkDisconnection();
default:
Log.e(LOG_TAG, "Invalid transition direction.");
return false;
}
}
/**
* Verify that network states are valid
* @return false if any of the states are invalid
*/
private boolean validateNetworkStates() {
if (mStateDepository.isEmpty()) {
Log.v(LOG_TAG, "no state is recorded");
mReason = "no state is recorded.";
return false;
} else if (mStateDepository.size() > 1) {
Log.v(LOG_TAG, "no broadcast is expected, instead broadcast is probably received");
mReason = "no broadcast is expected, instead broadcast is probably received";
return false;
} else if (mStateDepository.get(0) != mTransitionTarget) {
Log.v(LOG_TAG, String.format("%s is expected, but it is %s",
mTransitionTarget.toString(),
mStateDepository.get(0).toString()));
mReason = String.format("%s is expected, but it is %s",
mTransitionTarget.toString(),
mStateDepository.get(0).toString());
return false;
}
return true;
}
/**
* Verify the network state to disconnection
* @return false if any of the state transitions were not valid
*/
private boolean validateNetworkDisconnection() {
// Transition from CONNECTED -> DISCONNECTED: CONNECTED->DISCONNECTING->DISCONNECTED
StringBuffer str = new StringBuffer ("States: ");
str.append(printStates());
if (mStateDepository.get(0) != State.CONNECTED) {
str.append(String.format(" Initial state should be CONNECTED, but it is %s.",
mStateDepository.get(0)));
mReason = str.toString();
return false;
}
State lastState = mStateDepository.get(mStateDepository.size() - 1);
if ( lastState != mTransitionTarget) {
str.append(String.format(" Last state should be DISCONNECTED, but it is %s",
lastState));
mReason = str.toString();
return false;
}
for (int i = 1; i < mStateDepository.size() - 1; i++) {
State preState = mStateDepository.get(i-1);
State curState = mStateDepository.get(i);
if ((preState == State.CONNECTED) && ((curState == State.DISCONNECTING) ||
(curState == State.DISCONNECTED))) {
continue;
} else if ((preState == State.DISCONNECTING) && (curState == State.DISCONNECTED)) {
continue;
} else if ((preState == State.DISCONNECTED) && (curState == State.DISCONNECTED)) {
continue;
} else {
str.append(String.format(" Transition state from %s to %s is not valid",
preState.toString(), curState.toString()));
mReason = str.toString();
return false;
}
}
mReason = str.toString();
return true;
}
/**
* Verify the network state to connection
* @return false if any of the state transitions were not valid
*/
private boolean validateNetworkConnection() {
StringBuffer str = new StringBuffer("States ");
str.append(printStates());
if (mStateDepository.get(0) != State.DISCONNECTED) {
str.append(String.format(" Initial state should be DISCONNECTED, but it is %s.",
mStateDepository.get(0)));
mReason = str.toString();
return false;
}
State lastState = mStateDepository.get(mStateDepository.size() - 1);
if ( lastState != mTransitionTarget) {
str.append(String.format(" Last state should be %s, but it is %s", mTransitionTarget,
lastState));
mReason = str.toString();
return false;
}
for (int i = 1; i < mStateDepository.size(); i++) {
State preState = mStateDepository.get(i-1);
State curState = mStateDepository.get(i);
if ((preState == State.DISCONNECTED) && ((curState == State.CONNECTING) ||
(curState == State.CONNECTED) || (curState == State.DISCONNECTED))) {
continue;
} else if ((preState == State.CONNECTING) && (curState == State.CONNECTED)) {
continue;
} else if ((preState == State.CONNECTED) && (curState == State.CONNECTED)) {
continue;
} else {
str.append(String.format(" Transition state from %s to %s is not valid.",
preState.toString(), curState.toString()));
mReason = str.toString();
return false;
}
}
mReason = str.toString();
return true;
}
/**
* Fetch the different network state transitions
* @return {@link List} of {@link State}
*/
public List<State> getTransitionStates() {
return mStateDepository;
}
/**
* Fetch the reason for network state transition failure
* @return the {@link String} for the failure
*/
public String getFailureReason() {
return mReason;
}
/**
* Print the network state
* @return {@link String} representation of the network state
*/
public String printStates() {
StringBuilder stateBuilder = new StringBuilder();
for (int i = 0; i < mStateDepository.size(); i++) {
stateBuilder.append(" ").append(mStateDepository.get(i).toString()).append("->");
}
return stateBuilder.toString();
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("mTransitionDirection: ").append(mTransitionDirection.toString()).
append("; ").append("states:").
append(printStates()).append("; ");
return builder.toString();
}
}

View File

@@ -0,0 +1,109 @@
/*
* 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.util;
import android.util.Log;
import org.apache.http.util.ByteArrayBuffer;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
public class BandwidthTestUtil {
private static final String LOG_TAG = "BandwidthTestUtil";
/**
* Parses the first line in a file if exists.
*
* @param file {@link File} the input
* @return the integer value of the first line of the file.
*/
public static int parseIntValueFromFile(File file) {
int value = 0;
if (file.exists()) {
try {
FileInputStream fstream = new FileInputStream(file);
DataInputStream in = new DataInputStream(fstream);
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String strLine = br.readLine();
if (strLine != null) {
value = Integer.parseInt(strLine);
}
// Close the input stream
in.close();
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
}
}
return value;
}
/**
* Creates the Download string for the test server.
*
* @param server url of the test server
* @param size in bytes of the file to download
* @param deviceId the device id that is downloading
* @param timestamp
* @return download url
*/
public static String buildDownloadUrl(String server, int size, String deviceId,
String timestamp) {
String downloadUrl = server + "/download?size=" + size + "&device_id=" + deviceId +
"&timestamp=" + timestamp;
return downloadUrl;
}
/**
* Download a given file from a target url to a given destination file.
* @param targetUrl the url to download
* @param file the {@link File} location where to save to
* @return true if it succeeded.
*/
public static boolean DownloadFromUrl(String targetUrl, File file) {
try {
URL url = new URL(targetUrl);
Log.d(LOG_TAG, "Download begining");
Log.d(LOG_TAG, "Download url:" + url);
Log.d(LOG_TAG, "Downloaded file name:" + file.getAbsolutePath());
URLConnection ucon = url.openConnection();
InputStream is = ucon.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
ByteArrayBuffer baf = new ByteArrayBuffer(50);
int current = 0;
while ((current = bis.read()) != -1) {
baf.append((byte) current);
}
FileOutputStream fos = new FileOutputStream(file);
fos.write(baf.toByteArray());
fos.close();
} catch (IOException e) {
Log.d(LOG_TAG, "Failed to download file with error: " + e);
return false;
}
return true;
}
}

View File

@@ -0,0 +1,684 @@
/*
* 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.util;
import android.app.DownloadManager;
import android.app.DownloadManager.Query;
import android.app.DownloadManager.Request;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.NetworkInfo.State;
import android.net.Uri;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.os.Message;
import android.provider.Settings;
import android.util.Log;
import com.android.bandwidthtest.NetworkState;
import com.android.bandwidthtest.NetworkState.StateTransitionDirection;
import com.android.internal.util.AsyncChannel;
import java.util.List;
/*
* Utility class used to set the connectivity of the device and to download files.
*/
public class ConnectionUtil {
private static final String LOG_TAG = "ConnectionUtil";
private static final String DOWNLOAD_MANAGER_PKG_NAME = "com.android.providers.downloads";
private static final int WAIT_FOR_SCAN_RESULT = 10 * 1000; // 10 seconds
private static final int WIFI_SCAN_TIMEOUT = 50 * 1000;
public static final int SHORT_TIMEOUT = 5 * 1000;
public static final int LONG_TIMEOUT = 10 * 1000;
private ConnectivityReceiver mConnectivityReceiver = null;
private WifiReceiver mWifiReceiver = null;
private DownloadReceiver mDownloadReceiver = null;
private DownloadManager mDownloadManager;
private NetworkInfo mNetworkInfo;
private NetworkInfo mOtherNetworkInfo;
private boolean mScanResultIsAvailable = false;
private ConnectivityManager mCM;
private Object mWifiMonitor = new Object();
private Object mConnectivityMonitor = new Object();
private Object mDownloadMonitor = new Object();
private int mWifiState;
private NetworkInfo mWifiNetworkInfo;
private WifiManager mWifiManager;
private Context mContext;
// Verify connectivity state
private static final int NUM_NETWORK_TYPES = ConnectivityManager.MAX_NETWORK_TYPE + 1;
private NetworkState[] mConnectivityState = new NetworkState[NUM_NETWORK_TYPES];
public ConnectionUtil(Context context) {
mContext = context;
}
/**
* Initialize the class. Needs to be called before any other methods in {@link ConnectionUtil}
*
* @throws Exception
*/
public void initialize() throws Exception {
// Register a connectivity receiver for CONNECTIVITY_ACTION
mConnectivityReceiver = new ConnectivityReceiver();
mContext.registerReceiver(mConnectivityReceiver,
new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
// Register a download receiver for ACTION_DOWNLOAD_COMPLETE
mDownloadReceiver = new DownloadReceiver();
mContext.registerReceiver(mDownloadReceiver,
new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
// Register a wifi receiver
mWifiReceiver = new WifiReceiver();
IntentFilter mIntentFilter = new IntentFilter();
mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
mIntentFilter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
mIntentFilter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
mContext.registerReceiver(mWifiReceiver, mIntentFilter);
// Get an instance of ConnectivityManager
mCM = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
// Get an instance of WifiManager
mWifiManager =(WifiManager)mContext.getSystemService(Context.WIFI_SERVICE);
mWifiManager.asyncConnect(mContext, new WifiServiceHandler());
mDownloadManager = (DownloadManager)mContext.getSystemService(Context.DOWNLOAD_SERVICE);
initializeNetworkStates();
mWifiManager.setWifiEnabled(true);
Log.v(LOG_TAG, "Clear Wifi before we start the test.");
sleep(SHORT_TIMEOUT);
removeConfiguredNetworksAndDisableWifi();
}
/**
* A wrapper of a broadcast receiver which provides network connectivity information
* for all kinds of network: wifi, mobile, etc.
*/
private class ConnectivityReceiver extends BroadcastReceiver {
/**
* {@inheritDoc}
*/
@Override
public void onReceive(Context context, Intent intent) {
if (isInitialStickyBroadcast()) {
Log.d(LOG_TAG, "This is a sticky broadcast don't do anything.");
return;
}
Log.v(LOG_TAG, "ConnectivityReceiver: onReceive() is called with " + intent);
String action = intent.getAction();
if (!action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
Log.v("ConnectivityReceiver", "onReceive() called with " + intent);
return;
}
if (intent.hasExtra(ConnectivityManager.EXTRA_NETWORK_INFO)) {
mNetworkInfo = (NetworkInfo)
intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
}
if (intent.hasExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO)) {
mOtherNetworkInfo = (NetworkInfo)
intent.getParcelableExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO);
}
Log.v(LOG_TAG, "mNetworkInfo: " + mNetworkInfo.toString());
recordNetworkState(mNetworkInfo.getType(), mNetworkInfo.getState());
if (mOtherNetworkInfo != null) {
Log.v(LOG_TAG, "mOtherNetworkInfo: " + mOtherNetworkInfo.toString());
recordNetworkState(mOtherNetworkInfo.getType(), mOtherNetworkInfo.getState());
}
notifyNetworkConnectivityChange();
}
}
/**
* A wrapper of a broadcast receiver which provides wifi information.
*/
private class WifiReceiver extends BroadcastReceiver {
/**
* {@inheritDoc}
*/
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.v("WifiReceiver", "onReceive() is calleld with " + intent);
if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
Log.v(LOG_TAG, "Scan results are available");
notifyScanResult();
} else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
mWifiNetworkInfo =
(NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
Log.v(LOG_TAG, "mWifiNetworkInfo: " + mWifiNetworkInfo.toString());
if (mWifiNetworkInfo.getState() == State.CONNECTED) {
intent.getStringExtra(WifiManager.EXTRA_BSSID);
}
notifyWifiState();
} else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
mWifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
WifiManager.WIFI_STATE_UNKNOWN);
notifyWifiState();
} else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) {
notifyWifiAPState();
} else {
return;
}
}
}
/**
* A wrapper of a broadcast receiver which provides download manager information.
*/
private class DownloadReceiver extends BroadcastReceiver {
/**
* {@inheritDoc}
*/
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.v("DownloadReceiver", "onReceive() is called with " + intent);
// Download complete
if (action.equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
notifiyDownloadState();
}
}
}
private class WifiServiceHandler extends Handler {
/**
* {@inheritDoc}
*/
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
// AsyncChannel in msg.obj
} else {
Log.v(LOG_TAG, "Failed to establish AsyncChannel connection");
}
break;
default:
// Ignore
break;
}
}
}
/**
* Initialize all the network states.
*/
public void initializeNetworkStates() {
// For each network type, initialize network states to UNKNOWN, and no verification
// flag is set.
for (int networkType = NUM_NETWORK_TYPES - 1; networkType >= 0; networkType--) {
mConnectivityState[networkType] = new NetworkState();
Log.v(LOG_TAG, "Initialize network state for " + networkType + ": " +
mConnectivityState[networkType].toString());
}
}
public void recordNetworkState(int networkType, State networkState) {
// deposit a network state
Log.v(LOG_TAG, "record network state for network " + networkType +
", state is " + networkState);
mConnectivityState[networkType].recordState(networkState);
}
/**
* Set the state transition criteria
*
* @param networkType
* @param initState
* @param transitionDir
* @param targetState
*/
public void setStateTransitionCriteria(int networkType, State initState,
StateTransitionDirection transitionDir, State targetState) {
mConnectivityState[networkType].setStateTransitionCriteria(
initState, transitionDir, targetState);
}
/**
* Validate the states recorded.
* @param networkType
* @return
*/
public boolean validateNetworkStates(int networkType) {
Log.v(LOG_TAG, "validate network state for " + networkType + ": ");
return mConnectivityState[networkType].validateStateTransition();
}
/**
* Fetch the failure reason for the transition.
* @param networkType
* @return result from network state validation
*/
public String getTransitionFailureReason(int networkType) {
Log.v(LOG_TAG, "get network state transition failure reason for " + networkType + ": " +
mConnectivityState[networkType].toString());
return mConnectivityState[networkType].getFailureReason();
}
/**
* Send a notification via the mConnectivityMonitor when the network connectivity changes.
*/
private void notifyNetworkConnectivityChange() {
synchronized(mConnectivityMonitor) {
Log.v(LOG_TAG, "notify network connectivity changed");
mConnectivityMonitor.notifyAll();
}
}
/**
* Send a notification when a scan for the wifi network is done.
*/
private void notifyScanResult() {
synchronized (this) {
Log.v(LOG_TAG, "notify that scan results are available");
this.notify();
}
}
/**
* Send a notification via the mWifiMonitor when the wifi state changes.
*/
private void notifyWifiState() {
synchronized (mWifiMonitor) {
Log.v(LOG_TAG, "notify wifi state changed.");
mWifiMonitor.notify();
}
}
/**
* Send a notification via the mDownloadMonitor when a download is complete.
*/
private void notifiyDownloadState() {
synchronized (mDownloadMonitor) {
Log.v(LOG_TAG, "notifiy download manager state changed.");
mDownloadMonitor.notify();
}
}
/**
* Send a notification when the wifi ap state changes.
*/
private void notifyWifiAPState() {
synchronized (this) {
Log.v(LOG_TAG, "notify wifi AP state changed.");
this.notify();
}
}
/**
* Start a download on a given url and wait for completion.
*
* @param targetUrl the target to download.x
* @param timeout to wait for download to finish
* @return true if we successfully downloaded the requestedUrl, false otherwise.
*/
public boolean startDownloadAndWait(String targetUrl, long timeout) {
if (targetUrl.length() == 0 || targetUrl == null) {
Log.v(LOG_TAG, "Empty or Null target url requested to DownloadManager");
return true;
}
Request request = new Request(Uri.parse(targetUrl));
long enqueue = mDownloadManager.enqueue(request);
Log.v(LOG_TAG, "Sending download request of " + targetUrl + " to DownloadManager");
long startTime = System.currentTimeMillis();
while (true) {
if ((System.currentTimeMillis() - startTime) > timeout) {
Log.v(LOG_TAG, "startDownloadAndWait timed out, failed to fetch " + targetUrl +
" within " + timeout);
return downloadSuccessful(enqueue);
}
Log.v(LOG_TAG, "Waiting for the download to finish " + targetUrl);
synchronized (mDownloadMonitor) {
try {
mDownloadMonitor.wait(SHORT_TIMEOUT);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!downloadSuccessful(enqueue)) {
continue;
}
return true;
}
}
}
/**
* Fetch the Download Manager's UID.
* @return the Download Manager's UID
*/
public int downloadManagerUid() {
try {
PackageManager pm = mContext.getPackageManager();
ApplicationInfo appInfo = pm.getApplicationInfo(DOWNLOAD_MANAGER_PKG_NAME,
PackageManager.GET_META_DATA);
return appInfo.uid;
} catch (NameNotFoundException e) {
Log.d(LOG_TAG, "Did not find the package for the download service.");
return -1;
}
}
/**
* Determines if a given download was successful by querying the DownloadManager.
*
* @param enqueue the id used to identify/query the DownloadManager with.
* @return true if download was successful, false otherwise.
*/
private boolean downloadSuccessful(long enqueue) {
Query query = new Query();
query.setFilterById(enqueue);
Cursor c = mDownloadManager.query(query);
if (c.moveToFirst()) {
int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
Log.v(LOG_TAG, "Successfully downloaded file!");
return true;
}
}
return false;
}
/**
* Wait for network connectivity state.
* @param networkType the network to check for
* @param expectedState the desired state
* @param timeout in milliseconds
* @return true if the network connectivity state matched what was expected
*/
public boolean waitForNetworkState(int networkType, State expectedState, long timeout) {
long startTime = System.currentTimeMillis();
while (true) {
if ((System.currentTimeMillis() - startTime) > timeout) {
Log.v(LOG_TAG, "waitForNetworkState time out, the state of network type " + networkType +
" is: " + mCM.getNetworkInfo(networkType).getState());
if (mCM.getNetworkInfo(networkType).getState() != expectedState) {
return false;
} else {
// the broadcast has been sent out. the state has been changed.
Log.v(LOG_TAG, "networktype: " + networkType + " state: " +
mCM.getNetworkInfo(networkType));
return true;
}
}
Log.v(LOG_TAG, "Wait for the connectivity state for network: " + networkType +
" to be " + expectedState.toString());
synchronized (mConnectivityMonitor) {
try {
mConnectivityMonitor.wait(SHORT_TIMEOUT);
} catch (InterruptedException e) {
e.printStackTrace();
}
if ((mNetworkInfo.getType() != networkType) ||
(mNetworkInfo.getState() != expectedState)) {
Log.v(LOG_TAG, "network state for " + mNetworkInfo.getType() +
"is: " + mNetworkInfo.getState());
continue;
}
return true;
}
}
}
/**
* Wait for a given wifi state to occur within a given timeout.
* @param expectedState the expected wifi state.
* @param timeout for the state to be set in milliseconds.
* @return true if the state was achieved within the timeout, false otherwise.
*/
public boolean waitForWifiState(int expectedState, long timeout) {
// Wait for Wifi state: WIFI_STATE_DISABLED, WIFI_STATE_DISABLING, WIFI_STATE_ENABLED,
// WIFI_STATE_ENALBING, WIFI_STATE_UNKNOWN
long startTime = System.currentTimeMillis();
while (true) {
if ((System.currentTimeMillis() - startTime) > timeout) {
if (mWifiState != expectedState) {
return false;
} else {
return true;
}
}
Log.v(LOG_TAG, "Wait for wifi state to be: " + expectedState);
synchronized (mWifiMonitor) {
try {
mWifiMonitor.wait(SHORT_TIMEOUT);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (mWifiState != expectedState) {
Log.v(LOG_TAG, "Wifi state is: " + mWifiState);
continue;
}
return true;
}
}
}
/**
* Convenience method to determine if we are connected to a mobile network.
* @return true if connected to a mobile network, false otherwise.
*/
public boolean isConnectedToMobile() {
return (mNetworkInfo.getType() == ConnectivityManager.TYPE_MOBILE);
}
/**
* Convenience method to determine if we are connected to wifi.
* @return true if connected to wifi, false otherwise.
*/
public boolean isConnectedToWifi() {
return (mNetworkInfo.getType() == ConnectivityManager.TYPE_WIFI);
}
/**
* Associate the device to given SSID
* If the device is already associated with a WiFi, disconnect and forget it,
* We don't verify whether the connection is successful or not, leave this to the test
*/
public boolean connectToWifi(String knownSSID) {
WifiConfiguration config = new WifiConfiguration();
config.SSID = knownSSID;
config.allowedKeyManagement.set(KeyMgmt.NONE);
return connectToWifiWithConfiguration(config);
}
/**
* Connect to Wi-Fi with the given configuration.
* @param config
* @return true if we ar connected to a given
*/
public boolean connectToWifiWithConfiguration(WifiConfiguration config) {
// The SSID in the configuration is a pure string, need to convert it to a quoted string.
String ssid = config.SSID;
config.SSID = convertToQuotedString(ssid);
// If wifi is not enabled, enable it
if (!mWifiManager.isWifiEnabled()) {
Log.v(LOG_TAG, "Wifi is not enabled, enable it");
mWifiManager.setWifiEnabled(true);
// wait for the wifi state change before start scanning.
if (!waitForWifiState(WifiManager.WIFI_STATE_ENABLED, 2 * SHORT_TIMEOUT)) {
Log.v(LOG_TAG, "Wait for WIFI_STATE_ENABLED failed");
return false;
}
}
boolean foundApInScanResults = false;
for (int retry = 0; retry < 5; retry++) {
List<ScanResult> netList = mWifiManager.getScanResults();
if (netList != null) {
Log.v(LOG_TAG, "size of scan result list: " + netList.size());
for (int i = 0; i < netList.size(); i++) {
ScanResult sr= netList.get(i);
if (sr.SSID.equals(ssid)) {
Log.v(LOG_TAG, "Found " + ssid + " in the scan result list.");
Log.v(LOG_TAG, "Retry: " + retry);
foundApInScanResults = true;
mWifiManager.connectNetwork(config);
break;
}
}
}
if (foundApInScanResults) {
return true;
} else {
// Start an active scan
mWifiManager.startScanActive();
mScanResultIsAvailable = false;
long startTime = System.currentTimeMillis();
while (!mScanResultIsAvailable) {
if ((System.currentTimeMillis() - startTime) > WIFI_SCAN_TIMEOUT) {
Log.v(LOG_TAG, "wait for scan results timeout");
return false;
}
// wait for the scan results to be available
synchronized (this) {
// wait for the scan result to be available
try {
this.wait(WAIT_FOR_SCAN_RESULT);
} catch (InterruptedException e) {
e.printStackTrace();
}
if ((mWifiManager.getScanResults() == null) ||
(mWifiManager.getScanResults().size() <= 0)) {
continue;
}
mScanResultIsAvailable = true;
}
}
}
}
return false;
}
/*
* Disconnect from the current AP and remove configured networks.
*/
public boolean disconnectAP() {
// remove saved networks
List<WifiConfiguration> wifiConfigList = mWifiManager.getConfiguredNetworks();
Log.v(LOG_TAG, "size of wifiConfigList: " + wifiConfigList.size());
for (WifiConfiguration wifiConfig: wifiConfigList) {
Log.v(LOG_TAG, "Remove wifi configuration: " + wifiConfig.networkId);
int netId = wifiConfig.networkId;
mWifiManager.forgetNetwork(netId);
}
return true;
}
/**
* Enable Wifi
* @return true if Wifi is enabled successfully
*/
public boolean enableWifi() {
return mWifiManager.setWifiEnabled(true);
}
/**
* Disable Wifi
* @return true if Wifi is disabled successfully
*/
public boolean disableWifi() {
return mWifiManager.setWifiEnabled(false);
}
/**
* Remove configured networks and disable wifi
*/
public boolean removeConfiguredNetworksAndDisableWifi() {
if (!disconnectAP()) {
return false;
}
sleep(SHORT_TIMEOUT);
if (!mWifiManager.setWifiEnabled(false)) {
return false;
}
sleep(SHORT_TIMEOUT);
return true;
}
/**
* Make the current thread sleep.
* @param sleeptime the time to sleep in milliseconds
*/
private void sleep(long sleeptime) {
try {
Thread.sleep(sleeptime);
} catch (InterruptedException e) {}
}
/**
* Set airplane mode on device, caller is responsible to ensuring correct state.
* @param context {@link Context}
* @param enableAM to enable or disable airplane mode.
*/
public void setAirplaneMode(Context context, boolean enableAM) {
//set the airplane mode
Settings.System.putInt(context.getContentResolver(), Settings.System.AIRPLANE_MODE_ON,
enableAM ? 1 : 0);
// Post the intent
Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
intent.putExtra("state", enableAM);
context.sendBroadcast(intent);
}
/**
* Add quotes around the string.
* @param string to convert
* @return string with quotes around it
*/
protected static String convertToQuotedString(String string) {
return "\"" + string + "\"";
}
public void cleanUp() {
// Unregister receivers if defined.
if (mConnectivityReceiver != null) {
mContext.unregisterReceiver(mConnectivityReceiver);
}
if (mWifiReceiver != null) {
mContext.unregisterReceiver(mWifiReceiver);
}
if (mDownloadReceiver != null) {
mContext.unregisterReceiver(mDownloadReceiver);
}
Log.v(LOG_TAG, "onDestroy, inst=" + Integer.toHexString(hashCode()));
}
}