blob: a5e5ab0e77b9b41f59a9730af8301dabe1986366 [file] [log] [blame]
/*
* 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.io.IOException;
import java.net.UnknownHostException;
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() {
NetworkInfo networkInfo = mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
return networkInfo.isConnected();
}
/**
* Convenience method to determine if we are connected to wifi.
* @return true if connected to wifi, false otherwise.
*/
public boolean isConnectedToWifi() {
NetworkInfo networkInfo = mCM.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
return networkInfo.isConnected();
}
/**
* 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()));
}
/**
* Helper method used to test data connectivity by pinging a series of popular sites.
* @return true if device has data connectivity, false otherwise.
*/
public boolean hasData() {
String[] hostList = {"www.google.com", "www.yahoo.com",
"www.bing.com", "www.facebook.com", "www.ask.com"};
try {
for (int i = 0; i < hostList.length; ++i) {
String host = hostList[i];
Process p = Runtime.getRuntime().exec("ping -c 10 -w 100 " + host);
int status = p.waitFor();
if (status == 0) {
return true;
}
}
} catch (UnknownHostException e) {
Log.e(LOG_TAG, "Ping test Failed: Unknown Host");
} catch (IOException e) {
Log.e(LOG_TAG, "Ping test Failed: IOException");
} catch (InterruptedException e) {
Log.e(LOG_TAG, "Ping test Failed: InterruptedException");
}
return false;
}
}