blob: 9418de1f7c6a826e5eefed47114b1d0483d6ac25 [file] [log] [blame]
/*
* Copyright (C) 2010 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 android.net.wifi;
import android.content.Context;
import android.content.Intent;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkUtils;
import android.net.NetworkInfo.DetailedState;
import android.net.ProxyProperties;
import android.net.RouteInfo;
import android.net.wifi.WifiConfiguration.IpAssignment;
import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.net.wifi.WifiConfiguration.ProxySettings;
import android.net.wifi.WifiConfiguration.Status;
import android.net.wifi.NetworkUpdateResult;
import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
import android.os.Environment;
import android.os.Message;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.UserHandle;
import android.security.KeyStore;
import android.text.TextUtils;
import android.util.Log;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* This class provides the API to manage configured
* wifi networks. The API is not thread safe is being
* used only from WifiStateMachine.
*
* It deals with the following
* - Add/update/remove a WifiConfiguration
* The configuration contains two types of information.
* = IP and proxy configuration that is handled by WifiConfigStore and
* is saved to disk on any change.
*
* The format of configuration file is as follows:
* <version>
* <netA_key1><netA_value1><netA_key2><netA_value2>...<EOS>
* <netB_key1><netB_value1><netB_key2><netB_value2>...<EOS>
* ..
*
* (key, value) pairs for a given network are grouped together and can
* be in any order. A EOS at the end of a set of (key, value) pairs
* indicates that the next set of (key, value) pairs are for a new
* network. A network is identified by a unique ID_KEY. If there is no
* ID_KEY in the (key, value) pairs, the data is discarded.
*
* An invalid version on read would result in discarding the contents of
* the file. On the next write, the latest version is written to file.
*
* Any failures during read or write to the configuration file are ignored
* without reporting to the user since the likelihood of these errors are
* low and the impact on connectivity is low.
*
* = SSID & security details that is pushed to the supplicant.
* supplicant saves these details to the disk on calling
* saveConfigCommand().
*
* We have two kinds of APIs exposed:
* > public API calls that provide fine grained control
* - enableNetwork, disableNetwork, addOrUpdateNetwork(),
* removeNetwork(). For these calls, the config is not persisted
* to the disk. (TODO: deprecate these calls in WifiManager)
* > The new API calls - selectNetwork(), saveNetwork() & forgetNetwork().
* These calls persist the supplicant config to disk.
*
* - Maintain a list of configured networks for quick access
*
*/
class WifiConfigStore {
private Context mContext;
private static final String TAG = "WifiConfigStore";
private static final boolean DBG = false;
/* configured networks with network id as the key */
private HashMap<Integer, WifiConfiguration> mConfiguredNetworks =
new HashMap<Integer, WifiConfiguration>();
/* A network id is a unique identifier for a network configured in the
* supplicant. Network ids are generated when the supplicant reads
* the configuration file at start and can thus change for networks.
* We store the IP configuration for networks along with a unique id
* that is generated from SSID and security type of the network. A mapping
* from the generated unique id to network id of the network is needed to
* map supplicant config to IP configuration. */
private HashMap<Integer, Integer> mNetworkIds =
new HashMap<Integer, Integer>();
/* Tracks the highest priority of configured networks */
private int mLastPriority = -1;
private static final String ipConfigFile = Environment.getDataDirectory() +
"/misc/wifi/ipconfig.txt";
private static final int IPCONFIG_FILE_VERSION = 2;
/* IP and proxy configuration keys */
private static final String ID_KEY = "id";
private static final String IP_ASSIGNMENT_KEY = "ipAssignment";
private static final String LINK_ADDRESS_KEY = "linkAddress";
private static final String GATEWAY_KEY = "gateway";
private static final String DNS_KEY = "dns";
private static final String PROXY_SETTINGS_KEY = "proxySettings";
private static final String PROXY_HOST_KEY = "proxyHost";
private static final String PROXY_PORT_KEY = "proxyPort";
private static final String EXCLUSION_LIST_KEY = "exclusionList";
private static final String EOS = "eos";
private WifiNative mWifiNative;
private final KeyStore mKeyStore = KeyStore.getInstance();
WifiConfigStore(Context c, WifiNative wn) {
mContext = c;
mWifiNative = wn;
}
/**
* Fetch the list of configured networks
* and enable all stored networks in supplicant.
*/
void loadAndEnableAllNetworks() {
if (DBG) log("Loading config and enabling all networks");
loadConfiguredNetworks();
enableAllNetworks();
}
/**
* Fetch the list of currently configured networks
* @return List of networks
*/
List<WifiConfiguration> getConfiguredNetworks() {
List<WifiConfiguration> networks = new ArrayList<WifiConfiguration>();
for(WifiConfiguration config : mConfiguredNetworks.values()) {
networks.add(new WifiConfiguration(config));
}
return networks;
}
/**
* enable all networks and save config. This will be a no-op if the list
* of configured networks indicates all networks as being enabled
*/
void enableAllNetworks() {
boolean networkEnabledStateChanged = false;
for(WifiConfiguration config : mConfiguredNetworks.values()) {
if(config != null && config.status == Status.DISABLED) {
if(mWifiNative.enableNetwork(config.networkId, false)) {
networkEnabledStateChanged = true;
config.status = Status.ENABLED;
} else {
loge("Enable network failed on " + config.networkId);
}
}
}
if (networkEnabledStateChanged) {
mWifiNative.saveConfig();
sendConfiguredNetworksChangedBroadcast();
}
}
/**
* Selects the specified network for connection. This involves
* updating the priority of all the networks and enabling the given
* network while disabling others.
*
* Selecting a network will leave the other networks disabled and
* a call to enableAllNetworks() needs to be issued upon a connection
* or a failure event from supplicant
*
* @param netId network to select for connection
* @return false if the network id is invalid
*/
boolean selectNetwork(int netId) {
if (netId == INVALID_NETWORK_ID) return false;
// Reset the priority of each network at start or if it goes too high.
if (mLastPriority == -1 || mLastPriority > 1000000) {
for(WifiConfiguration config : mConfiguredNetworks.values()) {
if (config.networkId != INVALID_NETWORK_ID) {
config.priority = 0;
addOrUpdateNetworkNative(config);
}
}
mLastPriority = 0;
}
// Set to the highest priority and save the configuration.
WifiConfiguration config = new WifiConfiguration();
config.networkId = netId;
config.priority = ++mLastPriority;
addOrUpdateNetworkNative(config);
mWifiNative.saveConfig();
/* Enable the given network while disabling all other networks */
enableNetworkWithoutBroadcast(netId, true);
/* Avoid saving the config & sending a broadcast to prevent settings
* from displaying a disabled list of networks */
return true;
}
/**
* Add/update the specified configuration and save config
*
* @param config WifiConfiguration to be saved
* @return network update result
*/
NetworkUpdateResult saveNetwork(WifiConfiguration config) {
// A new network cannot have null SSID
if (config == null || (config.networkId == INVALID_NETWORK_ID &&
config.SSID == null)) {
return new NetworkUpdateResult(INVALID_NETWORK_ID);
}
boolean newNetwork = (config.networkId == INVALID_NETWORK_ID);
NetworkUpdateResult result = addOrUpdateNetworkNative(config);
int netId = result.getNetworkId();
/* enable a new network */
if (newNetwork && netId != INVALID_NETWORK_ID) {
mWifiNative.enableNetwork(netId, false);
mConfiguredNetworks.get(netId).status = Status.ENABLED;
}
mWifiNative.saveConfig();
sendConfiguredNetworksChangedBroadcast(config, result.isNewNetwork() ?
WifiManager.CHANGE_REASON_ADDED : WifiManager.CHANGE_REASON_CONFIG_CHANGE);
return result;
}
void updateStatus(int netId, DetailedState state) {
if (netId != INVALID_NETWORK_ID) {
WifiConfiguration config = mConfiguredNetworks.get(netId);
if (config == null) return;
switch (state) {
case CONNECTED:
config.status = Status.CURRENT;
break;
case DISCONNECTED:
//If network is already disabled, keep the status
if (config.status == Status.CURRENT) {
config.status = Status.ENABLED;
}
break;
default:
//do nothing, retain the existing state
break;
}
}
}
/**
* Forget the specified network and save config
*
* @param netId network to forget
* @return {@code true} if it succeeds, {@code false} otherwise
*/
boolean forgetNetwork(int netId) {
if (mWifiNative.removeNetwork(netId)) {
mWifiNative.saveConfig();
removeConfigAndSendBroadcastIfNeeded(netId);
return true;
} else {
loge("Failed to remove network " + netId);
return false;
}
}
/**
* Add/update a network. Note that there is no saveConfig operation.
* This function is retained for compatibility with the public
* API. The more powerful saveNetwork() is used by the
* state machine
*
* @param config wifi configuration to add/update
* @return network Id
*/
int addOrUpdateNetwork(WifiConfiguration config) {
NetworkUpdateResult result = addOrUpdateNetworkNative(config);
if (result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID) {
sendConfiguredNetworksChangedBroadcast(mConfiguredNetworks.get(result.getNetworkId()),
result.isNewNetwork ? WifiManager.CHANGE_REASON_ADDED :
WifiManager.CHANGE_REASON_CONFIG_CHANGE);
}
return result.getNetworkId();
}
/**
* Remove a network. Note that there is no saveConfig operation.
* This function is retained for compatibility with the public
* API. The more powerful forgetNetwork() is used by the
* state machine for network removal
*
* @param netId network to be removed
* @return {@code true} if it succeeds, {@code false} otherwise
*/
boolean removeNetwork(int netId) {
boolean ret = mWifiNative.removeNetwork(netId);
if (ret) {
removeConfigAndSendBroadcastIfNeeded(netId);
}
return ret;
}
private void removeConfigAndSendBroadcastIfNeeded(int netId) {
WifiConfiguration config = mConfiguredNetworks.get(netId);
if (config != null) {
// Remove any associated keys
if (config.enterpriseConfig != null) {
config.enterpriseConfig.removeKeys(mKeyStore);
}
mConfiguredNetworks.remove(netId);
mNetworkIds.remove(configKey(config));
writeIpAndProxyConfigurations();
sendConfiguredNetworksChangedBroadcast(config, WifiManager.CHANGE_REASON_REMOVED);
}
}
/**
* Enable a network. Note that there is no saveConfig operation.
* This function is retained for compatibility with the public
* API. The more powerful selectNetwork()/saveNetwork() is used by the
* state machine for connecting to a network
*
* @param netId network to be enabled
* @return {@code true} if it succeeds, {@code false} otherwise
*/
boolean enableNetwork(int netId, boolean disableOthers) {
boolean ret = enableNetworkWithoutBroadcast(netId, disableOthers);
if (disableOthers) {
sendConfiguredNetworksChangedBroadcast();
} else {
WifiConfiguration enabledNetwork = null;
synchronized(mConfiguredNetworks) {
enabledNetwork = mConfiguredNetworks.get(netId);
}
// check just in case the network was removed by someone else.
if (enabledNetwork != null) {
sendConfiguredNetworksChangedBroadcast(enabledNetwork,
WifiManager.CHANGE_REASON_CONFIG_CHANGE);
}
}
return ret;
}
boolean enableNetworkWithoutBroadcast(int netId, boolean disableOthers) {
boolean ret = mWifiNative.enableNetwork(netId, disableOthers);
WifiConfiguration config = mConfiguredNetworks.get(netId);
if (config != null) config.status = Status.ENABLED;
if (disableOthers) {
markAllNetworksDisabledExcept(netId);
}
return ret;
}
void disableAllNetworks() {
boolean networkDisabled = false;
for(WifiConfiguration config : mConfiguredNetworks.values()) {
if(config != null && config.status != Status.DISABLED) {
if(mWifiNative.disableNetwork(config.networkId)) {
networkDisabled = true;
config.status = Status.DISABLED;
} else {
loge("Disable network failed on " + config.networkId);
}
}
}
if (networkDisabled) {
sendConfiguredNetworksChangedBroadcast();
}
}
/**
* Disable a network. Note that there is no saveConfig operation.
* @param netId network to be disabled
* @return {@code true} if it succeeds, {@code false} otherwise
*/
boolean disableNetwork(int netId) {
return disableNetwork(netId, WifiConfiguration.DISABLED_UNKNOWN_REASON);
}
/**
* Disable a network. Note that there is no saveConfig operation.
* @param netId network to be disabled
* @param reason reason code network was disabled
* @return {@code true} if it succeeds, {@code false} otherwise
*/
boolean disableNetwork(int netId, int reason) {
boolean ret = mWifiNative.disableNetwork(netId);
WifiConfiguration network = null;
WifiConfiguration config = mConfiguredNetworks.get(netId);
/* Only change the reason if the network was not previously disabled */
if (config != null && config.status != Status.DISABLED) {
config.status = Status.DISABLED;
config.disableReason = reason;
network = config;
}
if (network != null) {
sendConfiguredNetworksChangedBroadcast(network,
WifiManager.CHANGE_REASON_CONFIG_CHANGE);
}
return ret;
}
/**
* Save the configured networks in supplicant to disk
* @return {@code true} if it succeeds, {@code false} otherwise
*/
boolean saveConfig() {
return mWifiNative.saveConfig();
}
/**
* Start WPS pin method configuration with pin obtained
* from the access point
* @param config WPS configuration
* @return Wps result containing status and pin
*/
WpsResult startWpsWithPinFromAccessPoint(WpsInfo config) {
WpsResult result = new WpsResult();
if (mWifiNative.startWpsRegistrar(config.BSSID, config.pin)) {
/* WPS leaves all networks disabled */
markAllNetworksDisabled();
result.status = WpsResult.Status.SUCCESS;
} else {
loge("Failed to start WPS pin method configuration");
result.status = WpsResult.Status.FAILURE;
}
return result;
}
/**
* Start WPS pin method configuration with pin obtained
* from the device
* @return WpsResult indicating status and pin
*/
WpsResult startWpsWithPinFromDevice(WpsInfo config) {
WpsResult result = new WpsResult();
result.pin = mWifiNative.startWpsPinDisplay(config.BSSID);
/* WPS leaves all networks disabled */
if (!TextUtils.isEmpty(result.pin)) {
markAllNetworksDisabled();
result.status = WpsResult.Status.SUCCESS;
} else {
loge("Failed to start WPS pin method configuration");
result.status = WpsResult.Status.FAILURE;
}
return result;
}
/**
* Start WPS push button configuration
* @param config WPS configuration
* @return WpsResult indicating status and pin
*/
WpsResult startWpsPbc(WpsInfo config) {
WpsResult result = new WpsResult();
if (mWifiNative.startWpsPbc(config.BSSID)) {
/* WPS leaves all networks disabled */
markAllNetworksDisabled();
result.status = WpsResult.Status.SUCCESS;
} else {
loge("Failed to start WPS push button configuration");
result.status = WpsResult.Status.FAILURE;
}
return result;
}
/**
* Fetch the link properties for a given network id
* @return LinkProperties for the given network id
*/
LinkProperties getLinkProperties(int netId) {
WifiConfiguration config = mConfiguredNetworks.get(netId);
if (config != null) return new LinkProperties(config.linkProperties);
return null;
}
/**
* set IP configuration for a given network id
*/
void setLinkProperties(int netId, LinkProperties linkProperties) {
WifiConfiguration config = mConfiguredNetworks.get(netId);
if (config != null) {
// add old proxy details - TODO - is this still needed?
if(config.linkProperties != null) {
linkProperties.setHttpProxy(config.linkProperties.getHttpProxy());
}
config.linkProperties = linkProperties;
}
}
/**
* clear IP configuration for a given network id
* @param network id
*/
void clearLinkProperties(int netId) {
WifiConfiguration config = mConfiguredNetworks.get(netId);
if (config != null && config.linkProperties != null) {
// Clear everything except proxy
ProxyProperties proxy = config.linkProperties.getHttpProxy();
config.linkProperties.clear();
config.linkProperties.setHttpProxy(proxy);
}
}
/**
* Fetch the proxy properties for a given network id
* @param network id
* @return ProxyProperties for the network id
*/
ProxyProperties getProxyProperties(int netId) {
LinkProperties linkProperties = getLinkProperties(netId);
if (linkProperties != null) {
return new ProxyProperties(linkProperties.getHttpProxy());
}
return null;
}
/**
* Return if the specified network is using static IP
* @param network id
* @return {@code true} if using static ip for netId
*/
boolean isUsingStaticIp(int netId) {
WifiConfiguration config = mConfiguredNetworks.get(netId);
if (config != null && config.ipAssignment == IpAssignment.STATIC) {
return true;
}
return false;
}
/**
* Should be called when a single network configuration is made.
* @param network The network configuration that changed.
* @param reason The reason for the change, should be one of WifiManager.CHANGE_REASON_ADDED,
* WifiManager.CHANGE_REASON_REMOVED, or WifiManager.CHANGE_REASON_CHANGE.
*/
private void sendConfiguredNetworksChangedBroadcast(WifiConfiguration network,
int reason) {
Intent intent = new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
intent.putExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED, false);
intent.putExtra(WifiManager.EXTRA_WIFI_CONFIGURATION, network);
intent.putExtra(WifiManager.EXTRA_CHANGE_REASON, reason);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
/**
* Should be called when multiple network configuration changes are made.
*/
private void sendConfiguredNetworksChangedBroadcast() {
Intent intent = new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
intent.putExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED, true);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
void loadConfiguredNetworks() {
String listStr = mWifiNative.listNetworks();
mLastPriority = 0;
mConfiguredNetworks.clear();
mNetworkIds.clear();
if (listStr == null)
return;
String[] lines = listStr.split("\n");
// Skip the first line, which is a header
for (int i = 1; i < lines.length; i++) {
String[] result = lines[i].split("\t");
// network-id | ssid | bssid | flags
WifiConfiguration config = new WifiConfiguration();
try {
config.networkId = Integer.parseInt(result[0]);
} catch(NumberFormatException e) {
continue;
}
if (result.length > 3) {
if (result[3].indexOf("[CURRENT]") != -1)
config.status = WifiConfiguration.Status.CURRENT;
else if (result[3].indexOf("[DISABLED]") != -1)
config.status = WifiConfiguration.Status.DISABLED;
else
config.status = WifiConfiguration.Status.ENABLED;
} else {
config.status = WifiConfiguration.Status.ENABLED;
}
readNetworkVariables(config);
if (config.priority > mLastPriority) {
mLastPriority = config.priority;
}
config.ipAssignment = IpAssignment.DHCP;
config.proxySettings = ProxySettings.NONE;
mConfiguredNetworks.put(config.networkId, config);
mNetworkIds.put(configKey(config), config.networkId);
}
readIpAndProxyConfigurations();
sendConfiguredNetworksChangedBroadcast();
}
/* Mark all networks except specified netId as disabled */
private void markAllNetworksDisabledExcept(int netId) {
for(WifiConfiguration config : mConfiguredNetworks.values()) {
if(config != null && config.networkId != netId) {
if (config.status != Status.DISABLED) {
config.status = Status.DISABLED;
config.disableReason = WifiConfiguration.DISABLED_UNKNOWN_REASON;
}
}
}
}
private void markAllNetworksDisabled() {
markAllNetworksDisabledExcept(INVALID_NETWORK_ID);
}
private void writeIpAndProxyConfigurations() {
/* Make a copy */
List<WifiConfiguration> networks = new ArrayList<WifiConfiguration>();
for(WifiConfiguration config : mConfiguredNetworks.values()) {
networks.add(new WifiConfiguration(config));
}
DelayedDiskWrite.write(networks);
}
private static class DelayedDiskWrite {
private static HandlerThread sDiskWriteHandlerThread;
private static Handler sDiskWriteHandler;
/* Tracks multiple writes on the same thread */
private static int sWriteSequence = 0;
private static final String TAG = "DelayedDiskWrite";
static void write (final List<WifiConfiguration> networks) {
/* Do a delayed write to disk on a seperate handler thread */
synchronized (DelayedDiskWrite.class) {
if (++sWriteSequence == 1) {
sDiskWriteHandlerThread = new HandlerThread("WifiConfigThread");
sDiskWriteHandlerThread.start();
sDiskWriteHandler = new Handler(sDiskWriteHandlerThread.getLooper());
}
}
sDiskWriteHandler.post(new Runnable() {
@Override
public void run() {
onWriteCalled(networks);
}
});
}
private static void onWriteCalled(List<WifiConfiguration> networks) {
DataOutputStream out = null;
try {
out = new DataOutputStream(new BufferedOutputStream(
new FileOutputStream(ipConfigFile)));
out.writeInt(IPCONFIG_FILE_VERSION);
for(WifiConfiguration config : networks) {
boolean writeToFile = false;
try {
LinkProperties linkProperties = config.linkProperties;
switch (config.ipAssignment) {
case STATIC:
out.writeUTF(IP_ASSIGNMENT_KEY);
out.writeUTF(config.ipAssignment.toString());
for (LinkAddress linkAddr : linkProperties.getLinkAddresses()) {
out.writeUTF(LINK_ADDRESS_KEY);
out.writeUTF(linkAddr.getAddress().getHostAddress());
out.writeInt(linkAddr.getNetworkPrefixLength());
}
for (RouteInfo route : linkProperties.getRoutes()) {
out.writeUTF(GATEWAY_KEY);
LinkAddress dest = route.getDestination();
if (dest != null) {
out.writeInt(1);
out.writeUTF(dest.getAddress().getHostAddress());
out.writeInt(dest.getNetworkPrefixLength());
} else {
out.writeInt(0);
}
if (route.getGateway() != null) {
out.writeInt(1);
out.writeUTF(route.getGateway().getHostAddress());
} else {
out.writeInt(0);
}
}
for (InetAddress inetAddr : linkProperties.getDnses()) {
out.writeUTF(DNS_KEY);
out.writeUTF(inetAddr.getHostAddress());
}
writeToFile = true;
break;
case DHCP:
out.writeUTF(IP_ASSIGNMENT_KEY);
out.writeUTF(config.ipAssignment.toString());
writeToFile = true;
break;
case UNASSIGNED:
/* Ignore */
break;
default:
loge("Ignore invalid ip assignment while writing");
break;
}
switch (config.proxySettings) {
case STATIC:
ProxyProperties proxyProperties = linkProperties.getHttpProxy();
String exclusionList = proxyProperties.getExclusionList();
out.writeUTF(PROXY_SETTINGS_KEY);
out.writeUTF(config.proxySettings.toString());
out.writeUTF(PROXY_HOST_KEY);
out.writeUTF(proxyProperties.getHost());
out.writeUTF(PROXY_PORT_KEY);
out.writeInt(proxyProperties.getPort());
out.writeUTF(EXCLUSION_LIST_KEY);
out.writeUTF(exclusionList);
writeToFile = true;
break;
case NONE:
out.writeUTF(PROXY_SETTINGS_KEY);
out.writeUTF(config.proxySettings.toString());
writeToFile = true;
break;
case UNASSIGNED:
/* Ignore */
break;
default:
loge("Ignthisore invalid proxy settings while writing");
break;
}
if (writeToFile) {
out.writeUTF(ID_KEY);
out.writeInt(configKey(config));
}
} catch (NullPointerException e) {
loge("Failure in writing " + config.linkProperties + e);
}
out.writeUTF(EOS);
}
} catch (IOException e) {
loge("Error writing data file");
} finally {
if (out != null) {
try {
out.close();
} catch (Exception e) {}
}
//Quit if no more writes sent
synchronized (DelayedDiskWrite.class) {
if (--sWriteSequence == 0) {
sDiskWriteHandler.getLooper().quit();
sDiskWriteHandler = null;
sDiskWriteHandlerThread = null;
}
}
}
}
private static void loge(String s) {
Log.e(TAG, s);
}
}
private void readIpAndProxyConfigurations() {
DataInputStream in = null;
try {
in = new DataInputStream(new BufferedInputStream(new FileInputStream(
ipConfigFile)));
int version = in.readInt();
if (version != 2 && version != 1) {
loge("Bad version on IP configuration file, ignore read");
return;
}
while (true) {
int id = -1;
// Default is DHCP with no proxy
IpAssignment ipAssignment = IpAssignment.DHCP;
ProxySettings proxySettings = ProxySettings.NONE;
LinkProperties linkProperties = new LinkProperties();
String proxyHost = null;
int proxyPort = -1;
String exclusionList = null;
String key;
do {
key = in.readUTF();
try {
if (key.equals(ID_KEY)) {
id = in.readInt();
} else if (key.equals(IP_ASSIGNMENT_KEY)) {
ipAssignment = IpAssignment.valueOf(in.readUTF());
} else if (key.equals(LINK_ADDRESS_KEY)) {
LinkAddress linkAddr = new LinkAddress(
NetworkUtils.numericToInetAddress(in.readUTF()), in.readInt());
linkProperties.addLinkAddress(linkAddr);
} else if (key.equals(GATEWAY_KEY)) {
LinkAddress dest = null;
InetAddress gateway = null;
if (version == 1) {
// only supported default gateways - leave the dest/prefix empty
gateway = NetworkUtils.numericToInetAddress(in.readUTF());
} else {
if (in.readInt() == 1) {
dest = new LinkAddress(
NetworkUtils.numericToInetAddress(in.readUTF()),
in.readInt());
}
if (in.readInt() == 1) {
gateway = NetworkUtils.numericToInetAddress(in.readUTF());
}
}
linkProperties.addRoute(new RouteInfo(dest, gateway));
} else if (key.equals(DNS_KEY)) {
linkProperties.addDns(
NetworkUtils.numericToInetAddress(in.readUTF()));
} else if (key.equals(PROXY_SETTINGS_KEY)) {
proxySettings = ProxySettings.valueOf(in.readUTF());
} else if (key.equals(PROXY_HOST_KEY)) {
proxyHost = in.readUTF();
} else if (key.equals(PROXY_PORT_KEY)) {
proxyPort = in.readInt();
} else if (key.equals(EXCLUSION_LIST_KEY)) {
exclusionList = in.readUTF();
} else if (key.equals(EOS)) {
break;
} else {
loge("Ignore unknown key " + key + "while reading");
}
} catch (IllegalArgumentException e) {
loge("Ignore invalid address while reading" + e);
}
} while (true);
if (id != -1) {
WifiConfiguration config = mConfiguredNetworks.get(
mNetworkIds.get(id));
if (config == null) {
loge("configuration found for missing network, ignored");
} else {
config.linkProperties = linkProperties;
switch (ipAssignment) {
case STATIC:
case DHCP:
config.ipAssignment = ipAssignment;
break;
case UNASSIGNED:
loge("BUG: Found UNASSIGNED IP on file, use DHCP");
config.ipAssignment = IpAssignment.DHCP;
break;
default:
loge("Ignore invalid ip assignment while reading");
break;
}
switch (proxySettings) {
case STATIC:
config.proxySettings = proxySettings;
ProxyProperties proxyProperties =
new ProxyProperties(proxyHost, proxyPort, exclusionList);
linkProperties.setHttpProxy(proxyProperties);
break;
case NONE:
config.proxySettings = proxySettings;
break;
case UNASSIGNED:
loge("BUG: Found UNASSIGNED proxy on file, use NONE");
config.proxySettings = ProxySettings.NONE;
break;
default:
loge("Ignore invalid proxy settings while reading");
break;
}
}
} else {
if (DBG) log("Missing id while parsing configuration");
}
}
} catch (EOFException ignore) {
} catch (IOException e) {
loge("Error parsing configuration" + e);
} finally {
if (in != null) {
try {
in.close();
} catch (Exception e) {}
}
}
}
private NetworkUpdateResult addOrUpdateNetworkNative(WifiConfiguration config) {
/*
* If the supplied networkId is INVALID_NETWORK_ID, we create a new empty
* network configuration. Otherwise, the networkId should
* refer to an existing configuration.
*/
int netId = config.networkId;
boolean newNetwork = false;
// networkId of INVALID_NETWORK_ID means we want to create a new network
if (netId == INVALID_NETWORK_ID) {
Integer savedNetId = mNetworkIds.get(configKey(config));
if (savedNetId != null) {
netId = savedNetId;
} else {
newNetwork = true;
netId = mWifiNative.addNetwork();
if (netId < 0) {
loge("Failed to add a network!");
return new NetworkUpdateResult(INVALID_NETWORK_ID);
}
}
}
boolean updateFailed = true;
setVariables: {
if (config.SSID != null &&
!mWifiNative.setNetworkVariable(
netId,
WifiConfiguration.ssidVarName,
config.SSID)) {
loge("failed to set SSID: "+config.SSID);
break setVariables;
}
if (config.BSSID != null &&
!mWifiNative.setNetworkVariable(
netId,
WifiConfiguration.bssidVarName,
config.BSSID)) {
loge("failed to set BSSID: "+config.BSSID);
break setVariables;
}
String allowedKeyManagementString =
makeString(config.allowedKeyManagement, WifiConfiguration.KeyMgmt.strings);
if (config.allowedKeyManagement.cardinality() != 0 &&
!mWifiNative.setNetworkVariable(
netId,
WifiConfiguration.KeyMgmt.varName,
allowedKeyManagementString)) {
loge("failed to set key_mgmt: "+
allowedKeyManagementString);
break setVariables;
}
String allowedProtocolsString =
makeString(config.allowedProtocols, WifiConfiguration.Protocol.strings);
if (config.allowedProtocols.cardinality() != 0 &&
!mWifiNative.setNetworkVariable(
netId,
WifiConfiguration.Protocol.varName,
allowedProtocolsString)) {
loge("failed to set proto: "+
allowedProtocolsString);
break setVariables;
}
String allowedAuthAlgorithmsString =
makeString(config.allowedAuthAlgorithms, WifiConfiguration.AuthAlgorithm.strings);
if (config.allowedAuthAlgorithms.cardinality() != 0 &&
!mWifiNative.setNetworkVariable(
netId,
WifiConfiguration.AuthAlgorithm.varName,
allowedAuthAlgorithmsString)) {
loge("failed to set auth_alg: "+
allowedAuthAlgorithmsString);
break setVariables;
}
String allowedPairwiseCiphersString =
makeString(config.allowedPairwiseCiphers,
WifiConfiguration.PairwiseCipher.strings);
if (config.allowedPairwiseCiphers.cardinality() != 0 &&
!mWifiNative.setNetworkVariable(
netId,
WifiConfiguration.PairwiseCipher.varName,
allowedPairwiseCiphersString)) {
loge("failed to set pairwise: "+
allowedPairwiseCiphersString);
break setVariables;
}
String allowedGroupCiphersString =
makeString(config.allowedGroupCiphers, WifiConfiguration.GroupCipher.strings);
if (config.allowedGroupCiphers.cardinality() != 0 &&
!mWifiNative.setNetworkVariable(
netId,
WifiConfiguration.GroupCipher.varName,
allowedGroupCiphersString)) {
loge("failed to set group: "+
allowedGroupCiphersString);
break setVariables;
}
// Prevent client screw-up by passing in a WifiConfiguration we gave it
// by preventing "*" as a key.
if (config.preSharedKey != null && !config.preSharedKey.equals("*") &&
!mWifiNative.setNetworkVariable(
netId,
WifiConfiguration.pskVarName,
config.preSharedKey)) {
loge("failed to set psk");
break setVariables;
}
boolean hasSetKey = false;
if (config.wepKeys != null) {
for (int i = 0; i < config.wepKeys.length; i++) {
// Prevent client screw-up by passing in a WifiConfiguration we gave it
// by preventing "*" as a key.
if (config.wepKeys[i] != null && !config.wepKeys[i].equals("*")) {
if (!mWifiNative.setNetworkVariable(
netId,
WifiConfiguration.wepKeyVarNames[i],
config.wepKeys[i])) {
loge("failed to set wep_key" + i + ": " + config.wepKeys[i]);
break setVariables;
}
hasSetKey = true;
}
}
}
if (hasSetKey) {
if (!mWifiNative.setNetworkVariable(
netId,
WifiConfiguration.wepTxKeyIdxVarName,
Integer.toString(config.wepTxKeyIndex))) {
loge("failed to set wep_tx_keyidx: " + config.wepTxKeyIndex);
break setVariables;
}
}
if (!mWifiNative.setNetworkVariable(
netId,
WifiConfiguration.priorityVarName,
Integer.toString(config.priority))) {
loge(config.SSID + ": failed to set priority: "
+config.priority);
break setVariables;
}
if (config.hiddenSSID && !mWifiNative.setNetworkVariable(
netId,
WifiConfiguration.hiddenSSIDVarName,
Integer.toString(config.hiddenSSID ? 1 : 0))) {
loge(config.SSID + ": failed to set hiddenSSID: "+
config.hiddenSSID);
break setVariables;
}
if (config.enterpriseConfig != null &&
config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE) {
WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
if (enterpriseConfig.needsKeyStore()) {
/**
* Keyguard settings may eventually be controlled by device policy.
* We check here if keystore is unlocked before installing
* credentials.
* TODO: Figure a way to store these credentials for wifi alone
* TODO: Do we need a dialog here ?
*/
if (mKeyStore.state() != KeyStore.State.UNLOCKED) {
loge(config.SSID + ": key store is locked");
break setVariables;
}
try {
/* config passed may include only fields being updated.
* In order to generate the key id, fetch uninitialized
* fields from the currently tracked configuration
*/
WifiConfiguration currentConfig = mConfiguredNetworks.get(netId);
String keyId = config.getKeyIdForCredentials(currentConfig);
if (!enterpriseConfig.installKeys(mKeyStore, keyId)) {
loge(config.SSID + ": failed to install keys");
break setVariables;
}
} catch (IllegalStateException e) {
loge(config.SSID + " invalid config for key installation");
break setVariables;
}
}
HashMap<String, String> enterpriseFields = enterpriseConfig.getFields();
for (String key : enterpriseFields.keySet()) {
String value = enterpriseFields.get(key);
if (!mWifiNative.setNetworkVariable(
netId,
key,
value)) {
enterpriseConfig.removeKeys(mKeyStore);
loge(config.SSID + ": failed to set " + key +
": " + value);
break setVariables;
}
}
}
updateFailed = false;
} //end of setVariables
if (updateFailed) {
if (newNetwork) {
mWifiNative.removeNetwork(netId);
loge("Failed to set a network variable, removed network: " + netId);
}
return new NetworkUpdateResult(INVALID_NETWORK_ID);
}
/* An update of the network variables requires reading them
* back from the supplicant to update mConfiguredNetworks.
* This is because some of the variables (SSID, wep keys &
* passphrases) reflect different values when read back than
* when written. For example, wep key is stored as * irrespective
* of the value sent to the supplicant
*/
WifiConfiguration currentConfig = mConfiguredNetworks.get(netId);
if (currentConfig == null) {
currentConfig = new WifiConfiguration();
currentConfig.ipAssignment = IpAssignment.DHCP;
currentConfig.proxySettings = ProxySettings.NONE;
currentConfig.networkId = netId;
}
readNetworkVariables(currentConfig);
mConfiguredNetworks.put(netId, currentConfig);
mNetworkIds.put(configKey(currentConfig), netId);
NetworkUpdateResult result = writeIpAndProxyConfigurationsOnChange(currentConfig, config);
result.setIsNewNetwork(newNetwork);
result.setNetworkId(netId);
return result;
}
/* Compare current and new configuration and write to file on change */
private NetworkUpdateResult writeIpAndProxyConfigurationsOnChange(
WifiConfiguration currentConfig,
WifiConfiguration newConfig) {
boolean ipChanged = false;
boolean proxyChanged = false;
LinkProperties linkProperties = null;
switch (newConfig.ipAssignment) {
case STATIC:
Collection<LinkAddress> currentLinkAddresses = currentConfig.linkProperties
.getLinkAddresses();
Collection<LinkAddress> newLinkAddresses = newConfig.linkProperties
.getLinkAddresses();
Collection<InetAddress> currentDnses = currentConfig.linkProperties.getDnses();
Collection<InetAddress> newDnses = newConfig.linkProperties.getDnses();
Collection<RouteInfo> currentRoutes = currentConfig.linkProperties.getRoutes();
Collection<RouteInfo> newRoutes = newConfig.linkProperties.getRoutes();
boolean linkAddressesDiffer =
(currentLinkAddresses.size() != newLinkAddresses.size()) ||
!currentLinkAddresses.containsAll(newLinkAddresses);
boolean dnsesDiffer = (currentDnses.size() != newDnses.size()) ||
!currentDnses.containsAll(newDnses);
boolean routesDiffer = (currentRoutes.size() != newRoutes.size()) ||
!currentRoutes.containsAll(newRoutes);
if ((currentConfig.ipAssignment != newConfig.ipAssignment) ||
linkAddressesDiffer ||
dnsesDiffer ||
routesDiffer) {
ipChanged = true;
}
break;
case DHCP:
if (currentConfig.ipAssignment != newConfig.ipAssignment) {
ipChanged = true;
}
break;
case UNASSIGNED:
/* Ignore */
break;
default:
loge("Ignore invalid ip assignment during write");
break;
}
switch (newConfig.proxySettings) {
case STATIC:
ProxyProperties newHttpProxy = newConfig.linkProperties.getHttpProxy();
ProxyProperties currentHttpProxy = currentConfig.linkProperties.getHttpProxy();
if (newHttpProxy != null) {
proxyChanged = !newHttpProxy.equals(currentHttpProxy);
} else {
proxyChanged = (currentHttpProxy != null);
}
break;
case NONE:
if (currentConfig.proxySettings != newConfig.proxySettings) {
proxyChanged = true;
}
break;
case UNASSIGNED:
/* Ignore */
break;
default:
loge("Ignore invalid proxy configuration during write");
break;
}
if (!ipChanged) {
linkProperties = copyIpSettingsFromConfig(currentConfig);
} else {
currentConfig.ipAssignment = newConfig.ipAssignment;
linkProperties = copyIpSettingsFromConfig(newConfig);
log("IP config changed SSID = " + currentConfig.SSID + " linkProperties: " +
linkProperties.toString());
}
if (!proxyChanged) {
linkProperties.setHttpProxy(currentConfig.linkProperties.getHttpProxy());
} else {
currentConfig.proxySettings = newConfig.proxySettings;
linkProperties.setHttpProxy(newConfig.linkProperties.getHttpProxy());
log("proxy changed SSID = " + currentConfig.SSID);
if (linkProperties.getHttpProxy() != null) {
log(" proxyProperties: " + linkProperties.getHttpProxy().toString());
}
}
if (ipChanged || proxyChanged) {
currentConfig.linkProperties = linkProperties;
writeIpAndProxyConfigurations();
sendConfiguredNetworksChangedBroadcast(currentConfig,
WifiManager.CHANGE_REASON_CONFIG_CHANGE);
}
return new NetworkUpdateResult(ipChanged, proxyChanged);
}
private LinkProperties copyIpSettingsFromConfig(WifiConfiguration config) {
LinkProperties linkProperties = new LinkProperties();
linkProperties.setInterfaceName(config.linkProperties.getInterfaceName());
for (LinkAddress linkAddr : config.linkProperties.getLinkAddresses()) {
linkProperties.addLinkAddress(linkAddr);
}
for (RouteInfo route : config.linkProperties.getRoutes()) {
linkProperties.addRoute(route);
}
for (InetAddress dns : config.linkProperties.getDnses()) {
linkProperties.addDns(dns);
}
return linkProperties;
}
/**
* Read the variables from the supplicant daemon that are needed to
* fill in the WifiConfiguration object.
*
* @param config the {@link WifiConfiguration} object to be filled in.
*/
private void readNetworkVariables(WifiConfiguration config) {
int netId = config.networkId;
if (netId < 0)
return;
/*
* TODO: maybe should have a native method that takes an array of
* variable names and returns an array of values. But we'd still
* be doing a round trip to the supplicant daemon for each variable.
*/
String value;
value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.ssidVarName);
if (!TextUtils.isEmpty(value)) {
if (value.charAt(0) != '"') {
config.SSID = "\"" + WifiSsid.createFromHex(value).toString() + "\"";
//TODO: convert a hex string that is not UTF-8 decodable to a P-formatted
//supplicant string
} else {
config.SSID = value;
}
} else {
config.SSID = null;
}
value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.bssidVarName);
if (!TextUtils.isEmpty(value)) {
config.BSSID = value;
} else {
config.BSSID = null;
}
value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.priorityVarName);
config.priority = -1;
if (!TextUtils.isEmpty(value)) {
try {
config.priority = Integer.parseInt(value);
} catch (NumberFormatException ignore) {
}
}
value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.hiddenSSIDVarName);
config.hiddenSSID = false;
if (!TextUtils.isEmpty(value)) {
try {
config.hiddenSSID = Integer.parseInt(value) != 0;
} catch (NumberFormatException ignore) {
}
}
value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.wepTxKeyIdxVarName);
config.wepTxKeyIndex = -1;
if (!TextUtils.isEmpty(value)) {
try {
config.wepTxKeyIndex = Integer.parseInt(value);
} catch (NumberFormatException ignore) {
}
}
for (int i = 0; i < 4; i++) {
value = mWifiNative.getNetworkVariable(netId,
WifiConfiguration.wepKeyVarNames[i]);
if (!TextUtils.isEmpty(value)) {
config.wepKeys[i] = value;
} else {
config.wepKeys[i] = null;
}
}
value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.pskVarName);
if (!TextUtils.isEmpty(value)) {
config.preSharedKey = value;
} else {
config.preSharedKey = null;
}
value = mWifiNative.getNetworkVariable(config.networkId,
WifiConfiguration.Protocol.varName);
if (!TextUtils.isEmpty(value)) {
String vals[] = value.split(" ");
for (String val : vals) {
int index =
lookupString(val, WifiConfiguration.Protocol.strings);
if (0 <= index) {
config.allowedProtocols.set(index);
}
}
}
value = mWifiNative.getNetworkVariable(config.networkId,
WifiConfiguration.KeyMgmt.varName);
if (!TextUtils.isEmpty(value)) {
String vals[] = value.split(" ");
for (String val : vals) {
int index =
lookupString(val, WifiConfiguration.KeyMgmt.strings);
if (0 <= index) {
config.allowedKeyManagement.set(index);
}
}
}
value = mWifiNative.getNetworkVariable(config.networkId,
WifiConfiguration.AuthAlgorithm.varName);
if (!TextUtils.isEmpty(value)) {
String vals[] = value.split(" ");
for (String val : vals) {
int index =
lookupString(val, WifiConfiguration.AuthAlgorithm.strings);
if (0 <= index) {
config.allowedAuthAlgorithms.set(index);
}
}
}
value = mWifiNative.getNetworkVariable(config.networkId,
WifiConfiguration.PairwiseCipher.varName);
if (!TextUtils.isEmpty(value)) {
String vals[] = value.split(" ");
for (String val : vals) {
int index =
lookupString(val, WifiConfiguration.PairwiseCipher.strings);
if (0 <= index) {
config.allowedPairwiseCiphers.set(index);
}
}
}
value = mWifiNative.getNetworkVariable(config.networkId,
WifiConfiguration.GroupCipher.varName);
if (!TextUtils.isEmpty(value)) {
String vals[] = value.split(" ");
for (String val : vals) {
int index =
lookupString(val, WifiConfiguration.GroupCipher.strings);
if (0 <= index) {
config.allowedGroupCiphers.set(index);
}
}
}
if (config.enterpriseConfig == null) {
config.enterpriseConfig = new WifiEnterpriseConfig();
}
HashMap<String, String> enterpriseFields = config.enterpriseConfig.getFields();
for (String key : WifiEnterpriseConfig.getSupplicantKeys()) {
value = mWifiNative.getNetworkVariable(netId, key);
if (!TextUtils.isEmpty(value)) {
enterpriseFields.put(key, removeDoubleQuotes(value));
} else {
enterpriseFields.put(key, WifiEnterpriseConfig.EMPTY_VALUE);
}
}
if (config.enterpriseConfig.migrateOldEapTlsNative(mWifiNative, netId)) {
saveConfig();
}
config.enterpriseConfig.migrateCerts(mKeyStore);
}
private String removeDoubleQuotes(String string) {
int length = string.length();
if ((length > 1) && (string.charAt(0) == '"')
&& (string.charAt(length - 1) == '"')) {
return string.substring(1, length - 1);
}
return string;
}
private String convertToQuotedString(String string) {
return "\"" + string + "\"";
}
private String makeString(BitSet set, String[] strings) {
StringBuffer buf = new StringBuffer();
int nextSetBit = -1;
/* Make sure all set bits are in [0, strings.length) to avoid
* going out of bounds on strings. (Shouldn't happen, but...) */
set = set.get(0, strings.length);
while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) {
buf.append(strings[nextSetBit].replace('_', '-')).append(' ');
}
// remove trailing space
if (set.cardinality() > 0) {
buf.setLength(buf.length() - 1);
}
return buf.toString();
}
private int lookupString(String string, String[] strings) {
int size = strings.length;
string = string.replace('-', '_');
for (int i = 0; i < size; i++)
if (string.equals(strings[i]))
return i;
// if we ever get here, we should probably add the
// value to WifiConfiguration to reflect that it's
// supported by the WPA supplicant
loge("Failed to look-up a string: " + string);
return -1;
}
/* Returns a unique for a given configuration */
private static int configKey(WifiConfiguration config) {
String key;
if (config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
key = config.SSID + KeyMgmt.strings[KeyMgmt.WPA_PSK];
} else if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP) ||
config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
key = config.SSID + KeyMgmt.strings[KeyMgmt.WPA_EAP];
} else if (config.wepKeys[0] != null) {
key = config.SSID + "WEP";
} else {
key = config.SSID + KeyMgmt.strings[KeyMgmt.NONE];
}
return key.hashCode();
}
void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("WifiConfigStore");
pw.println("mLastPriority " + mLastPriority);
pw.println("Configured networks");
for (WifiConfiguration conf : getConfiguredNetworks()) {
pw.println(conf);
}
pw.println();
}
public String getConfigFile() {
return ipConfigFile;
}
private void loge(String s) {
Log.e(TAG, s);
}
private void log(String s) {
Log.d(TAG, s);
}
}