diff options
8 files changed, 1137 insertions, 200 deletions
diff --git a/service/java/com/android/server/wifi/ConfigurationMap.java b/service/java/com/android/server/wifi/ConfigurationMap.java index 837c5799c..809952014 100644 --- a/service/java/com/android/server/wifi/ConfigurationMap.java +++ b/service/java/com/android/server/wifi/ConfigurationMap.java @@ -1,6 +1,7 @@ package com.android.server.wifi; import android.net.wifi.WifiConfiguration; +import android.os.UserHandle; import java.util.ArrayList; import java.util.Collection; @@ -12,14 +13,21 @@ import java.util.Map; public class ConfigurationMap { private final Map<Integer, WifiConfiguration> mPerID = new HashMap<>(); private final Map<Integer, WifiConfiguration> mPerConfigKey = new HashMap<>(); - private final Map<String, Integer> mPerFQDN = new HashMap<>(); + + private final Map<Integer, WifiConfiguration> mPerIDForCurrentUser = new HashMap<>(); + private final Map<String, WifiConfiguration> mPerFQDNForCurrentUser = new HashMap<>(); + + private int mCurrentUserId = UserHandle.USER_SYSTEM; // RW methods: - public WifiConfiguration put(int netid, WifiConfiguration config) { - WifiConfiguration current = mPerID.put(netid, config); + public WifiConfiguration put(WifiConfiguration config) { + final WifiConfiguration current = mPerID.put(config.networkId, config); mPerConfigKey.put(config.configKey().hashCode(), config); // This is ridiculous... - if (config.FQDN != null && config.FQDN.length() > 0) { - mPerFQDN.put(config.FQDN, netid); + if (config.isVisibleToUser(mCurrentUserId)) { + mPerIDForCurrentUser.put(config.networkId, config); + if (config.FQDN != null && config.FQDN.length() > 0) { + mPerFQDNForCurrentUser.put(config.FQDN, config); + } } return current; } @@ -31,9 +39,11 @@ public class ConfigurationMap { } mPerConfigKey.remove(config.configKey().hashCode()); - Iterator<Map.Entry<String, Integer>> entries = mPerFQDN.entrySet().iterator(); + mPerIDForCurrentUser.remove(netID); + Iterator<Map.Entry<String, WifiConfiguration>> entries = + mPerFQDNForCurrentUser.entrySet().iterator(); while (entries.hasNext()) { - if (entries.next().getValue() == netID) { + if (entries.next().getValue().networkId == netID) { entries.remove(); break; } @@ -44,32 +54,67 @@ public class ConfigurationMap { public void clear() { mPerID.clear(); mPerConfigKey.clear(); - mPerFQDN.clear(); + mPerIDForCurrentUser.clear(); + mPerFQDNForCurrentUser.clear(); + } + + /** + * Handles the switch to a different foreground user: + * - Hides private network configurations belonging to the previous foreground user + * - Reveals private network configurations belonging to the new foreground user + * + * @param userId the id of the new foreground user + * @return a list of {@link WifiConfiguration}s that became hidden because of the user switch + */ + public List<WifiConfiguration> handleUserSwitch(int userId) { + mPerIDForCurrentUser.clear(); + mPerFQDNForCurrentUser.clear(); + + final int previousUserId = mCurrentUserId; + mCurrentUserId = userId; + + final List<WifiConfiguration> hiddenConfigurations = new ArrayList<>(); + for (Map.Entry<Integer, WifiConfiguration> entry : mPerID.entrySet()) { + final WifiConfiguration config = entry.getValue(); + if (config.isVisibleToUser(mCurrentUserId)) { + mPerIDForCurrentUser.put(entry.getKey(), config); + if (config.FQDN != null && config.FQDN.length() > 0) { + mPerFQDNForCurrentUser.put(config.FQDN, config); + } + } else if (config.isVisibleToUser(previousUserId)) { + hiddenConfigurations.add(config); + } + } + + return hiddenConfigurations; } // RO methods: - public WifiConfiguration get(int netid) { + public WifiConfiguration getForAllUsers(int netid) { return mPerID.get(netid); } - public int size() { + public WifiConfiguration getForCurrentUser(int netid) { + return mPerIDForCurrentUser.get(netid); + } + + public int sizeForAllUsers() { return mPerID.size(); } - public boolean isEmpty() { - return mPerID.size() == 0; + public int sizeForCurrentUser() { + return mPerIDForCurrentUser.size(); } - public WifiConfiguration getByFQDN(String fqdn) { - Integer id = mPerFQDN.get(fqdn); - return id != null ? mPerID.get(id) : null; + public WifiConfiguration getByFQDNForCurrentUser(String fqdn) { + return mPerFQDNForCurrentUser.get(fqdn); } - public WifiConfiguration getByConfigKey(String key) { + public WifiConfiguration getByConfigKeyForCurrentUser(String key) { if (key == null) { return null; } - for (WifiConfiguration config : mPerID.values()) { + for (WifiConfiguration config : mPerIDForCurrentUser.values()) { if (config.configKey().equals(key)) { return config; } @@ -77,13 +122,13 @@ public class ConfigurationMap { return null; } - public WifiConfiguration getByConfigKeyID(int id) { + public WifiConfiguration getByConfigKeyIDForAllUsers(int id) { return mPerConfigKey.get(id); } - public Collection<WifiConfiguration> getEnabledNetworks() { + public Collection<WifiConfiguration> getEnabledNetworksForCurrentUser() { List<WifiConfiguration> list = new ArrayList<>(); - for (WifiConfiguration config : mPerID.values()) { + for (WifiConfiguration config : mPerIDForCurrentUser.values()) { if (config.status != WifiConfiguration.Status.DISABLED) { list.add(config); } @@ -91,8 +136,8 @@ public class ConfigurationMap { return list; } - public WifiConfiguration getEphemeral(String ssid) { - for (WifiConfiguration config : mPerID.values()) { + public WifiConfiguration getEphemeralForCurrentUser(String ssid) { + for (WifiConfiguration config : mPerIDForCurrentUser.values()) { if (ssid.equals(config.SSID) && config.ephemeral) { return config; } @@ -100,7 +145,11 @@ public class ConfigurationMap { return null; } - public Collection<WifiConfiguration> values() { + public Collection<WifiConfiguration> valuesForAllUsers() { return mPerID.values(); } + + public Collection<WifiConfiguration> valuesForCurrentUser() { + return mPerIDForCurrentUser.values(); + } } diff --git a/service/java/com/android/server/wifi/WifiConfigStore.java b/service/java/com/android/server/wifi/WifiConfigStore.java index 35f146e1a..13218a454 100644 --- a/service/java/com/android/server/wifi/WifiConfigStore.java +++ b/service/java/com/android/server/wifi/WifiConfigStore.java @@ -215,7 +215,7 @@ public class WifiConfigStore extends IpConfigStore { /* Network History Keys */ private static final String SSID_KEY = "SSID"; - private static final String CONFIG_KEY = "CONFIG"; + static final String CONFIG_KEY = "CONFIG"; private static final String CHOICE_KEY = "CHOICE"; private static final String LINK_KEY = "LINK"; private static final String BSSID_KEY = "BSSID"; @@ -235,7 +235,7 @@ public class WifiConfigStore extends IpConfigStore { private static final String FAILURE_KEY = "FAILURE"; private static final String DID_SELF_ADD_KEY = "DID_SELF_ADD"; private static final String PEER_CONFIGURATION_KEY = "PEER_CONFIGURATION"; - private static final String CREATOR_UID_KEY = "CREATOR_UID_KEY"; + static final String CREATOR_UID_KEY = "CREATOR_UID_KEY"; private static final String CONNECT_UID_KEY = "CONNECT_UID_KEY"; private static final String UPDATE_UID_KEY = "UPDATE_UID"; private static final String SUPPLICANT_STATUS_KEY = "SUP_STATUS"; @@ -254,6 +254,7 @@ public class WifiConfigStore extends IpConfigStore { private static final String USER_APPROVED_KEY = "USER_APPROVED"; private static final String CREATION_TIME_KEY = "CREATION_TIME"; private static final String UPDATE_TIME_KEY = "UPDATE_TIME"; + static final String SHARED_KEY = "SHARED"; private static final String SEPARATOR = ": "; private static final String NL = "\n"; @@ -739,13 +740,13 @@ public class WifiConfigStore extends IpConfigStore { } int getConfiguredNetworksSize() { - return mConfiguredNetworks.size(); + return mConfiguredNetworks.sizeForCurrentUser(); } private List<WifiConfiguration> getConfiguredNetworks(Map<String, String> pskMap) { List<WifiConfiguration> networks = new ArrayList<>(); - for(WifiConfiguration config : mConfiguredNetworks.values()) { + for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) { WifiConfiguration newConfig = new WifiConfiguration(config); // When updating this condition, update WifiStateMachine's CONNECT_NETWORK handler to // correctly handle updating existing configs that are filtered out here. @@ -773,7 +774,7 @@ public class WifiConfigStore extends IpConfigStore { private List<WifiConfiguration> getAllConfiguredNetworks() { List<WifiConfiguration> networks = new ArrayList<>(); - for(WifiConfiguration config : mConfiguredNetworks.values()) { + for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) { WifiConfiguration newConfig = new WifiConfiguration(config); networks.add(newConfig); } @@ -832,7 +833,7 @@ public class WifiConfigStore extends IpConfigStore { List<WifiConfiguration> getRecentConfiguredNetworks(int milli, boolean copy) { List<WifiConfiguration> networks = new ArrayList<WifiConfiguration>(); - for (WifiConfiguration config : mConfiguredNetworks.values()) { + for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) { if (config.ephemeral) { // Do not enumerate and return this configuration to any one, // instead treat it as unknown. the configuration can still be retrieved @@ -897,7 +898,7 @@ public class WifiConfigStore extends IpConfigStore { * @return Wificonfiguration */ public WifiConfiguration getWifiConfiguration(int netId) { - return mConfiguredNetworks.get(netId); + return mConfiguredNetworks.getForCurrentUser(netId); } /** @@ -905,7 +906,7 @@ public class WifiConfigStore extends IpConfigStore { * @return Wificonfiguration */ public WifiConfiguration getWifiConfiguration(String key) { - return mConfiguredNetworks.getByConfigKey(key); + return mConfiguredNetworks.getByConfigKeyForCurrentUser(key); } /** @@ -915,7 +916,7 @@ public class WifiConfigStore extends IpConfigStore { void enableAllNetworks() { boolean networkEnabledStateChanged = false; - for (WifiConfiguration config : mConfiguredNetworks.values()) { + for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) { if (config != null && !config.ephemeral && !config.getNetworkSelectionStatus().isNetworkEnabled()) { if (tryEnableQualifiedNetwork(config)) { @@ -966,10 +967,15 @@ public class WifiConfigStore extends IpConfigStore { boolean selectNetwork(WifiConfiguration config, boolean updatePriorities, int uid) { if (VDBG) localLogNetwork("selectNetwork", config.networkId); if (config.networkId == INVALID_NETWORK_ID) return false; + if (!config.isVisibleToUser(mWifiStateMachine.getCurrentUserId())) { + loge("selectNetwork " + Integer.toString(config.networkId) + ": Network config is not " + + "visible to current user."); + return false; + } // Reset the priority of each network at start or if it goes too high. if (mLastPriority == -1 || mLastPriority > 1000000) { - for(WifiConfiguration config2 : mConfiguredNetworks.values()) { + for (WifiConfiguration config2 : mConfiguredNetworks.valuesForCurrentUser()) { if (updatePriorities) { if (config2.networkId != INVALID_NETWORK_ID) { config2.priority = 0; @@ -1036,9 +1042,16 @@ public class WifiConfigStore extends IpConfigStore { config.SSID == null)) { return new NetworkUpdateResult(INVALID_NETWORK_ID); } + + if (!config.isVisibleToUser(mWifiStateMachine.getCurrentUserId())) { + return new NetworkUpdateResult(INVALID_NETWORK_ID); + } + if (VDBG) localLogNetwork("WifiConfigStore: saveNetwork netId", config.networkId); if (VDBG) { - loge("WifiConfigStore saveNetwork, size=" + mConfiguredNetworks.size() + logd("WifiConfigStore saveNetwork," + + " size=" + Integer.toString(mConfiguredNetworks.sizeForAllUsers()) + + " (for all users)" + " SSID=" + config.SSID + " Uid=" + Integer.toString(config.creatorUid) + "/" + Integer.toString(config.lastUpdateUid)); @@ -1063,12 +1076,12 @@ public class WifiConfigStore extends IpConfigStore { if (VDBG) localLogNetwork("WifiConfigStore: will enable netId=", netId); mWifiNative.enableNetwork(netId, false); - conf = mConfiguredNetworks.get(netId); + conf = mConfiguredNetworks.getForCurrentUser(netId); if (conf != null) conf.status = Status.ENABLED; } - conf = mConfiguredNetworks.get(netId); + conf = mConfiguredNetworks.getForCurrentUser(netId); if (conf != null) { if (!conf.getNetworkSelectionStatus().isNetworkEnabled()) { if (VDBG) localLog("WifiConfigStore: re-enabling: " + conf.SSID); @@ -1162,7 +1175,7 @@ public class WifiConfigStore extends IpConfigStore { void updateStatus(int netId, DetailedState state) { if (netId != INVALID_NETWORK_ID) { - WifiConfiguration config = mConfiguredNetworks.get(netId); + WifiConfiguration config = mConfiguredNetworks.getForAllUsers(netId); if (config == null) return; switch (state) { case CONNECTED: @@ -1201,7 +1214,7 @@ public class WifiConfigStore extends IpConfigStore { return null; } - WifiConfiguration foundConfig = mConfiguredNetworks.getEphemeral(SSID); + WifiConfiguration foundConfig = mConfiguredNetworks.getEphemeralForCurrentUser(SSID); mDeletedEphemeralSSIDs.add(SSID); loge("Forget ephemeral SSID " + SSID + " num=" + mDeletedEphemeralSSIDs.size()); @@ -1226,7 +1239,8 @@ public class WifiConfigStore extends IpConfigStore { boolean forgetNetwork(int netId) { if (showNetworks) localLogNetwork("forgetNetwork", netId); - WifiConfiguration config = mConfiguredNetworks.get(netId); + WifiConfiguration config = mConfiguredNetworks.getForCurrentUser(netId); + boolean remove = removeConfigAndSendBroadcastIfNeeded(netId); if (!remove) { //success but we dont want to remove the network from supplicant conf file @@ -1255,8 +1269,11 @@ public class WifiConfigStore extends IpConfigStore { * @return network Id */ int addOrUpdateNetwork(WifiConfiguration config, int uid) { - if (showNetworks) localLogNetwork("addOrUpdateNetwork id=", config.networkId); + if (config == null || !config.isVisibleToUser(mWifiStateMachine.getCurrentUserId())) { + return WifiConfiguration.INVALID_NETWORK_ID; + } + if (showNetworks) localLogNetwork("addOrUpdateNetwork id=", config.networkId); if (config.isPasspoint()) { /* create a temporary SSID with providerFriendlyName */ Long csum = getChecksum(config.FQDN); @@ -1266,7 +1283,7 @@ public class WifiConfigStore extends IpConfigStore { NetworkUpdateResult result = addOrUpdateNetworkNative(config, uid); if (result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID) { - WifiConfiguration conf = mConfiguredNetworks.get(result.getNetworkId()); + WifiConfiguration conf = mConfiguredNetworks.getForCurrentUser(result.getNetworkId()); if (conf != null) { sendConfiguredNetworksChangedBroadcast(conf, result.isNewNetwork ? WifiManager.CHANGE_REASON_ADDED : @@ -1380,7 +1397,11 @@ public class WifiConfigStore extends IpConfigStore { */ boolean removeNetwork(int netId) { if (showNetworks) localLogNetwork("removeNetwork", netId); - WifiConfiguration config = mConfiguredNetworks.get(netId); + WifiConfiguration config = mConfiguredNetworks.getForCurrentUser(netId); + if (config == null) { + return false; + } + boolean ret = mWifiNative.removeNetwork(netId); if (ret) { removeConfigAndSendBroadcastIfNeeded(netId); @@ -1399,7 +1420,7 @@ public class WifiConfigStore extends IpConfigStore { } private boolean removeConfigAndSendBroadcastIfNeeded(int netId) { - WifiConfiguration config = mConfiguredNetworks.get(netId); + WifiConfiguration config = mConfiguredNetworks.getForCurrentUser(netId); if (config != null) { if (VDBG) { loge("removeNetwork " + Integer.toString(netId) + " key=" + @@ -1459,7 +1480,7 @@ public class WifiConfigStore extends IpConfigStore { boolean success = true; WifiConfiguration [] copiedConfigs = - mConfiguredNetworks.values().toArray(new WifiConfiguration[0]); + mConfiguredNetworks.valuesForCurrentUser().toArray(new WifiConfiguration[0]); for (WifiConfiguration config : copiedConfigs) { if (app.uid != config.creatorUid || !app.packageName.equals(config.creatorName)) { continue; @@ -1481,7 +1502,7 @@ public class WifiConfigStore extends IpConfigStore { boolean success = true; WifiConfiguration[] copiedConfigs = - mConfiguredNetworks.values().toArray(new WifiConfiguration[0]); + mConfiguredNetworks.valuesForAllUsers().toArray(new WifiConfiguration[0]); for (WifiConfiguration config : copiedConfigs) { if (userId != UserHandle.getUserId(config.creatorUid)) { continue; @@ -1506,6 +1527,11 @@ public class WifiConfigStore extends IpConfigStore { * @return {@code true} if it succeeds, {@code false} otherwise */ boolean enableNetwork(int netId, boolean disableOthers, int uid) { + WifiConfiguration config = mConfiguredNetworks.getForCurrentUser(netId); + if (config == null) { + return false; + } + boolean ret = enableNetworkWithoutBroadcast(netId, disableOthers); if (disableOthers) { if (VDBG) localLogNetwork("enableNetwork(disableOthers=true, uid=" + uid + ") ", netId); @@ -1516,7 +1542,7 @@ public class WifiConfigStore extends IpConfigStore { if (VDBG) localLogNetwork("enableNetwork(disableOthers=false) ", netId); WifiConfiguration enabledNetwork; synchronized(mConfiguredNetworks) { // !!! Useless synchronization! - enabledNetwork = mConfiguredNetworks.get(netId); + enabledNetwork = mConfiguredNetworks.getForCurrentUser(netId); } // check just in case the network was removed by someone else. if (enabledNetwork != null) { @@ -1528,10 +1554,14 @@ public class WifiConfigStore extends IpConfigStore { } boolean enableNetworkWithoutBroadcast(int netId, boolean disableOthers) { + final WifiConfiguration config = mConfiguredNetworks.getForCurrentUser(netId); + if (config == null) { + return false; + } + boolean ret = mWifiNative.enableNetwork(netId, disableOthers); - WifiConfiguration config = mConfiguredNetworks.get(netId); - if (config != null) config.status = Status.ENABLED; + config.status = Status.ENABLED; if (disableOthers) { markAllNetworksDisabledExcept(netId); @@ -1542,7 +1572,7 @@ public class WifiConfigStore extends IpConfigStore { void disableAllNetworks() { if (VDBG) localLog("disableAllNetworks"); boolean networkDisabled = false; - for (WifiConfiguration enabled : mConfiguredNetworks.getEnabledNetworks()) { + for (WifiConfiguration enabled : mConfiguredNetworks.getEnabledNetworksForCurrentUser()) { if(mWifiNative.disableNetwork(enabled.networkId)) { networkDisabled = true; enabled.status = Status.DISABLED; @@ -1845,7 +1875,7 @@ public class WifiConfigStore extends IpConfigStore { * Fetch the static IP configuration for a given network id */ StaticIpConfiguration getStaticIpConfiguration(int netId) { - WifiConfiguration config = mConfiguredNetworks.get(netId); + WifiConfiguration config = mConfiguredNetworks.getForCurrentUser(netId); if (config != null) { return config.getStaticIpConfiguration(); } @@ -1856,7 +1886,7 @@ public class WifiConfigStore extends IpConfigStore { * Set the static IP configuration for a given network id */ void setStaticIpConfiguration(int netId, StaticIpConfiguration staticIpConfiguration) { - WifiConfiguration config = mConfiguredNetworks.get(netId); + WifiConfiguration config = mConfiguredNetworks.getForCurrentUser(netId); if (config != null) { config.setStaticIpConfiguration(staticIpConfiguration); } @@ -1866,7 +1896,7 @@ public class WifiConfigStore extends IpConfigStore { * set default GW MAC address */ void setDefaultGwMacAddress(int netId, String macAddress) { - WifiConfiguration config = mConfiguredNetworks.get(netId); + WifiConfiguration config = mConfiguredNetworks.getForCurrentUser(netId); if (config != null) { //update defaultGwMacAddress config.defaultGwMacAddress = macAddress; @@ -1880,7 +1910,7 @@ public class WifiConfigStore extends IpConfigStore { * @return ProxyInfo for the network id */ ProxyInfo getProxyProperties(int netId) { - WifiConfiguration config = mConfiguredNetworks.get(netId); + WifiConfiguration config = mConfiguredNetworks.getForCurrentUser(netId); if (config != null) { return config.getHttpProxy(); } @@ -1893,7 +1923,7 @@ public class WifiConfigStore extends IpConfigStore { * @return {@code true} if using static ip for netId */ boolean isUsingStaticIp(int netId) { - WifiConfiguration config = mConfiguredNetworks.get(netId); + WifiConfiguration config = mConfiguredNetworks.getForCurrentUser(netId); if (config != null && config.getIpAssignment() == IpAssignment.STATIC) { return true; } @@ -1901,7 +1931,7 @@ public class WifiConfigStore extends IpConfigStore { } boolean isEphemeral(int netId) { - WifiConfiguration config = mConfiguredNetworks.get(netId); + WifiConfiguration config = mConfiguredNetworks.getForCurrentUser(netId); return config != null && config.ephemeral; } @@ -1935,7 +1965,7 @@ public class WifiConfigStore extends IpConfigStore { mLastPriority = 0; - mConfiguredNetworks.clear(); + final Map<String, WifiConfiguration> configs = new HashMap<>(); final SparseArray<Map<String, String>> networkExtras = new SparseArray<>(); @@ -1993,6 +2023,9 @@ public class WifiConfigStore extends IpConfigStore { config.networkId, ID_STRING_VAR_NAME)); if (fqdn != null) { extras.put(ID_STRING_KEY_FQDN, fqdn); + config.FQDN = fqdn; + // Mark the configuration as a Hotspot 2.0 network. + config.providerFriendlyName = ""; } } networkExtras.put(config.networkId, extras); @@ -2013,40 +2046,64 @@ public class WifiConfigStore extends IpConfigStore { config.setIpAssignment(IpAssignment.DHCP); config.setProxySettings(ProxySettings.NONE); - WifiConfiguration possibleOldConfig = - mConfiguredNetworks.getByConfigKey(config.configKey()); - if (possibleOldConfig != null) { - // That SSID is already known, just ignore this duplicate entry + if (!WifiServiceImpl.isValid(config)) { if (showNetworks) { - localLog("update duplicate network " + possibleOldConfig.networkId - + " with " + config.networkId); + localLog("Ignoring network " + config.networkId + " because configuration " + + "loaded from wpa_supplicant.conf is not valid."); } - // This can happen after the user manually connected to an AP and try to use WPS - // to connect the AP later.In this way, supplicant will create a new network for - // the AP although there is an existing network already. - mWifiNative.removeNetwork(possibleOldConfig.networkId); - mConfiguredNetworks.remove(possibleOldConfig.networkId); - mConfiguredNetworks.put(config.networkId, config); - - } else if(WifiServiceImpl.isValid(config)){ - mConfiguredNetworks.put(config.networkId, config); - if (showNetworks) { - localLogNetwork("loaded configured network", config.networkId); - } - } else { + continue; + } + + // The configKey is explicitly stored in wpa_supplicant.conf, because config does + // not contain sufficient information to compute it at this point. + String configKey = extras.get(ID_STRING_KEY_CONFIG_KEY); + if (configKey == null) { + // Handle the legacy case where the configKey is not stored in + // wpa_supplicant.conf but can be computed straight away. + configKey = config.configKey(); + } + + final WifiConfiguration duplicateConfig = configs.put(configKey, config); + if (duplicateConfig != null) { + // The network is already known. Overwrite the duplicate entry. if (showNetworks) { - log("Ignoring loaded configured for network " + config.networkId - + " because config are not valid"); + localLog("Replacing duplicate network " + duplicateConfig.networkId + + " with " + config.networkId + "."); } + // This can happen after the user manually connected to an AP and tried to use + // WPS to connect the AP later. In this case, the supplicant will create a new + // network for the AP although there is an existing network already. + mWifiNative.removeNetwork(duplicateConfig.networkId); } } done = (lines.length == 1); } - readPasspointConfig(networkExtras); + readNetworkHistory(configs); + readPasspointConfig(configs, networkExtras); + + // We are only now updating mConfiguredNetworks for two reasons: + // 1) The information required to compute configKeys is spread across wpa_supplicant.conf + // and networkHistory.txt. Thus, we had to load both files first. + // 2) mConfiguredNetworks caches a Passpoint network's FQDN the moment the network is added. + // Thus, we had to load the FQDNs first. + mConfiguredNetworks.clear(); + for (Map.Entry<String, WifiConfiguration> entry : configs.entrySet()) { + final String configKey = entry.getKey(); + final WifiConfiguration config = entry.getValue(); + if (!configKey.equals(config.configKey())) { + if (showNetworks) { + log("Ignoring network " + config.networkId + " because the configKey loaded " + + "from wpa_supplicant.conf is not valid."); + } + mWifiNative.removeNetwork(config.networkId); + continue; + } + mConfiguredNetworks.put(config); + } + readIpAndProxyConfigurations(); - readNetworkHistory(); readAutoJoinConfig(); buildPnoList(); @@ -2054,10 +2111,11 @@ public class WifiConfigStore extends IpConfigStore { sendConfiguredNetworksChangedBroadcast(); if (showNetworks) { - localLog("loadConfiguredNetworks loaded " + mConfiguredNetworks.size() + " networks"); + localLog("loadConfiguredNetworks loaded " + mConfiguredNetworks.sizeForAllUsers() + + " networks (for all users)"); } - if (mConfiguredNetworks.isEmpty()) { + if (mConfiguredNetworks.sizeForAllUsers() == 0) { // no networks? Lets log if the file contents logKernelTime(); logContents(SUPPLICANT_CONFIG_FILE); @@ -2093,6 +2151,12 @@ public class WifiConfigStore extends IpConfigStore { } private Map<String, String> readNetworkVariablesFromSupplicantFile(String key) { + // TODO(b/26733972): This method assumes that the SSID is a unique identifier for network + // configurations. That is wrong. There may be any number of networks with the same SSID. + // There may also be any number of network configurations for the same network. The correct + // unique identifier is the configKey. This method should be switched from SSID to configKey + // (which is either stored in wpa_supplicant.conf directly or can be computed from the + // information found in that file). Map<String, String> result = new HashMap<>(); BufferedReader reader = null; if (VDBG) loge("readNetworkVariablesFromSupplicantFile key=" + key); @@ -2159,7 +2223,7 @@ public class WifiConfigStore extends IpConfigStore { /* Mark all networks except specified netId as disabled */ private void markAllNetworksDisabledExcept(int netId) { - for(WifiConfiguration config : mConfiguredNetworks.values()) { + for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) { if(config != null && config.networkId != netId) { if (config.status != Status.DISABLED) { config.status = Status.DISABLED; @@ -2178,7 +2242,7 @@ public class WifiConfigStore extends IpConfigStore { // unlocked key store; unless the certificates can be stored with // hardware encryption - for(WifiConfiguration config : mConfiguredNetworks.values()) { + for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) { if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP) && config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) { @@ -2192,8 +2256,8 @@ public class WifiConfigStore extends IpConfigStore { return false; } - void readPasspointConfig(SparseArray<Map<String, String>> networkExtras) { - + void readPasspointConfig(Map<String, WifiConfiguration> configs, + SparseArray<Map<String, String>> networkExtras) { List<HomeSP> homeSPs; try { homeSPs = mMOManager.loadAllSPs(); @@ -2206,7 +2270,7 @@ public class WifiConfigStore extends IpConfigStore { for (HomeSP homeSp : homeSPs) { String fqdn = homeSp.getFQDN(); Log.d(TAG, "Looking for " + fqdn); - for (WifiConfiguration config : mConfiguredNetworks.values()) { + for (WifiConfiguration config : configs.values()) { Log.d(TAG, "Testing " + config.SSID); if (config.enterpriseConfig == null) { @@ -2231,8 +2295,6 @@ public class WifiConfigStore extends IpConfigStore { config.enterpriseConfig.setPlmn( imsiParameter != null ? imsiParameter.toString() : null); config.enterpriseConfig.setRealm(homeSp.getCredential().getRealm()); - // Allow mConfiguredNetworks to cache the FQDN. - mConfiguredNetworks.put(config.networkId, config); } } } @@ -2262,12 +2324,13 @@ public class WifiConfigStore extends IpConfigStore { /* Make a copy */ final List<WifiConfiguration> networks = new ArrayList<WifiConfiguration>(); - for (WifiConfiguration config : mConfiguredNetworks.values()) { + for (WifiConfiguration config : mConfiguredNetworks.valuesForAllUsers()) { networks.add(new WifiConfiguration(config)); } if (VDBG) { - loge(" writeKnownNetworkHistory() num networks:" + - mConfiguredNetworks.size() + " needWrite=" + needUpdate); + logd(" writeKnownNetworkHistory() num networks:" + + mConfiguredNetworks.sizeForAllUsers() + " (for all users) needWrite=" + + needUpdate); } if (needUpdate == false) { return; @@ -2378,6 +2441,7 @@ public class WifiConfigStore extends IpConfigStore { config.lastUpdateName + NL); out.writeUTF(USER_APPROVED_KEY + SEPARATOR + Integer.toString(config.userApproved) + NL); + out.writeUTF(SHARED_KEY + SEPARATOR + Boolean.toString(config.shared) + NL); String allowedKeyManagementString = makeString(config.allowedKeyManagement, WifiConfiguration.KeyMgmt.strings); @@ -2502,7 +2566,16 @@ public class WifiConfigStore extends IpConfigStore { && lastSelectedConfiguration.equals(config.configKey())); } - private void readNetworkHistory() { + /** + * Adds information stored in networkHistory.txt to the given configs. The configs are provided + * as a mapping from configKey to WifiConfiguration, because the WifiConfigurations themselves + * do not contain sufficient information to compute their configKeys until after the information + * that is stored in networkHistory.txt has been added to them. + * + * @param configs mapping from configKey to a WifiConfiguration that contains the information + * information read from wpa_supplicant.conf + */ + private void readNetworkHistory(Map<String, WifiConfiguration> configs) { if (showNetworks) { localLog("readNetworkHistory() path:" + networkHistoryConfigFile); } @@ -2535,7 +2608,8 @@ public class WifiConfigStore extends IpConfigStore { String value = line.substring(colon + 1).trim(); if (key.equals(CONFIG_KEY)) { - config = mConfiguredNetworks.getByConfigKey(value); + config = configs.get(value); + // skip reading that configuration data // since we don't have a corresponding network ID if (config == null) { @@ -2721,6 +2795,9 @@ public class WifiConfigStore extends IpConfigStore { case USER_APPROVED_KEY: config.userApproved = Integer.parseInt(value); break; + case SHARED_KEY: + config.shared = Boolean.parseBoolean(value); + break; } } } @@ -2771,7 +2848,7 @@ public class WifiConfigStore extends IpConfigStore { private void writeIpAndProxyConfigurations() { final SparseArray<IpConfiguration> networks = new SparseArray<IpConfiguration>(); - for(WifiConfiguration config : mConfiguredNetworks.values()) { + for (WifiConfiguration config : mConfiguredNetworks.valuesForAllUsers()) { if (!config.ephemeral) { networks.put(configKey(config), config.getIpConfiguration()); } @@ -2790,7 +2867,7 @@ public class WifiConfigStore extends IpConfigStore { for (int i = 0; i < networks.size(); i++) { int id = networks.keyAt(i); - WifiConfiguration config = mConfiguredNetworks.getByConfigKeyID(id); + WifiConfiguration config = mConfiguredNetworks.getByConfigKeyIDForAllUsers(id); // This is the only place the map is looked up through a (dangerous) hash-value! if (config == null || config.ephemeral) { @@ -2831,7 +2908,8 @@ public class WifiConfigStore extends IpConfigStore { boolean existingMO = false; // networkId of INVALID_NETWORK_ID means we want to create a new network if (netId == INVALID_NETWORK_ID) { - WifiConfiguration savedConfig = mConfiguredNetworks.getByConfigKey(config.configKey()); + WifiConfiguration savedConfig = + mConfiguredNetworks.getByConfigKeyForCurrentUser(config.configKey()); if (savedConfig != null) { netId = savedConfig.networkId; } else { @@ -3044,7 +3122,8 @@ public class WifiConfigStore extends IpConfigStore { * In order to generate the key id, fetch uninitialized * fields from the currently tracked configuration */ - WifiConfiguration currentConfig = mConfiguredNetworks.get(netId); + WifiConfiguration currentConfig = + mConfiguredNetworks.getForCurrentUser(netId); String keyId = config.getKeyIdForCredentials(currentConfig); if (!installKeys(enterpriseConfig, keyId)) { @@ -3098,7 +3177,7 @@ public class WifiConfigStore extends IpConfigStore { * when written. For example, wep key is stored as * irrespective * of the value sent to the supplicant */ - WifiConfiguration currentConfig = mConfiguredNetworks.get(netId); + WifiConfiguration currentConfig = mConfiguredNetworks.getForCurrentUser(netId); if (currentConfig == null) { currentConfig = new WifiConfiguration(); currentConfig.setIpAssignment(IpAssignment.DHCP); @@ -3122,6 +3201,7 @@ public class WifiConfigStore extends IpConfigStore { currentConfig.numNoInternetAccessReports = config.numNoInternetAccessReports; currentConfig.updateTime = config.updateTime; currentConfig.creationTime = config.creationTime; + currentConfig.shared = config.shared; } if (DBG) { log("created new config netId=" + Integer.toString(netId) @@ -3206,7 +3286,7 @@ public class WifiConfigStore extends IpConfigStore { currentConfig.lastUpdateUid = config.lastUpdateUid; } - mConfiguredNetworks.put(netId, currentConfig); + mConfiguredNetworks.put(currentConfig); NetworkUpdateResult result = writeIpAndProxyConfigurationsOnChange(currentConfig, config); result.setIsNewNetwork(newNetwork); @@ -3215,13 +3295,13 @@ public class WifiConfigStore extends IpConfigStore { if (homeSP != null) { writePasspointConfigs(null, homeSP); } - writeKnownNetworkHistory(false); + writeKnownNetworkHistory(true); return result; } public WifiConfiguration getWifiConfigForHomeSP(HomeSP homeSP) { - WifiConfiguration config = mConfiguredNetworks.getByFQDN(homeSP.getFQDN()); + WifiConfiguration config = mConfiguredNetworks.getByFQDNForCurrentUser(homeSP.getFQDN()); if (config == null) { Log.e(TAG, "Could not find network for homeSP " + homeSP.getFQDN()); } @@ -3229,7 +3309,7 @@ public class WifiConfigStore extends IpConfigStore { } public HomeSP getHomeSPForConfig(WifiConfiguration config) { - WifiConfiguration storedConfig = mConfiguredNetworks.get(config.networkId); + WifiConfiguration storedConfig = mConfiguredNetworks.getForCurrentUser(config.networkId); return storedConfig != null && storedConfig.isPasspoint() ? mMOManager.getHomeSP(storedConfig.FQDN) : null; } @@ -3249,6 +3329,11 @@ public class WifiConfigStore extends IpConfigStore { * @param config */ public void linkConfiguration(WifiConfiguration config) { + if (!config.isVisibleToUser(mWifiStateMachine.getCurrentUserId())) { + loge("linkConfiguration: Attempting to link config " + config.configKey() + + " that is not visible to the current user."); + return; + } if (getScanDetailCache(config) != null && getScanDetailCache(config).size() > 6) { // Ignore configurations with large number of BSSIDs @@ -3258,7 +3343,7 @@ public class WifiConfigStore extends IpConfigStore { // Only link WPA_PSK config return; } - for (WifiConfiguration link : mConfiguredNetworks.values()) { + for (WifiConfiguration link : mConfiguredNetworks.valuesForCurrentUser()) { boolean doLink = false; if (link.configKey().equals(config.configKey())) { @@ -3767,7 +3852,7 @@ public class WifiConfigStore extends IpConfigStore { } } - for (WifiConfiguration config : mConfiguredNetworks.values()) { + for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) { boolean found = false; if (config.SSID == null || !config.SSID.equals(SSID)) { continue; @@ -3800,6 +3885,58 @@ public class WifiConfigStore extends IpConfigStore { } } + /** + * Handles the switch to a different foreground user: + * - Removes all ephemeral networks + * - Disables private network configurations belonging to the previous foreground user + * - Enables private network configurations belonging to the new foreground user + * + * TODO(b/25600871): Terminate background users if the new foreground user has one or more + * private network configurations. + */ + public void handleUserSwitch() { + Set<WifiConfiguration> ephemeralConfigs = new HashSet<>(); + for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) { + if (config.ephemeral) { + ephemeralConfigs.add(config); + } + } + if (!ephemeralConfigs.isEmpty()) { + for (WifiConfiguration config : ephemeralConfigs) { + if (config.configKey().equals(lastSelectedConfiguration)) { + lastSelectedConfiguration = null; + } + if (config.enterpriseConfig != null) { + removeKeys(config.enterpriseConfig); + } + mConfiguredNetworks.remove(config.networkId); + mScanDetailCaches.remove(config.networkId); + mWifiNative.removeNetwork(config.networkId); + } + mWifiNative.saveConfig(); + writeKnownNetworkHistory(true); + } + + final List<WifiConfiguration> hiddenConfigurations = + mConfiguredNetworks.handleUserSwitch(mWifiStateMachine.getCurrentUserId()); + for (WifiConfiguration network : hiddenConfigurations) { + if (mWifiNative.disableNetwork(network.networkId)) { + network.status = Status.DISABLED; + } + } + + for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) { + enableNetworkWithoutBroadcast(config.networkId, false); + } + enableAllNetworks(); + + // TODO(b/25600871): This broadcast is unnecessary if either of the following is true: + // * The user switch did not change the list of visible networks + // * The user switch revealed additional networks that were temporarily disabled and got + // re-enabled now (because enableAllNetworks() sent the same broadcast already). + sendConfiguredNetworksChangedBroadcast(); + } + /* Compare current and new configuration and write to file on change */ private NetworkUpdateResult writeIpAndProxyConfigurationsOnChange( WifiConfiguration currentConfig, @@ -4141,6 +4278,10 @@ public class WifiConfigStore extends IpConfigStore { return ipConfigFile; } + protected void logd(String s) { + Log.d(TAG, s); + } + protected void loge(String s) { loge(s, false); } @@ -4186,7 +4327,7 @@ public class WifiConfigStore extends IpConfigStore { WifiConfiguration config; synchronized(mConfiguredNetworks) { // !!! Useless synchronization - config = mConfiguredNetworks.get(netId); + config = mConfiguredNetworks.getForAllUsers(netId); } if (config != null) { @@ -4287,7 +4428,7 @@ public class WifiConfigStore extends IpConfigStore { } void resetSimNetworks() { - for(WifiConfiguration config : mConfiguredNetworks.values()) { + for (WifiConfiguration config : mConfiguredNetworks.valuesForAllUsers()) { if (isSimConfig(config)) { /* This configuration may have cached Pseudonym IDs; lets remove them */ mWifiNative.setNetworkVariable(config.networkId, "identity", "NULL"); @@ -4301,28 +4442,29 @@ public class WifiConfigStore extends IpConfigStore { // matching the one we are trying to add. if(config.networkId != INVALID_NETWORK_ID) { - return (mConfiguredNetworks.get(config.networkId) != null); + return (mConfiguredNetworks.getForCurrentUser(config.networkId) != null); } - return (mConfiguredNetworks.getByConfigKey(config.configKey()) != null); + return (mConfiguredNetworks.getByConfigKeyForCurrentUser(config.configKey()) != null); } /** * Checks if uid has access to modify the configuration corresponding to networkId. * - * Factors involved in modifiability of a config are as follows. - * If uid is a Device Owner app then it has full control over the device, including WiFi - * configs. - * If the modification is only for administrative annotation (e.g. when connecting) or the - * config is not lockdown eligible (currently that means any config not last updated by the DO) - * then the creator of config or an app holding OVERRIDE_CONFIG_WIFI can modify the config. - * If the config is lockdown eligible and the modification is substantial (not annotation) - * then the requirement to be able to modify the config by the uid is as follows: - * a) the uid has to hold OVERRIDE_CONFIG_WIFI and - * b) the lockdown feature should be disabled. + * The conditions checked are, in descending priority order: + * - Disallow modification if the the configuration is not visible to the uid. + * - Allow modification if the uid represents the Device Owner app. + * - Allow modification if both of the following are true: + * - The uid represents the configuration's creator or an app holding OVERRIDE_CONFIG_WIFI. + * - The modification is only for administrative annotation (e.g. when connecting) or the + * configuration is not lockdown eligible (which currently means that it was not last + * updated by the DO). + * - Allow modification if configuration lockdown is explicitly disabled and the uid represents + * an app holding OVERRIDE_CONFIG_WIFI. + * - In all other cases, disallow modification. */ boolean canModifyNetwork(int uid, int networkId, boolean onlyAnnotate) { - WifiConfiguration config = mConfiguredNetworks.get(networkId); + WifiConfiguration config = mConfiguredNetworks.getForCurrentUser(networkId); if (config == null) { loge("canModifyNetwork: cannot find config networkId " + networkId); @@ -4336,7 +4478,6 @@ public class WifiConfigStore extends IpConfigStore { DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); if (isUidDeviceOwner) { - // Device Owner has full control over the device, including WiFi Configs return true; } @@ -4381,7 +4522,8 @@ public class WifiConfigStore extends IpConfigStore { if (config.networkId != INVALID_NETWORK_ID){ netid = config.networkId; } else { - WifiConfiguration test = mConfiguredNetworks.getByConfigKey(config.configKey()); + WifiConfiguration test = + mConfiguredNetworks.getByConfigKeyForCurrentUser(config.configKey()); if (test == null) { return false; } else { @@ -4407,7 +4549,7 @@ public class WifiConfigStore extends IpConfigStore { */ void handleBadNetworkDisconnectReport(int netId, WifiInfo info) { /* TODO verify the bad network is current */ - WifiConfiguration config = mConfiguredNetworks.get(netId); + WifiConfiguration config = mConfiguredNetworks.getForCurrentUser(netId); if (config != null) { if ((info.is24GHz() && info.getRssi() <= WifiQualifiedNetworkSelector.QUALIFIED_RSSI_24G_BAND) @@ -4431,7 +4573,7 @@ public class WifiConfigStore extends IpConfigStore { return found; // Look for the BSSID in our config store - for (WifiConfiguration config : mConfiguredNetworks.values()) { + for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) { if (getScanDetailCache(config) != null) { for (ScanDetail scanDetail : getScanDetailCache(config).values()) { if (scanDetail.getBSSIDString().equals(BSSID)) { diff --git a/service/java/com/android/server/wifi/WifiService.java b/service/java/com/android/server/wifi/WifiService.java index 8caa3eb4c..28e5b18a6 100644 --- a/service/java/com/android/server/wifi/WifiService.java +++ b/service/java/com/android/server/wifi/WifiService.java @@ -18,6 +18,7 @@ package com.android.server.wifi; import android.content.Context; import android.util.Log; + import com.android.server.SystemService; public final class WifiService extends SystemService { @@ -42,4 +43,9 @@ public final class WifiService extends SystemService { mImpl.checkAndStartWifi(); } } + + @Override + public void onSwitchUser(int userId) { + mImpl.handleUserSwitch(userId); + } } diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java index 2dcac2505..832ccb102 100644 --- a/service/java/com/android/server/wifi/WifiServiceImpl.java +++ b/service/java/com/android/server/wifi/WifiServiceImpl.java @@ -400,6 +400,10 @@ public class WifiServiceImpl extends IWifiManager.Stub { if (wifiEnabled) setWifiEnabled(wifiEnabled); } + public void handleUserSwitch(int userId) { + mWifiStateMachine.handleUserSwitch(userId); + } + /** * see {@link android.net.wifi.WifiManager#pingSupplicant()} * @return {@code true} if the operation succeeds, {@code false} otherwise @@ -863,11 +867,6 @@ public class WifiServiceImpl extends IWifiManager.Stub { public boolean removeNetwork(int netId) { enforceChangePermission(); - if (!isOwner(Binder.getCallingUid())) { - Slog.e(TAG, "Remove is not authorized for user"); - return false; - } - if (mWifiStateMachineChannel != null) { return mWifiStateMachine.syncRemoveNetwork(mWifiStateMachineChannel, netId); } else { diff --git a/service/java/com/android/server/wifi/WifiStateMachine.java b/service/java/com/android/server/wifi/WifiStateMachine.java index ab64d8ed7..cefc7df20 100644 --- a/service/java/com/android/server/wifi/WifiStateMachine.java +++ b/service/java/com/android/server/wifi/WifiStateMachine.java @@ -235,6 +235,8 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiPno private int mHalFeatureSet = 0; private static int mPnoResultFound = 0; + private int mCurrentUserId = UserHandle.USER_SYSTEM; + @Override public void onPnoNetworkFound(ScanResult results[]) { if (DBG) { @@ -881,6 +883,8 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiPno /* used to indicated RSSI threshold breach in hw */ static final int CMD_RSSI_THRESHOLD_BREACH = BASE + 164; + /* used to indicate that the foreground user was switched */ + static final int CMD_USER_SWITCH = BASE + 165; /* Wifi state machine modes of operation */ @@ -2727,6 +2731,10 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiPno mWifiQualifiedNetworkSelector.dump(fd, pw, args); } + public void handleUserSwitch(int userId) { + sendMessage(CMD_USER_SWITCH, userId); + } + /** * ****************************************************** * Internal private functions @@ -3331,6 +3339,10 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiPno sb.append(" thresholds="); sb.append(Arrays.toString(mRssiRanges)); break; + case CMD_USER_SWITCH: + sb.append(" userId="); + sb.append(Integer.toString(msg.arg1)); + break; default: sb.append(" "); sb.append(Integer.toString(msg.arg1)); @@ -6923,6 +6935,9 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiPno case CMD_RSSI_THRESHOLD_BREACH: s = "CMD_RSSI_THRESHOLD_BREACH"; break; + case CMD_USER_SWITCH: + s = "CMD_USER_SWITCH"; + break; default: s = "what:" + Integer.toString(what); break; @@ -7131,6 +7146,15 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiPno } break; case CMD_ADD_OR_UPDATE_NETWORK: + // Only the current foreground user can modify networks. + if (UserHandle.getUserId(message.sendingUid) != mCurrentUserId) { + loge("Only the current foreground user can modify networks " + + " currentUserId=" + mCurrentUserId + + " sendingUserId=" + UserHandle.getUserId(message.sendingUid)); + replyToMessage(message, message.what, FAILURE); + break; + } + config = (WifiConfiguration) message.obj; if (!recordUidIfAuthorized(config, message.sendingUid, @@ -7173,7 +7197,16 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiPno replyToMessage(message, CMD_ADD_OR_UPDATE_NETWORK, res); break; case CMD_REMOVE_NETWORK: + // Only the current foreground user can modify networks. + if (UserHandle.getUserId(message.sendingUid) != mCurrentUserId) { + loge("Only the current foreground user can modify networks " + + " currentUserId=" + mCurrentUserId + + " sendingUserId=" + UserHandle.getUserId(message.sendingUid)); + replyToMessage(message, message.what, FAILURE); + break; + } netId = message.arg1; + if (!mWifiConfigStore.canModifyNetwork(message.sendingUid, netId, /* onlyAnnotate */ false)) { logw("Not authorized to remove network " @@ -7190,6 +7223,15 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiPno replyToMessage(message, message.what, ok ? SUCCESS : FAILURE); break; case CMD_ENABLE_NETWORK: + // Only the current foreground user can modify networks. + if (UserHandle.getUserId(message.sendingUid) != mCurrentUserId) { + loge("Only the current foreground user can modify networks " + + " currentUserId=" + mCurrentUserId + + " sendingUserId=" + UserHandle.getUserId(message.sendingUid)); + replyToMessage(message, message.what, FAILURE); + break; + } + boolean disableOthers = message.arg2 == 1; netId = message.arg1; config = mWifiConfigStore.getWifiConfiguration(netId); @@ -7445,8 +7487,13 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiPno // If we're autojoining a network that the user or an app explicitly selected, // keep track of the UID that selected it. - int lastConnectUid = mWifiConfigStore.isLastSelectedConfiguration(config) ? - config.lastConnectUid : WifiConfiguration.UNKNOWN_UID; + // TODO(b/25600871): Keep track of the lastSelectedConfiguration and the + // lastConnectUid on a per-user basis. + int lastConnectUid = WifiConfiguration.UNKNOWN_UID; + if (mWifiConfigStore.isLastSelectedConfiguration(config) + && UserHandle.getUserId(config.lastConnectUid) == mCurrentUserId) { + lastConnectUid = config.lastConnectUid; + } if (mWifiConfigStore.selectNetwork(config, /* updatePriorities = */ false, lastConnectUid) && mWifiNative.reconnect()) { @@ -7505,6 +7552,16 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiPno mWifiConfigStore.removeNetworksForUser(message.arg1); break; case WifiManager.CONNECT_NETWORK: + // Only the current foreground user can modify networks. + if (UserHandle.getUserId(message.sendingUid) != mCurrentUserId) { + loge("Only the current foreground user can modify networks " + + " currentUserId=" + mCurrentUserId + + " sendingUserId=" + UserHandle.getUserId(message.sendingUid)); + replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED, + WifiManager.NOT_AUTHORIZED); + break; + } + /** * The connect message can contain a network id passed as arg1 on message or * or a config passed as obj on message. @@ -7646,6 +7703,16 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiPno mWifiConnectionStatistics.numWifiManagerJoinAttempt++; // Fall thru case WifiStateMachine.CMD_AUTO_SAVE_NETWORK: + // Only the current foreground user can modify networks. + if (UserHandle.getUserId(message.sendingUid) != mCurrentUserId) { + loge("Only the current foreground user can modify networks " + + " currentUserId=" + mCurrentUserId + + " sendingUserId=" + UserHandle.getUserId(message.sendingUid)); + replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED, + WifiManager.NOT_AUTHORIZED); + break; + } + lastSavedConfigurationAttempt = null; // Used for debug config = (WifiConfiguration) message.obj; if (config == null) { @@ -7732,6 +7799,16 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiPno } break; case WifiManager.FORGET_NETWORK: + // Only the current foreground user can modify networks. + if (UserHandle.getUserId(message.sendingUid) != mCurrentUserId) { + loge("Only the current foreground user can modify networks " + + " currentUserId=" + mCurrentUserId + + " sendingUserId=" + UserHandle.getUserId(message.sendingUid)); + replyToMessage(message, WifiManager.FORGET_NETWORK_FAILED, + WifiManager.NOT_AUTHORIZED); + break; + } + // Debug only, remember last configuration that was forgotten WifiConfiguration toRemove = mWifiConfigStore.getWifiConfiguration(message.arg1); @@ -8453,6 +8530,10 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiPno } /* allow parent state to reset data for other networks */ return NOT_HANDLED; + case CMD_USER_SWITCH: + mCurrentUserId = message.arg1; + mWifiConfigStore.handleUserSwitch(); + break; default: return NOT_HANDLED; } @@ -10080,6 +10161,10 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiPno } } + public int getCurrentUserId() { + return mCurrentUserId; + } + /** * @param reason reason code from supplicant on network disconnected event * @return true if this is a suspicious disconnect diff --git a/tests/wifitests/src/com/android/server/wifi/ConfigurationMapTest.java b/tests/wifitests/src/com/android/server/wifi/ConfigurationMapTest.java new file mode 100644 index 000000000..917fd77a7 --- /dev/null +++ b/tests/wifitests/src/com/android/server/wifi/ConfigurationMapTest.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2016 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.server.wifi; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import android.net.wifi.WifiConfiguration; +import android.os.UserHandle; +import android.test.suitebuilder.annotation.SmallTest; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Unit tests for {@link com.android.server.wifi.ConfigurationMapTest}. + */ +@SmallTest +public class ConfigurationMapTest { + private static final List<WifiConfiguration> CONFIGS = Arrays.asList( + WifiConfigurationUtil.generateWifiConfig(0, 1000000, "\"red\"", true, true, null, null), + WifiConfigurationUtil.generateWifiConfig( + 1, 1000001, "\"green\"", false, true, null, null), + WifiConfigurationUtil.generateWifiConfig( + 2, 1000002, "\"blue\"", true, false, "example.com", "Blue"), + WifiConfigurationUtil.generateWifiConfig( + 3, 1100000, "\"cyan\"", true, true, null, null), + WifiConfigurationUtil.generateWifiConfig( + 4, 1100001, "\"yellow\"", false, false, null, null), + WifiConfigurationUtil.generateWifiConfig( + 5, 1100002, "\"magenta\"", true, true, "example.org", "Magenta")); + + private int mCurrentUserId = UserHandle.USER_SYSTEM; + private final ConfigurationMap mConfigs = new ConfigurationMap(); + + public void switchUser(int newUserId) { + Set<WifiConfiguration> hiddenConfigurations = new HashSet<>(); + for (WifiConfiguration config : mConfigs.valuesForAllUsers()) { + if (config.isVisibleToUser(mCurrentUserId) && !config.isVisibleToUser(newUserId)) { + hiddenConfigurations.add(config); + } + } + + mCurrentUserId = newUserId; + assertEquals(hiddenConfigurations, new HashSet<>(mConfigs.handleUserSwitch(newUserId))); + } + + public void verifyGetters(List<WifiConfiguration> configs) { + final Set<WifiConfiguration> configsForCurrentUser = new HashSet<>(); + final Set<WifiConfiguration> enabledConfigsForCurrentUser = new HashSet<>(); + final List<WifiConfiguration> configsNotForCurrentUser = new ArrayList<>(); + + // Find out which network configurations should be / should not be visible to the current + // user. Also, check that *ForAllUsers() methods can be used to access all network + // configurations, irrespective of their visibility to the current user. + for (WifiConfiguration config : configs) { + if (config.isVisibleToUser(mCurrentUserId)) { + configsForCurrentUser.add(config); + if (config.status != WifiConfiguration.Status.DISABLED) { + enabledConfigsForCurrentUser.add(config); + } + } else { + configsNotForCurrentUser.add(config); + } + + assertEquals(config, mConfigs.getForAllUsers(config.networkId)); + assertEquals(config, + mConfigs.getByConfigKeyIDForAllUsers(config.configKey().hashCode())); + } + + // Verify that *ForCurrentUser() methods can be used to access network configurations + // visible to the current user. + for (WifiConfiguration config : configsForCurrentUser) { + assertEquals(config, mConfigs.getForCurrentUser(config.networkId)); + if (config.FQDN != null) { + assertEquals(config, mConfigs.getByFQDNForCurrentUser(config.FQDN)); + } + assertEquals(config, mConfigs.getByConfigKeyForCurrentUser(config.configKey())); + final boolean wasEphemeral = config.ephemeral; + config.ephemeral = false; + assertNull(mConfigs.getEphemeralForCurrentUser(config.SSID)); + config.ephemeral = true; + assertEquals(config, mConfigs.getEphemeralForCurrentUser(config.SSID)); + config.ephemeral = wasEphemeral; + } + + // Verify that *ForCurrentUser() methods cannot be used to access network configurations not + // visible to the current user. + for (WifiConfiguration config : configsNotForCurrentUser) { + assertNull(mConfigs.getForCurrentUser(config.networkId)); + if (config.FQDN != null) { + assertNull(mConfigs.getByFQDNForCurrentUser(config.FQDN)); + } + assertNull(mConfigs.getByConfigKeyForCurrentUser(config.configKey())); + final boolean wasEphemeral = config.ephemeral; + config.ephemeral = false; + assertNull(mConfigs.getEphemeralForCurrentUser(config.SSID)); + config.ephemeral = true; + assertNull(mConfigs.getEphemeralForCurrentUser(config.SSID)); + config.ephemeral = wasEphemeral; + } + + // Verify that the methods which refer to more than one network configuration return the + // correct sets of networks. + assertEquals(configs.size(), mConfigs.sizeForAllUsers()); + assertEquals(configsForCurrentUser.size(), mConfigs.sizeForCurrentUser()); + assertEquals(enabledConfigsForCurrentUser, + new HashSet<WifiConfiguration>(mConfigs.getEnabledNetworksForCurrentUser())); + assertEquals(new HashSet<>(configs), + new HashSet<WifiConfiguration>(mConfigs.valuesForAllUsers())); + } + + /** + * Verifies that all getters return the correct network configurations, taking into account the + * current user. Also verifies that handleUserSwitch() returns the list of network + * configurations that are no longer visible. + */ + @Test + public void testGettersAndHandleUserSwitch() { + for (WifiConfiguration config : CONFIGS) { + assertNull(mConfigs.put(config)); + } + + verifyGetters(CONFIGS); + + switchUser(10); + verifyGetters(CONFIGS); + + switchUser(11); + verifyGetters(CONFIGS); + } + + /** + * Verifies put(), remove() and clear(). + */ + @Test + public void testPutRemoveClear() { + final List<WifiConfiguration> configs = new ArrayList<>(); + final WifiConfiguration config1 = CONFIGS.get(0); + + // Verify that there are no network configurations to start with. + switchUser(UserHandle.getUserId(config1.creatorUid)); + verifyGetters(configs); + + // Add |config1|. + assertNull(mConfigs.put(config1)); + // Verify that the getters return |config1|. + configs.add(config1); + verifyGetters(configs); + + // Overwrite |config1| with |config2|. + final WifiConfiguration config2 = CONFIGS.get(1); + config2.networkId = config1.networkId; + assertEquals(config1, mConfigs.put(config2)); + // Verify that the getters return |config2| only. + configs.clear(); + configs.add(config2); + verifyGetters(configs); + + // Add |config3|. + final WifiConfiguration config3 = CONFIGS.get(2); + assertNull(mConfigs.put(config3)); + // Verify that the getters return |config2| and |config3|. + configs.add(config3); + verifyGetters(configs); + + // Remove |config2|. + assertEquals(config2, mConfigs.remove(config2.networkId)); + // Verify that the getters return |config3| only. + configs.remove(config2); + verifyGetters(configs); + + // Clear all network configurations. + mConfigs.clear(); + // Verify that the getters do not return any network configurations. + configs.clear(); + verifyGetters(configs); + } +} diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java index 0a9af5e0b..638a726e6 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java @@ -17,20 +17,25 @@ package com.android.server.wifi; import static org.hamcrest.CoreMatchers.not; +import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyObject; import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.intThat; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; import android.net.wifi.WifiConfiguration; -import android.net.wifi.WifiEnterpriseConfig; +import android.os.UserHandle; import android.test.AndroidTestCase; +import com.android.server.net.DelayedDiskWrite; +import com.android.server.wifi.MockAnswerUtil.AnswerWithArguments; import com.android.server.wifi.hotspot2.omadm.MOManager; import com.android.server.wifi.hotspot2.pps.Credential; import com.android.server.wifi.hotspot2.pps.HomeSP; @@ -38,31 +43,59 @@ import com.android.server.wifi.hotspot2.pps.HomeSP; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileOutputStream; import java.lang.reflect.Field; +import java.math.BigInteger; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; /** * Unit tests for {@link com.android.server.wifi.WifiConfigStore}. */ public class WifiConfigStoreTest extends AndroidTestCase { - private static final int UID = 10; - private static final String[] SSIDS = {"\"red\"", "\"green\"", "\"blue\""}; - private static final String[] ENCODED_SSIDS = {"726564", "677265656e", "626c7565"}; - private static final String[] FQDNS = {null, "example.com", "example.org"}; - private static final String[] PROVIDER_FRIENDLY_NAMES = {null, "Green", "Blue"}; - private static final String[] CONFIG_KEYS = {"\"red\"NONE", "example.comWPA_EAP", - "example.orgWPA_EAP"}; + private static final List<WifiConfiguration> CONFIGS = Arrays.asList( + WifiConfigurationUtil.generateWifiConfig( + 0, 1000000, "\"red\"", true, true, null, null), + WifiConfigurationUtil.generateWifiConfig( + 1, 1000001, "\"green\"", true, true, "example.com", "Green"), + WifiConfigurationUtil.generateWifiConfig( + 2, 1100000, "\"blue\"", false, true, "example.org", "Blue")); + + private static final int[] USER_IDS = {0, 10, 11}; + private static final Map<Integer, List<WifiConfiguration>> VISIBLE_CONFIGS = new HashMap<>(); + static { + for (int userId : USER_IDS) { + List<WifiConfiguration> configs = new ArrayList<>(); + for (int i = 0; i < CONFIGS.size(); ++i) { + if (CONFIGS.get(i).isVisibleToUser(userId)) { + configs.add(CONFIGS.get(i)); + } + } + VISIBLE_CONFIGS.put(userId, configs); + } + } @Mock private Context mContext; @Mock private WifiStateMachine mWifiStateMachine; @Mock private WifiNative mWifiNative; @Mock private FrameworkFacade mFrameworkFacade; + @Mock private DelayedDiskWrite mWriter; @Mock private MOManager mMOManager; private WifiConfigStore mConfigStore; + private ConfigurationMap mConfiguredNetworks; + public byte[] mNetworkHistory; @Override public void setUp() throws Exception { @@ -71,97 +104,387 @@ public class WifiConfigStoreTest extends AndroidTestCase { final Context realContext = getContext(); when(mContext.getPackageName()).thenReturn(realContext.getPackageName()); when(mContext.getResources()).thenReturn(realContext.getResources()); + when(mContext.getPackageManager()).thenReturn(realContext.getPackageManager()); + + when(mWifiStateMachine.getCurrentUserId()).thenReturn(UserHandle.USER_SYSTEM); mConfigStore = new WifiConfigStore(mContext, mWifiStateMachine, mWifiNative, mFrameworkFacade); + final Field configuredNetworksField = + WifiConfigStore.class.getDeclaredField("mConfiguredNetworks"); + configuredNetworksField.setAccessible(true); + mConfiguredNetworks = (ConfigurationMap) configuredNetworksField.get(mConfigStore); + + // Intercept writes to networkHistory.txt. + doAnswer(new AnswerWithArguments<Void>() { + public void answer(String filePath, DelayedDiskWrite.Writer writer) throws Exception { + final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + final DataOutputStream stream = new DataOutputStream(buffer); + writer.onWriteCalled(stream); + stream.close(); + mNetworkHistory = buffer.toByteArray(); + }}).when(mWriter).write(anyString(), (DelayedDiskWrite.Writer) anyObject()); + final Field writerField = WifiConfigStore.class.getSuperclass().getDeclaredField("mWriter"); + writerField.setAccessible(true); + writerField.set(mConfigStore, mWriter); + when(mMOManager.isEnabled()).thenReturn(true); final Field moManagerField = WifiConfigStore.class.getDeclaredField("mMOManager"); moManagerField.setAccessible(true); moManagerField.set(mConfigStore, mMOManager); } - private WifiConfiguration generateWifiConfig(int network) { - final WifiConfiguration config = new WifiConfiguration(); - config.SSID = SSIDS[network]; - config.creatorUid = UID; - if (FQDNS[network] != null) { - config.FQDN = FQDNS[network]; - config.providerFriendlyName = PROVIDER_FRIENDLY_NAMES[network]; - config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.SIM); + private void switchUser(int newUserId) { + when(mWifiStateMachine.getCurrentUserId()).thenReturn(newUserId); + mConfigStore.handleUserSwitch(); + } + + private void switchUserToCreatorOf(WifiConfiguration config) { + switchUser(UserHandle.getUserId(config.creatorUid)); + } + + private void addNetworks() throws Exception { + final int originalUserId = mWifiStateMachine.getCurrentUserId(); + + when(mWifiNative.setNetworkVariable(anyInt(), anyString(), anyString())).thenReturn(true); + when(mWifiNative.setNetworkExtra(anyInt(), anyString(), (Map<String, String>) anyObject())) + .thenReturn(true); + for (int i = 0; i < CONFIGS.size(); ++i) { + assertEquals(i, CONFIGS.get(i).networkId); + switchUserToCreatorOf(CONFIGS.get(i)); + final WifiConfiguration config = new WifiConfiguration(CONFIGS.get(i)); + config.networkId = -1; + when(mWifiNative.addNetwork()).thenReturn(i); + when(mWifiNative.getNetworkVariable(i, WifiConfiguration.ssidVarName)) + .thenReturn(encodeConfigSSID(CONFIGS.get(i))); + mConfigStore.saveNetwork(config, config.creatorUid); + } + + switchUser(originalUserId); + } + + private String encodeConfigSSID(WifiConfiguration config) throws Exception { + return new BigInteger(1, config.SSID.substring(1, config.SSID.length() - 1) + .getBytes("UTF-8")).toString(16); + } + + private WifiNative createNewWifiNativeMock() throws Exception { + final WifiNative wifiNative = mock(WifiNative.class); + final Field wifiNativeField = WifiConfigStore.class.getDeclaredField("mWifiNative"); + wifiNativeField.setAccessible(true); + wifiNativeField.set(mConfigStore, wifiNative); + return wifiNative; + } + + /** + * Verifies that getConfiguredNetworksSize() returns the number of network configurations + * visible to the current user. + */ + public void testGetConfiguredNetworksSize() throws Exception { + addNetworks(); + for (Map.Entry<Integer, List<WifiConfiguration>> entry : VISIBLE_CONFIGS.entrySet()) { + switchUser(entry.getKey()); + assertEquals(entry.getValue().size(), mConfigStore.getConfiguredNetworksSize()); + } + } + + private void verifyNetworkConfig(WifiConfiguration expectedConfig, + WifiConfiguration actualConfig) { + assertNotNull(actualConfig); + assertEquals(expectedConfig.SSID, actualConfig.SSID); + assertEquals(expectedConfig.FQDN, actualConfig.FQDN); + assertEquals(expectedConfig.providerFriendlyName, + actualConfig.providerFriendlyName); + assertEquals(expectedConfig.configKey(), actualConfig.configKey(false)); + } + + private void verifyNetworkConfigs(Collection<WifiConfiguration> expectedConfigs, + Collection<WifiConfiguration> actualConfigs) { + assertEquals(expectedConfigs.size(), actualConfigs.size()); + for (WifiConfiguration expectedConfig : expectedConfigs) { + WifiConfiguration actualConfig = null; + // Find the network configuration to test (assume that |actualConfigs| contains them in + // undefined order). + for (final WifiConfiguration candidate : actualConfigs) { + if (candidate.networkId == expectedConfig.networkId) { + actualConfig = candidate; + break; + } + } + verifyNetworkConfig(expectedConfig, actualConfig); + } + } + + /** + * Verifies that getConfiguredNetworksSize() returns the network configurations visible to the + * current user. + */ + public void testGetConfiguredNetworks() throws Exception { + addNetworks(); + for (Map.Entry<Integer, List<WifiConfiguration>> entry : VISIBLE_CONFIGS.entrySet()) { + switchUser(entry.getKey()); + verifyNetworkConfigs(entry.getValue(), mConfigStore.getConfiguredNetworks()); + } + } + + /** + * Verifies that getPrivilegedConfiguredNetworks() returns the network configurations visible to + * the current user. + */ + public void testGetPrivilegedConfiguredNetworks() throws Exception { + addNetworks(); + for (Map.Entry<Integer, List<WifiConfiguration>> entry : VISIBLE_CONFIGS.entrySet()) { + switchUser(entry.getKey()); + verifyNetworkConfigs(entry.getValue(), mConfigStore.getPrivilegedConfiguredNetworks()); + } + } + + /** + * Verifies that getWifiConfiguration(int netId) can be used to access network configurations + * visible to the current user only. + */ + public void testGetWifiConfigurationByNetworkId() throws Exception { + addNetworks(); + for (int userId : USER_IDS) { + switchUser(userId); + for (WifiConfiguration expectedConfig: CONFIGS) { + final WifiConfiguration actualConfig = + mConfigStore.getWifiConfiguration(expectedConfig.networkId); + if (expectedConfig.isVisibleToUser(userId)) { + verifyNetworkConfig(expectedConfig, actualConfig); + } else { + assertNull(actualConfig); + } + } + } + } + + /** + * Verifies that getWifiConfiguration(String key) can be used to access network configurations + * visible to the current user only. + */ + public void testGetWifiConfigurationByConfigKey() throws Exception { + addNetworks(); + for (int userId : USER_IDS) { + switchUser(userId); + for (WifiConfiguration expectedConfig: CONFIGS) { + final WifiConfiguration actualConfig = + mConfigStore.getWifiConfiguration(expectedConfig.configKey()); + if (expectedConfig.isVisibleToUser(userId)) { + verifyNetworkConfig(expectedConfig, actualConfig); + } else { + assertNull(actualConfig); + } + } + } + } + + /** + * Verifies that enableAllNetworks() enables all temporarily disabled network configurations + * visible to the current user. + */ + public void testEnableAllNetworks() throws Exception { + addNetworks(); + when(mWifiNative.enableNetwork(anyInt(), anyBoolean())).thenReturn(true); + for (int userId : USER_IDS) { + switchUser(userId); + + for (WifiConfiguration config : mConfiguredNetworks.valuesForAllUsers()) { + final WifiConfiguration.NetworkSelectionStatus status = + config.getNetworkSelectionStatus(); + status.setNetworkSelectionStatus(WifiConfiguration.NetworkSelectionStatus + .NETWORK_SELECTION_TEMPORARY_DISABLED); + status.setNetworkSelectionDisableReason( + WifiConfiguration.NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE); + status.setDisableTime(System.currentTimeMillis() - 60 * 60 * 1000); + } + + mConfigStore.enableAllNetworks(); + + for (WifiConfiguration config : mConfiguredNetworks.valuesForAllUsers()) { + assertEquals(config.isVisibleToUser(userId), + config.getNetworkSelectionStatus().isNetworkEnabled()); + } + } + } + + /** + * Verifies that selectNetwork() disables all network configurations visible to the current user + * except the selected one. + */ + public void testSelectNetwork() throws Exception { + addNetworks(); + + for (int userId : USER_IDS) { + switchUser(userId); + + for (WifiConfiguration config : mConfiguredNetworks.valuesForAllUsers()) { + // Enable all network configurations. + for (WifiConfiguration config2 : mConfiguredNetworks.valuesForAllUsers()) { + config2.status = WifiConfiguration.Status.ENABLED; + } + + // Try to select a network configuration. + final WifiNative wifiNative = createNewWifiNativeMock(); + final boolean success = + mConfigStore.selectNetwork(config, false, config.creatorUid); + if (!config.isVisibleToUser(userId)) { + // If the network configuration is not visible to the current user, verify that + // nothing changed. + assertFalse(success); + verify(wifiNative, never()).selectNetwork(anyInt()); + verify(wifiNative, never()).enableNetwork(anyInt(), anyBoolean()); + for (WifiConfiguration config2 : mConfiguredNetworks.valuesForAllUsers()) { + assertEquals(WifiConfiguration.Status.ENABLED, config2.status); + } + } else { + // If the network configuration is visible to the current user, verify that it + // was enabled and all other network configurations visible to the user were + // disabled. + assertTrue(success); + verify(wifiNative).selectNetwork(config.networkId); + verify(wifiNative, never()).selectNetwork(intThat(not(config.networkId))); + verify(wifiNative).enableNetwork(config.networkId, true); + verify(wifiNative, never()).enableNetwork(config.networkId, false); + verify(wifiNative, never()).enableNetwork(intThat(not(config.networkId)), + anyBoolean()); + for (WifiConfiguration config2 : mConfiguredNetworks.valuesForAllUsers()) { + if (config2.isVisibleToUser(userId) + && config2.networkId != config.networkId) { + assertEquals(WifiConfiguration.Status.DISABLED, config2.status); + } else { + assertEquals(WifiConfiguration.Status.ENABLED, config2.status); + } + } + } + } } - return config; } /** * Verifies that saveNetwork() correctly stores a network configuration in wpa_supplicant - * variables. - * TODO: Test all variables. Currently, only "ssid" and "id_str" are tested. + * variables and the networkHistory.txt file. + * TODO: Test all variables. Currently, only the following variables are tested: + * - In the wpa_supplicant: "ssid", "id_str" + * - In networkHistory.txt: "CONFIG", "CREATOR_UID_KEY", "SHARED" */ - public void verifySaveNetwork(int network) { + private void verifySaveNetwork(int network) throws Exception { + // Switch to the correct user. + switchUserToCreatorOf(CONFIGS.get(network)); + // Set up wpa_supplicant. when(mWifiNative.addNetwork()).thenReturn(0); - when(mWifiNative.setNetworkVariable(eq(0), anyString(), anyString())).thenReturn(true); - when(mWifiNative.setNetworkExtra(eq(0), anyString(), (Map<String, String>) anyObject())) + when(mWifiNative.setNetworkVariable(eq(network), anyString(), anyString())) .thenReturn(true); - when(mWifiNative.getNetworkVariable(0, WifiConfiguration.ssidVarName)) - .thenReturn(ENCODED_SSIDS[network]); + when(mWifiNative.setNetworkExtra(eq(network), anyString(), + (Map<String, String>) anyObject())).thenReturn(true); + when(mWifiNative.getNetworkVariable(network, WifiConfiguration.ssidVarName)) + .thenReturn(encodeConfigSSID(CONFIGS.get(network))); // Store a network configuration. - mConfigStore.saveNetwork(generateWifiConfig(network), UID); + mConfigStore.saveNetwork(CONFIGS.get(network), CONFIGS.get(network).creatorUid); // Verify that wpa_supplicant variables were written correctly for the network // configuration. final Map<String, String> metadata = new HashMap<String, String>(); - if (FQDNS[network] != null) { - metadata.put(WifiConfigStore.ID_STRING_KEY_FQDN, FQDNS[network]); + if (CONFIGS.get(network).FQDN != null) { + metadata.put(WifiConfigStore.ID_STRING_KEY_FQDN, CONFIGS.get(network).FQDN); } - metadata.put(WifiConfigStore.ID_STRING_KEY_CONFIG_KEY, CONFIG_KEYS[network]); - metadata.put(WifiConfigStore.ID_STRING_KEY_CREATOR_UID, Integer.toString(UID)); - verify(mWifiNative).setNetworkExtra(0, WifiConfigStore.ID_STRING_VAR_NAME, + metadata.put(WifiConfigStore.ID_STRING_KEY_CONFIG_KEY, CONFIGS.get(network).configKey()); + metadata.put(WifiConfigStore.ID_STRING_KEY_CREATOR_UID, + Integer.toString(CONFIGS.get(network).creatorUid)); + verify(mWifiNative).setNetworkExtra(network, WifiConfigStore.ID_STRING_VAR_NAME, metadata); // Verify that no wpa_supplicant variables were read or written for any other network // configurations. - verify(mWifiNative, never()).setNetworkExtra(intThat(not(0)), anyString(), + verify(mWifiNative, never()).setNetworkExtra(intThat(not(network)), anyString(), (Map<String, String>) anyObject()); - verify(mWifiNative, never()).setNetworkVariable(intThat(not(0)), anyString(), anyString()); - verify(mWifiNative, never()).getNetworkVariable(intThat(not(0)), anyString()); + verify(mWifiNative, never()).setNetworkVariable(intThat(not(network)), anyString(), + anyString()); + verify(mWifiNative, never()).getNetworkVariable(intThat(not(network)), anyString()); + + // Parse networkHistory.txt. + assertNotNull(mNetworkHistory); + final DataInputStream stream = + new DataInputStream(new ByteArrayInputStream(mNetworkHistory)); + List<String> keys = new ArrayList<>(); + List<String> values = new ArrayList<>(); + try { + while (true) { + final String[] tokens = stream.readUTF().split(":", 2); + if (tokens.length == 2) { + keys.add(tokens[0].trim()); + values.add(tokens[1].trim()); + } + } + } catch (EOFException e) { + // Ignore. This is expected. + } + + // Verify that a networkHistory.txt entry was written correctly for the network + // configuration. + assertTrue(keys.size() >= 3); + assertEquals(WifiConfigStore.CONFIG_KEY, keys.get(0)); + assertEquals(CONFIGS.get(network).configKey(), values.get(0)); + final int creatorUidIndex = keys.indexOf(WifiConfigStore.CREATOR_UID_KEY); + assertTrue(creatorUidIndex != -1); + assertEquals(Integer.toString(CONFIGS.get(network).creatorUid), + values.get(creatorUidIndex)); + final int sharedIndex = keys.indexOf(WifiConfigStore.SHARED_KEY); + assertTrue(sharedIndex != -1); + assertEquals(Boolean.toString(CONFIGS.get(network).shared), values.get(sharedIndex)); + + // Verify that no networkHistory.txt entries were written for any other network + // configurations. + final int lastConfigIndex = keys.lastIndexOf(WifiConfigStore.CONFIG_KEY); + assertEquals(0, lastConfigIndex); } /** * Verifies that saveNetwork() correctly stores a regular network configuration. */ - public void testSaveNetworkRegular() { + public void testSaveNetworkRegular() throws Exception { verifySaveNetwork(0); } /** * Verifies that saveNetwork() correctly stores a HotSpot 2.0 network configuration. */ - public void testSaveNetworkHotspot20() { + public void testSaveNetworkHotspot20() throws Exception { verifySaveNetwork(1); } /** - * Verifies that loadConfiguredNetworks() correctly reads data from the wpa_supplicant and - * the MOManager, correlating the two sources based on the FQDN for HotSpot 2.0 networks. - * TODO: Test all variables. Currently, only "ssid" and "id_str" are tested. + * Verifies that saveNetwork() correctly stores a private network configuration. + */ + public void testSaveNetworkPrivate() throws Exception { + verifySaveNetwork(2); + } + + /** + * Verifies that loadConfiguredNetworks() correctly reads data from the wpa_supplicant, the + * networkHistory.txt file and the MOManager, correlating the three sources based on the + * configKey and the FQDN for HotSpot 2.0 networks. + * TODO: Test all variables. Currently, only the following variables are tested: + * - In the wpa_supplicant: "ssid", "id_str" + * - In networkHistory.txt: "CONFIG", "CREATOR_UID_KEY", "SHARED" */ public void testLoadConfiguredNetworks() throws Exception { - // Set up list of networks returned by wpa_supplicant. + // Set up list of network configurations returned by wpa_supplicant. final String header = "network id / ssid / bssid / flags"; String networks = header; - for (int i = 0; i < SSIDS.length; ++i) { - networks += "\n" + Integer.toString(i) + "\t" + SSIDS[i] + "\tany"; + for (WifiConfiguration config : CONFIGS) { + networks += "\n" + Integer.toString(config.networkId) + "\t" + config.SSID + "\tany"; } when(mWifiNative.listNetworks(anyInt())).thenReturn(header); when(mWifiNative.listNetworks(-1)).thenReturn(networks); - // Set up variables returned by wpa_supplicant for the individual networks. - for (int i = 0; i < SSIDS.length; ++i) { + // Set up variables returned by wpa_supplicant for the individual network configurations. + for (int i = 0; i < CONFIGS.size(); ++i) { when(mWifiNative.getNetworkVariable(i, WifiConfiguration.ssidVarName)) - .thenReturn(ENCODED_SSIDS[i]); + .thenReturn(encodeConfigSSID(CONFIGS.get(i))); } // Legacy regular network configuration: No "id_str". when(mWifiNative.getNetworkExtra(0, WifiConfigStore.ID_STRING_VAR_NAME)) @@ -170,47 +493,140 @@ public class WifiConfigStoreTest extends AndroidTestCase { when(mWifiNative.getNetworkExtra(1, WifiConfigStore.ID_STRING_VAR_NAME)) .thenReturn(null); when(mWifiNative.getNetworkVariable(1, WifiConfigStore.ID_STRING_VAR_NAME)) - .thenReturn('"' + FQDNS[1] + '"'); + .thenReturn('"' + CONFIGS.get(1).FQDN + '"'); // Up-to-date configuration: Metadata in "id_str". final Map<String, String> metadata = new HashMap<String, String>(); - metadata.put(WifiConfigStore.ID_STRING_KEY_CONFIG_KEY, CONFIG_KEYS[2]); - metadata.put(WifiConfigStore.ID_STRING_KEY_CREATOR_UID, Integer.toString(UID)); - metadata.put(WifiConfigStore.ID_STRING_KEY_FQDN, FQDNS[2]); + metadata.put(WifiConfigStore.ID_STRING_KEY_CONFIG_KEY, CONFIGS.get(2).configKey()); + metadata.put(WifiConfigStore.ID_STRING_KEY_CREATOR_UID, + Integer.toString(CONFIGS.get(2).creatorUid)); + metadata.put(WifiConfigStore.ID_STRING_KEY_FQDN, CONFIGS.get(2).FQDN); when(mWifiNative.getNetworkExtra(2, WifiConfigStore.ID_STRING_VAR_NAME)) .thenReturn(metadata); + // Set up networkHistory.txt file. + final File file = File.createTempFile("networkHistory.txt", null); + file.deleteOnExit(); + Field wifiConfigStoreNetworkHistoryConfigFile = + WifiConfigStore.class.getDeclaredField("networkHistoryConfigFile"); + wifiConfigStoreNetworkHistoryConfigFile.setAccessible(true); + wifiConfigStoreNetworkHistoryConfigFile.set(null, file.getAbsolutePath()); + final DataOutputStream stream = new DataOutputStream(new FileOutputStream(file)); + for (WifiConfiguration config : CONFIGS) { + stream.writeUTF(WifiConfigStore.CONFIG_KEY + ": " + config.configKey() + '\n'); + stream.writeUTF(WifiConfigStore.CREATOR_UID_KEY + ": " + + Integer.toString(config.creatorUid) + '\n'); + stream.writeUTF(WifiConfigStore.SHARED_KEY + ": " + + Boolean.toString(config.shared) + '\n'); + } + stream.close(); + // Set up list of home service providers returned by MOManager. final List<HomeSP> homeSPs = new ArrayList<HomeSP>(); - for (int i : new int[] {1, 2}) { - homeSPs.add(new HomeSP(null, FQDNS[i], new HashSet<Long>(), new HashSet<String>(), - new HashSet<Long>(), new ArrayList<Long>(), PROVIDER_FRIENDLY_NAMES[i], null, - new Credential(0, 0, null, false, null, null), null, 0, null, null, null)); + for (WifiConfiguration config : CONFIGS) { + if (config.FQDN != null) { + homeSPs.add(new HomeSP(null, config.FQDN, new HashSet<Long>(), + new HashSet<String>(), new HashSet<Long>(), new ArrayList<Long>(), + config.providerFriendlyName, null, new Credential(0, 0, null, false, null, + null), null, 0, null, null, null)); + } } when(mMOManager.loadAllSPs()).thenReturn(homeSPs); // Load network configurations. mConfigStore.loadConfiguredNetworks(); - // Verify that network configurations were loaded. For HotSpot 2.0 networks, this also - // verifies that the data read from the wpa_supplicant was correlated with the data read - // from the MOManager based on the FQDN stored in the wpa_supplicant's "id_str" variable. - final List<WifiConfiguration> configs = mConfigStore.getConfiguredNetworks(); - assertEquals(SSIDS.length, configs.size()); - for (int i = 0; i < SSIDS.length; ++i) { - WifiConfiguration config = null; - // Find the network configuration to test (getConfiguredNetworks() returns them in - // undefined order). - for (final WifiConfiguration candidate : configs) { - if (candidate.networkId == i) { - config = candidate; - break; + // Verify that network configurations were loaded and correlated correctly across the three + // sources. + verifyNetworkConfigs(CONFIGS, mConfiguredNetworks.valuesForAllUsers()); + } + + /** + * Verifies that handleUserSwitch() removes ephemeral network configurations, disables network + * configurations that should no longer be visible and enables network configurations that + * should become visible. + */ + private void verifyHandleUserSwitch(int oldUserId, int newUserId, + boolean makeOneConfigEphemeral) throws Exception { + addNetworks(); + switchUser(oldUserId); + + final WifiNative wifiNative = createNewWifiNativeMock(); + final Field lastSelectedConfigurationField = + WifiConfigStore.class.getDeclaredField("lastSelectedConfiguration"); + lastSelectedConfigurationField.setAccessible(true); + WifiConfiguration removedEphemeralConfig = null; + final Set<WifiConfiguration> oldUserOnlyConfigs = new HashSet<>(); + final Set<WifiConfiguration> newUserOnlyConfigs = new HashSet<>(); + final Set<WifiConfiguration> neitherUserConfigs = new HashSet<>(); + final Collection<WifiConfiguration> oldConfigs = mConfiguredNetworks.valuesForAllUsers(); + int expectedNumberOfConfigs = oldConfigs.size(); + for (WifiConfiguration config : oldConfigs) { + if (config.isVisibleToUser(oldUserId)) { + config.status = WifiConfiguration.Status.ENABLED; + if (config.isVisibleToUser(newUserId)) { + if (makeOneConfigEphemeral && removedEphemeralConfig == null) { + config.ephemeral = true; + lastSelectedConfigurationField.set(mConfigStore, config.configKey()); + removedEphemeralConfig = config; + } + } else { + oldUserOnlyConfigs.add(config); + } + } else { + config.status = WifiConfiguration.Status.DISABLED; + if (config.isVisibleToUser(newUserId)) { + newUserOnlyConfigs.add(config); + } else { + neitherUserConfigs.add(config); } } - assertNotNull(config); - assertEquals(SSIDS[i], config.SSID); - assertEquals(FQDNS[i], config.FQDN); - assertEquals(PROVIDER_FRIENDLY_NAMES[i], config.providerFriendlyName); - assertEquals(CONFIG_KEYS[i], config.configKey(false)); } + when(wifiNative.disableNetwork(anyInt())).thenReturn(true); + + switchUser(newUserId); + if (makeOneConfigEphemeral) { + // Verify that the ephemeral network configuration was removed. + assertNotNull(removedEphemeralConfig); + assertNull(mConfiguredNetworks.getForAllUsers(removedEphemeralConfig.networkId)); + assertNull(lastSelectedConfigurationField.get(mConfigStore)); + verify(wifiNative).removeNetwork(removedEphemeralConfig.networkId); + --expectedNumberOfConfigs; + } else { + assertNull(removedEphemeralConfig); + } + + // Verify that the other network configurations were revealed/hidden and enabled/disabled as + // appropriate. + final Collection<WifiConfiguration> newConfigs = mConfiguredNetworks.valuesForAllUsers(); + assertEquals(expectedNumberOfConfigs, newConfigs.size()); + for (WifiConfiguration config : newConfigs) { + if (oldUserOnlyConfigs.contains(config)) { + verify(wifiNative).disableNetwork(config.networkId); + assertEquals(WifiConfiguration.Status.DISABLED, config.status); + } else { + verify(wifiNative, never()).disableNetwork(config.networkId); + if (neitherUserConfigs.contains(config)) { + assertEquals(WifiConfiguration.Status.DISABLED, config.status); + } else { + assertEquals(WifiConfiguration.Status.ENABLED, config.status); + } + } + } + } + + /** + * Verifies that handleUserSwitch() behaves correctly when the user switch removes an ephemeral + * network configuration and reveals a private network configuration. + */ + public void testHandleUserSwitchWithEphemeral() throws Exception { + verifyHandleUserSwitch(USER_IDS[2], USER_IDS[0], true); + } + + /** + * Verifies that handleUserSwitch() behaves correctly when the user switch hides a private + * network configuration. + */ + public void testHandleUserSwitchWithoutEphemeral() throws Exception { + verifyHandleUserSwitch(USER_IDS[0], USER_IDS[2], false); } } diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtil.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtil.java new file mode 100644 index 000000000..c786741cf --- /dev/null +++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtil.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2016 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.server.wifi; + +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiEnterpriseConfig; + +/** + * Helper for creating and populating WifiConfigurations in unit tests. + */ +public class WifiConfigurationUtil { + public static WifiConfiguration generateWifiConfig(int networkId, int uid, String ssid, + boolean shared, boolean enabled, String fqdn, String providerFriendlyName) { + final WifiConfiguration config = new WifiConfiguration(); + config.SSID = ssid; + config.networkId = networkId; + config.creatorUid = uid; + config.shared = shared; + config.status = enabled ? WifiConfiguration.Status.ENABLED + : WifiConfiguration.Status.DISABLED; + if (fqdn != null) { + config.FQDN = fqdn; + config.providerFriendlyName = providerFriendlyName; + config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.SIM); + } + return config; + } +} |