/* * Copyright (C) 2007 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.settings.wifi; import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED; import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED; import com.android.settings.R; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; import android.net.NetworkInfo.State; import android.net.wifi.ScanResult; import android.net.wifi.SupplicantState; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.Handler; import android.os.Message; import android.provider.Settings; import android.text.TextUtils; import android.util.Config; import android.util.Log; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; /** * Helper class for abstracting Wi-Fi. *
* Client must call {@link #onCreate()}, {@link #onCreatedCallback()},
* {@link #onPause()}, {@link #onResume()}.
*/
public class WifiLayer {
private static final String TAG = "SettingsWifiLayer";
static final boolean LOGV = false || Config.LOGV;
//============================
// Other member variables
//============================
private Context mContext;
private Callback mCallback;
static final int MESSAGE_ATTEMPT_SCAN = 1;
private Handler mHandler = new MyHandler();
//============================
// Wifi member variables
//============================
private WifiManager mWifiManager;
private IntentFilter mIntentFilter;
private List
* This shouldn't have any dependency on the callback.
*/
public void onCreate() {
mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
mIntentFilter = new IntentFilter();
mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
mIntentFilter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
mIntentFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
WIFI_NUM_OPEN_NETWORKS_KEPT = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.WIFI_NUM_OPEN_NETWORKS_KEPT, 10);
}
/**
* The client MUST call this.
*
* Callback is ready, this can do whatever it wants with it.
*/
public void onCreatedCallback() {
if (isWifiEnabled()) {
refreshAll(false);
}
}
/**
* The client MUST call this.
*
* @see android.app.Activity#onResume
*/
public void onResume() {
mContext.registerReceiver(mReceiver, mIntentFilter);
if (isWifiEnabled()) {
// Kick start the continual scan
queueContinuousScan();
}
}
/**
* The client MUST call this.
*
* @see android.app.Activity#onPause
*/
public void onPause() {
mContext.unregisterReceiver(mReceiver);
attemptReenableAllAps();
removeFutureScans();
}
//============================
// "Public" API
//============================
/**
* Returns an AccessPointState instance (that we track locally in WifiLayer)
* for the given state. First, we check if we track the given instance. If
* not, we find an equal AccessPointState instance that we track.
*
* @param state An AccessPointState instance that does not necessarily have
* to be one that this WifiLayer class tracks. For example, it
* could be the result of unparceling.
* @return An AccessPointState instance that this WifiLayer class tracks.
*/
public AccessPointState getWifiLayerApInstance(AccessPointState state) {
synchronized (this) {
if (hasApInstanceLocked(state)) {
return state;
}
return findApLocked(state.networkId, state.bssid, state.ssid, state.security);
}
}
/**
* Connects to the network, and creates the Wi-Fi API config if necessary.
*
* @param state The state of the network to connect to. This MUST be an
* instance that was given to you by this class. If you
* constructed the instance yourself (for example, after
* unparceling it), you should use
* {@link #getWifiLayerApInstance(AccessPointState)}.
* @return Whether the operation was successful.
*/
public boolean connectToNetwork(AccessPointState state) {
if (LOGV) {
Log.v(TAG, "Connecting to " + state);
}
// Need WifiConfiguration for the AP
WifiConfiguration config = findConfiguredNetwork(state);
if (LOGV) {
Log.v(TAG, " Found configured network " + config);
}
if (config == null) {
/*
* Connecting for the first time, need to create it. We will enable
* and save it below (when we set priority).
*/
config = addConfiguration(state, 0);
if (config == null) {
Log.e(TAG, "Config is still null, even after attempting to add it.");
error(R.string.error_connecting);
return false;
}
/*
* We could reload the configured networks, but instead just
* shortcut and add this state to our list in memory.
*/
ensureTrackingState(state);
} else {
// Make sure the configuration has the latest from the state
state.updateWifiConfiguration(config);
}
// Enable this network before we save to storage
if (!managerEnableNetwork(state, false)) {
Log.e(TAG, "Could not enable network ID " + state.networkId);
error(R.string.error_connecting);
return false;
}
/*
* Give it highest priority, this could cause a network ID change, so do
* it after any modifications to the network we're connecting to
*/
setHighestPriorityStateAndSave(state, config);
/*
* We force supplicant to connect to this network by disabling the
* others. We do this AFTER we save above so this disabled flag isn't
* persisted.
*/
mReenableApsOnNetworkStateChange = true;
if (!managerEnableNetwork(state, true)) {
Log.e(TAG, "Could not enable network ID " + state.networkId);
error(R.string.error_connecting);
return false;
}
if (LOGV) {
Log.v(TAG, " Enabled network " + state.networkId);
}
if (mCurrentSupplicantState == SupplicantState.DISCONNECTED ||
mCurrentSupplicantState == SupplicantState.SCANNING) {
mWifiManager.reconnect();
}
// Check for too many configured open networks
if (!state.hasSecurity()) {
checkForExcessOpenNetworks();
}
return true;
}
/**
* Saves a network, and creates the Wi-Fi API config if necessary.
*
* @param state The state of the network to save. If you constructed the
* instance yourself (for example, after unparceling it), you
* should use {@link #getWifiLayerApInstance(AccessPointState)}.
* @return Whether the operation was successful.
*/
public boolean saveNetwork(AccessPointState state) {
WifiConfiguration config = findConfiguredNetwork(state);
if (config == null) {
// if the user is adding a new network, assume that it is hidden
state.setHiddenSsid(true);
config = addConfiguration(state, ADD_CONFIGURATION_ENABLE);
if (config == null) {
Log.e(TAG, "Could not save configuration, call to addConfiguration failed.");
error(R.string.error_saving);
return false;
}
} else {
state.updateWifiConfiguration(config);
if (mWifiManager.updateNetwork(config) == -1) {
Log.e(TAG, "Could not update configuration, call to WifiManager failed.");
error(R.string.error_saving);
return false;
}
}
// Successfully added network, go ahead and persist
if (!managerSaveConfiguration()) {
Log.e(TAG, "Could not save configuration, call to WifiManager failed.");
error(R.string.error_saving);
return false;
}
/*
* We could reload the configured networks, but instead just shortcut
* and add this state to our list in memory
*/
ensureTrackingState(state);
return true;
}
/**
* Forgets a network.
*
* @param state The state of the network to forget. If you constructed the
* instance yourself (for example, after unparceling it), you
* should use {@link #getWifiLayerApInstance(AccessPointState)}.
* @return Whether the operation was succesful.
*/
public boolean forgetNetwork(AccessPointState state) {
if (!state.configured) {
Log.w(TAG, "Inconsistent state: Forgetting a network that is not configured.");
return true;
}
int oldNetworkId = state.networkId;
state.forget();
if (!state.seen) {
// If it is not seen, it should be removed from the UI
removeApFromUi(state);
}
synchronized (this) {
mApOtherList.remove(state);
// It should not be removed from the scan list, since if it was
// there that means it's still seen
}
if (!mWifiManager.removeNetwork(oldNetworkId)) {
Log.e(TAG, "Removing network " + state.ssid + " (network ID " + oldNetworkId +
") failed.");
return false;
}
if (!managerSaveConfiguration()) {
error(R.string.error_saving);
return false;
}
return true;
}
/**
* This ensures this class is tracking the given state. This means it is in
* our list of access points, either in the scanned list or in the
* remembered list.
*
* @param state The state that will be checked for tracking, and if not
* tracking will be added to the remembered list in memory.
*/
private void ensureTrackingState(AccessPointState state) {
synchronized (this) {
if (hasApInstanceLocked(state)) {
return;
}
mApOtherList.add(state);
}
}
/**
* Attempts to scan networks. This has a retry mechanism.
*/
public void attemptScan() {
// Remove any future scans since we're scanning right now
removeFutureScans();
if (!mWifiManager.isWifiEnabled()) return;
if (!mWifiManager.startScan()) {
postAttemptScan();
} else {
mScanRetryCount = 0;
}
}
private void queueContinuousScan() {
mHandler.removeMessages(MESSAGE_ATTEMPT_SCAN);
if (!mIsObtainingAddress) {
// Don't do continuous scan while in obtaining IP state
mHandler.sendEmptyMessageDelayed(MESSAGE_ATTEMPT_SCAN, CONTINUOUS_SCAN_DELAY_MS);
}
}
private void removeFutureScans() {
mHandler.removeMessages(MESSAGE_ATTEMPT_SCAN);
}
public boolean isWifiEnabled() {
return mWifiManager.isWifiEnabled();
}
public void error(int messageResId) {
Log.e(TAG, mContext.getResources().getString(messageResId));
if (mCallback != null) {
mCallback.onError(messageResId);
}
}
//============================
// Wifi logic
//============================
private void refreshAll(boolean attemptScan) {
loadConfiguredAccessPoints();
refreshStatus();
if (attemptScan) {
attemptScan();
}
}
private void postAttemptScan() {
onScanningStarted();
if (++mScanRetryCount < SCAN_MAX_RETRY) {
// Just in case, remove previous ones first
removeFutureScans();
mHandler.sendEmptyMessageDelayed(MESSAGE_ATTEMPT_SCAN, SCAN_RETRY_DELAY_MS);
} else {
// Show an error once we run out of attempts
error(R.string.error_scanning);
onScanningEnded();
}
}
private void onScanningStarted() {
if (mCallback != null) {
mCallback.onScanningStatusChanged(true);
}
}
private void onScanningEnded() {
queueContinuousScan();
if (mCallback != null) {
mCallback.onScanningStatusChanged(false);
}
}
private void clearApLists() {
List
* Must call while holding the lock for the lists, which is usually this
* WifiLayer instance.
*/
private boolean hasApInstanceLocked(AccessPointState state) {
for (int i = mApScanList.size() - 1; i >= 0; i--) {
if (mApScanList.get(i) == state) {
return true;
}
}
for (int i = mApOtherList.size() - 1; i >= 0; i--) {
if (mApOtherList.get(i) == state) {
return true;
}
}
return false;
}
private void loadConfiguredAccessPoints() {
final List
* If you have a list of configured networks from WifiManager, you probably
* shouldn't call this until you're done traversing the list.
*
* @param state The state to set as the highest priority.
* @param reusableConfiguration An optional WifiConfiguration that will be
* given to the WifiManager as updated data for the network ID.
* This will be filled with the new priority.
* @return Whether the operation was successful.
*/
private boolean setHighestPriorityStateAndSave(AccessPointState state,
WifiConfiguration reusableConfiguration) {
if (!isConsideredForHighestPriority(state)) {
Log.e(TAG,
"Could not set highest priority on state because state is not being considered.");
return false;
}
if (reusableConfiguration == null) {
reusableConfiguration = new WifiConfiguration();
}
int oldPriority = reusableConfiguration.priority;
reusableConfiguration.priority = getNextHighestPriority();
reusableConfiguration.networkId = state.networkId;
if (mWifiManager.updateNetwork(reusableConfiguration) == -1) {
// Rollback priority
reusableConfiguration.priority = oldPriority;
Log.e(TAG,
"Could not set highest priority on state because updating the supplicant network failed.");
return false;
}
if (!managerSaveConfiguration()) {
reusableConfiguration.priority = oldPriority;
Log.e(TAG,
"Could not set highest priority on state because saving config failed.");
return false;
}
state.priority = reusableConfiguration.priority;
if (LOGV) {
Log.v(TAG, " Set highest priority to "
+ state.priority + " from " + oldPriority);
}
return true;
}
/**
* Makes sure the next highest priority is larger than the given priority.
*/
private void checkNextHighestPriority(int priority) {
if (priority > HIGHEST_PRIORITY_MAX_VALUE || priority < 0) {
// This is a priority that we aren't managing
return;
}
if (mNextHighestPriority <= priority) {
mNextHighestPriority = priority + 1;
}
}
/**
* Checks if there are too many open networks, and removes the excess ones.
*/
private void checkForExcessOpenNetworks() {
synchronized(this) {
ArrayList
* Only
* {@link #setHighestPriorityStateAndSave(AccessPointState, WifiConfiguration)}
* should call this.
*
* @return The next highest priority to use.
*/
private int getNextHighestPriority() {
if (mNextHighestPriority > HIGHEST_PRIORITY_MAX_VALUE) {
shiftPriorities();
}
return mNextHighestPriority++;
}
/**
* Shift all the priorities so the lowest starts at 0.
*
* @return Whether the operation was successful.
*/
private boolean shiftPriorities() {
synchronized(this) {
ArrayList