diff options
author | Tsu Chiang Chuang <tsu@google.com> | 2011-07-27 15:51:49 -0700 |
---|---|---|
committer | Tsu Chiang Chuang <tsu@google.com> | 2011-08-02 19:11:41 -0700 |
commit | 33f869951fde247927e66c3aa4ab86fc61f783ad (patch) | |
tree | 1345bb58f469061dfb9e676a0972aa9e21388bd9 /core/tests/bandwidthtests/src | |
parent | 5465e054d33436fa446465ebcff871f6b7e1e3cc (diff) | |
download | frameworks_base-33f869951fde247927e66c3aa4ab86fc61f783ad.zip frameworks_base-33f869951fde247927e66c3aa4ab86fc61f783ad.tar.gz frameworks_base-33f869951fde247927e66c3aa4ab86fc61f783ad.tar.bz2 |
Bandwidth microbenchmark test app for ICS.
Change-Id: I6fed5c47818f0fe08b9f2c18f1070d3238a469b6
Diffstat (limited to 'core/tests/bandwidthtests/src')
5 files changed, 1305 insertions, 0 deletions
diff --git a/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java new file mode 100644 index 0000000..73c92b0 --- /dev/null +++ b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java @@ -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; + } +}
\ No newline at end of file diff --git a/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTestRunner.java b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTestRunner.java new file mode 100644 index 0000000..290ccee --- /dev/null +++ b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTestRunner.java @@ -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; + } + } +} diff --git a/core/tests/bandwidthtests/src/com/android/bandwidthtest/NetworkState.java b/core/tests/bandwidthtests/src/com/android/bandwidthtest/NetworkState.java new file mode 100644 index 0000000..fae0e76 --- /dev/null +++ b/core/tests/bandwidthtests/src/com/android/bandwidthtest/NetworkState.java @@ -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(); + } +} diff --git a/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/BandwidthTestUtil.java b/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/BandwidthTestUtil.java new file mode 100644 index 0000000..d850169 --- /dev/null +++ b/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/BandwidthTestUtil.java @@ -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 + + "×tamp=" + 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; + } + +} diff --git a/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java b/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java new file mode 100644 index 0000000..d663aad --- /dev/null +++ b/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java @@ -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())); + } +}
\ No newline at end of file |