blob: d8b4237a3659242cdd9e14edd677f7b0acc836d8 [file] [log] [blame]
/*
* 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.app.ActivityManager;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManagerInternal;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.IpConfiguration;
import android.net.ProxyInfo;
import android.net.StaticIpConfiguration;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
import android.net.wifi.WifiEnterpriseConfig;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiScanner;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.LocalLog;
import android.util.Log;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.wifi.WifiConfigStoreLegacy.WifiConfigStoreDataLegacy;
import com.android.server.wifi.hotspot2.PasspointManager;
import com.android.server.wifi.util.TelephonyUtil;
import com.android.server.wifi.util.WifiPermissionsUtil;
import com.android.server.wifi.util.WifiPermissionsWrapper;
import org.xmlpull.v1.XmlPullParserException;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* This class provides the APIs to manage configured Wi-Fi networks.
* It deals with the following:
* - Maintaining a list of configured networks for quick access.
* - Persisting the configurations to store when required.
* - Supporting WifiManager Public API calls:
* > addOrUpdateNetwork()
* > removeNetwork()
* > enableNetwork()
* > disableNetwork()
* - Handle user switching on multi-user devices.
*
* All network configurations retrieved from this class are copies of the original configuration
* stored in the internal database. So, any updates to the retrieved configuration object are
* meaningless and will not be reflected in the original database.
* This is done on purpose to ensure that only WifiConfigManager can modify configurations stored
* in the internal database. Any configuration updates should be triggered with appropriate helper
* methods of this class using the configuration's unique networkId.
*
* NOTE: These API's are not thread safe and should only be used from WifiStateMachine thread.
*/
public class WifiConfigManager {
/**
* String used to mask passwords to public interface.
*/
@VisibleForTesting
public static final String PASSWORD_MASK = "*";
/**
* Package name for SysUI. This is used to lookup the UID of SysUI which is used to allow
* Quick settings to modify network configurations.
*/
@VisibleForTesting
public static final String SYSUI_PACKAGE_NAME = "com.android.systemui";
/**
* Network Selection disable reason thresholds. These numbers are used to debounce network
* failures before we disable them.
* These are indexed using the disable reason constants defined in
* {@link android.net.wifi.WifiConfiguration.NetworkSelectionStatus}.
*/
@VisibleForTesting
public static final int[] NETWORK_SELECTION_DISABLE_THRESHOLD = {
-1, // threshold for NETWORK_SELECTION_ENABLE
1, // threshold for DISABLED_BAD_LINK
5, // threshold for DISABLED_ASSOCIATION_REJECTION
5, // threshold for DISABLED_AUTHENTICATION_FAILURE
5, // threshold for DISABLED_DHCP_FAILURE
5, // threshold for DISABLED_DNS_FAILURE
1, // threshold for DISABLED_WPS_START
6, // threshold for DISABLED_TLS_VERSION_MISMATCH
1, // threshold for DISABLED_AUTHENTICATION_NO_CREDENTIALS
1, // threshold for DISABLED_NO_INTERNET
1, // threshold for DISABLED_BY_WIFI_MANAGER
1, // threshold for DISABLED_BY_USER_SWITCH
1 // threshold for DISABLED_BY_WRONG_PASSWORD
};
/**
* Network Selection disable timeout for each kind of error. After the timeout milliseconds,
* enable the network again.
* These are indexed using the disable reason constants defined in
* {@link android.net.wifi.WifiConfiguration.NetworkSelectionStatus}.
*/
@VisibleForTesting
public static final int[] NETWORK_SELECTION_DISABLE_TIMEOUT_MS = {
Integer.MAX_VALUE, // threshold for NETWORK_SELECTION_ENABLE
15 * 60 * 1000, // threshold for DISABLED_BAD_LINK
5 * 60 * 1000, // threshold for DISABLED_ASSOCIATION_REJECTION
5 * 60 * 1000, // threshold for DISABLED_AUTHENTICATION_FAILURE
5 * 60 * 1000, // threshold for DISABLED_DHCP_FAILURE
5 * 60 * 1000, // threshold for DISABLED_DNS_FAILURE
0 * 60 * 1000, // threshold for DISABLED_WPS_START
Integer.MAX_VALUE, // threshold for DISABLED_TLS_VERSION
Integer.MAX_VALUE, // threshold for DISABLED_AUTHENTICATION_NO_CREDENTIALS
Integer.MAX_VALUE, // threshold for DISABLED_NO_INTERNET
Integer.MAX_VALUE, // threshold for DISABLED_BY_WIFI_MANAGER
Integer.MAX_VALUE, // threshold for DISABLED_BY_USER_SWITCH
Integer.MAX_VALUE // threshold for DISABLED_BY_WRONG_PASSWORD
};
/**
* Interface for other modules to listen to the saved network updated
* events.
*/
public interface OnSavedNetworkUpdateListener {
/**
* Invoked on saved network being added.
*/
void onSavedNetworkAdded(int networkId);
/**
* Invoked on saved network being enabled.
*/
void onSavedNetworkEnabled(int networkId);
/**
* Invoked on saved network being permanently disabled.
*/
void onSavedNetworkPermanentlyDisabled(int networkId);
/**
* Invoked on saved network being removed.
*/
void onSavedNetworkRemoved(int networkId);
/**
* Invoked on saved network being temporarily disabled.
*/
void onSavedNetworkTemporarilyDisabled(int networkId);
/**
* Invoked on saved network being updated.
*/
void onSavedNetworkUpdated(int networkId);
}
/**
* Max size of scan details to cache in {@link #mScanDetailCaches}.
*/
@VisibleForTesting
public static final int SCAN_CACHE_ENTRIES_MAX_SIZE = 192;
/**
* Once the size of the scan details in the cache {@link #mScanDetailCaches} exceeds
* {@link #SCAN_CACHE_ENTRIES_MAX_SIZE}, trim it down to this value so that we have some
* buffer time before the next eviction.
*/
@VisibleForTesting
public static final int SCAN_CACHE_ENTRIES_TRIM_SIZE = 128;
/**
* Link networks only if they have less than this number of scan cache entries.
*/
@VisibleForTesting
public static final int LINK_CONFIGURATION_MAX_SCAN_CACHE_ENTRIES = 6;
/**
* Link networks only if the bssid in scan results for the networks match in the first
* 16 ASCII chars in the bssid string. For example = "af:de:56;34:15:7"
*/
@VisibleForTesting
public static final int LINK_CONFIGURATION_BSSID_MATCH_LENGTH = 16;
/**
* Flags to be passed in for |canModifyNetwork| to decide if the change is minor and can
* bypass the lockdown checks.
*/
private static final boolean ALLOW_LOCKDOWN_CHECK_BYPASS = true;
private static final boolean DISALLOW_LOCKDOWN_CHECK_BYPASS = false;
/**
* Log tag for this class.
*/
private static final String TAG = "WifiConfigManager";
/**
* Maximum age of scan results that can be used for averaging out RSSI value.
*/
private static final int SCAN_RESULT_MAXIMUM_AGE_MS = 40000;
/**
* General sorting algorithm of all networks for scanning purposes:
* Place the configurations in descending order of their |numAssociation| values. If networks
* have the same |numAssociation|, place the configurations with
* |lastSeenInQualifiedNetworkSelection| set first.
*/
private static final WifiConfigurationUtil.WifiConfigurationComparator sScanListComparator =
new WifiConfigurationUtil.WifiConfigurationComparator() {
@Override
public int compareNetworksWithSameStatus(WifiConfiguration a, WifiConfiguration b) {
if (a.numAssociation != b.numAssociation) {
return Long.compare(b.numAssociation, a.numAssociation);
} else {
boolean isConfigALastSeen =
a.getNetworkSelectionStatus()
.getSeenInLastQualifiedNetworkSelection();
boolean isConfigBLastSeen =
b.getNetworkSelectionStatus()
.getSeenInLastQualifiedNetworkSelection();
return Boolean.compare(isConfigBLastSeen, isConfigALastSeen);
}
}
};
/**
* List of external dependencies for WifiConfigManager.
*/
private final Context mContext;
private final Clock mClock;
private final UserManager mUserManager;
private final BackupManagerProxy mBackupManagerProxy;
private final TelephonyManager mTelephonyManager;
private final WifiKeyStore mWifiKeyStore;
private final WifiConfigStore mWifiConfigStore;
private final WifiConfigStoreLegacy mWifiConfigStoreLegacy;
private final WifiPermissionsUtil mWifiPermissionsUtil;
private final WifiPermissionsWrapper mWifiPermissionsWrapper;
/**
* Local log used for debugging any WifiConfigManager issues.
*/
private final LocalLog mLocalLog =
new LocalLog(ActivityManager.isLowRamDeviceStatic() ? 128 : 256);
/**
* Map of configured networks with network id as the key.
*/
private final ConfigurationMap mConfiguredNetworks;
/**
* Stores a map of NetworkId to ScanDetailCache.
*/
private final Map<Integer, ScanDetailCache> mScanDetailCaches;
/**
* Framework keeps a list of ephemeral SSIDs that where deleted by user,
* so as, framework knows not to autoconnect again those SSIDs based on scorer input.
* The list is never cleared up.
* The SSIDs are encoded in a String as per definition of WifiConfiguration.SSID field.
*/
private final Set<String> mDeletedEphemeralSSIDs;
/**
* Flag to indicate if only networks with the same psk should be linked.
* TODO(b/30706406): Remove this flag if unused.
*/
private final boolean mOnlyLinkSameCredentialConfigurations;
/**
* Number of channels to scan for during partial scans initiated while connected.
*/
private final int mMaxNumActiveChannelsForPartialScans;
/**
* Verbose logging flag. Toggled by developer options.
*/
private boolean mVerboseLoggingEnabled = false;
/**
* Current logged in user ID.
*/
private int mCurrentUserId = UserHandle.USER_SYSTEM;
/**
* Flag to indicate that the new user's store has not yet been read since user switch.
* Initialize this flag to |true| to trigger a read on the first user unlock after
* bootup.
*/
private boolean mPendingUnlockStoreRead = true;
/**
* Flag to indicate if we have performed a read from store at all. This is used to gate
* any user unlock/switch operations until we read the store (Will happen if wifi is disabled
* when user updates from N to O).
*/
private boolean mPendingStoreRead = true;
/**
* Flag to indicate if the user unlock was deferred until the store load occurs.
*/
private boolean mDeferredUserUnlockRead = false;
/**
* Flag to indicate if SIM is present.
*/
private boolean mSimPresent = false;
/**
* This is keeping track of the next network ID to be assigned. Any new networks will be
* assigned |mNextNetworkId| as network ID.
*/
private int mNextNetworkId = 0;
/**
* UID of system UI. This uid is allowed to modify network configurations regardless of which
* user is logged in.
*/
private int mSystemUiUid = -1;
/**
* This is used to remember which network was selected successfully last by an app. This is set
* when an app invokes {@link #enableNetwork(int, boolean, int)} with |disableOthers| flag set.
* This is the only way for an app to request connection to a specific network using the
* {@link WifiManager} API's.
*/
private int mLastSelectedNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
private long mLastSelectedTimeStamp =
WifiConfiguration.NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP;
// Store data for network list and deleted ephemeral SSID list. Used for serializing
// parsing data to/from the config store.
private final NetworkListStoreData mNetworkListStoreData;
private final DeletedEphemeralSsidsStoreData mDeletedEphemeralSsidsStoreData;
// Store the saved network update listener.
private OnSavedNetworkUpdateListener mListener = null;
/**
* Create new instance of WifiConfigManager.
*/
WifiConfigManager(
Context context, Clock clock, UserManager userManager,
TelephonyManager telephonyManager, WifiKeyStore wifiKeyStore,
WifiConfigStore wifiConfigStore, WifiConfigStoreLegacy wifiConfigStoreLegacy,
WifiPermissionsUtil wifiPermissionsUtil,
WifiPermissionsWrapper wifiPermissionsWrapper,
NetworkListStoreData networkListStoreData,
DeletedEphemeralSsidsStoreData deletedEphemeralSsidsStoreData) {
mContext = context;
mClock = clock;
mUserManager = userManager;
mBackupManagerProxy = new BackupManagerProxy();
mTelephonyManager = telephonyManager;
mWifiKeyStore = wifiKeyStore;
mWifiConfigStore = wifiConfigStore;
mWifiConfigStoreLegacy = wifiConfigStoreLegacy;
mWifiPermissionsUtil = wifiPermissionsUtil;
mWifiPermissionsWrapper = wifiPermissionsWrapper;
mConfiguredNetworks = new ConfigurationMap(userManager);
mScanDetailCaches = new HashMap<>(16, 0.75f);
mDeletedEphemeralSSIDs = new HashSet<>();
// Register store data for network list and deleted ephemeral SSIDs.
mNetworkListStoreData = networkListStoreData;
mDeletedEphemeralSsidsStoreData = deletedEphemeralSsidsStoreData;
mWifiConfigStore.registerStoreData(mNetworkListStoreData);
mWifiConfigStore.registerStoreData(mDeletedEphemeralSsidsStoreData);
mOnlyLinkSameCredentialConfigurations = mContext.getResources().getBoolean(
R.bool.config_wifi_only_link_same_credential_configurations);
mMaxNumActiveChannelsForPartialScans = mContext.getResources().getInteger(
R.integer.config_wifi_framework_associated_partial_scan_max_num_active_channels);
try {
mSystemUiUid = mContext.getPackageManager().getPackageUidAsUser(SYSUI_PACKAGE_NAME,
PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Unable to resolve SystemUI's UID.");
}
}
/**
* Construct the string to be put in the |creationTime| & |updateTime| elements of
* WifiConfiguration from the provided wall clock millis.
*
* @param wallClockMillis Time in milliseconds to be converted to string.
*/
@VisibleForTesting
public static String createDebugTimeStampString(long wallClockMillis) {
StringBuilder sb = new StringBuilder();
sb.append("time=");
Calendar c = Calendar.getInstance();
c.setTimeInMillis(wallClockMillis);
sb.append(String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c));
return sb.toString();
}
/**
* Enable/disable verbose logging in WifiConfigManager & its helper classes.
*/
public void enableVerboseLogging(int verbose) {
if (verbose > 0) {
mVerboseLoggingEnabled = true;
} else {
mVerboseLoggingEnabled = false;
}
mWifiConfigStore.enableVerboseLogging(mVerboseLoggingEnabled);
mWifiKeyStore.enableVerboseLogging(mVerboseLoggingEnabled);
}
/**
* Helper method to mask all passwords/keys from the provided WifiConfiguration object. This
* is needed when the network configurations are being requested via the public WifiManager
* API's.
* This currently masks the following elements: psk, wepKeys & enterprise config password.
*/
private void maskPasswordsInWifiConfiguration(WifiConfiguration configuration) {
if (!TextUtils.isEmpty(configuration.preSharedKey)) {
configuration.preSharedKey = PASSWORD_MASK;
}
if (configuration.wepKeys != null) {
for (int i = 0; i < configuration.wepKeys.length; i++) {
if (!TextUtils.isEmpty(configuration.wepKeys[i])) {
configuration.wepKeys[i] = PASSWORD_MASK;
}
}
}
if (!TextUtils.isEmpty(configuration.enterpriseConfig.getPassword())) {
configuration.enterpriseConfig.setPassword(PASSWORD_MASK);
}
}
/**
* Helper method to create a copy of the provided internal WifiConfiguration object to be
* passed to external modules.
*
* @param configuration provided WifiConfiguration object.
* @param maskPasswords Mask passwords or not.
* @return Copy of the WifiConfiguration object.
*/
private WifiConfiguration createExternalWifiConfiguration(
WifiConfiguration configuration, boolean maskPasswords) {
WifiConfiguration network = new WifiConfiguration(configuration);
if (maskPasswords) {
maskPasswordsInWifiConfiguration(network);
}
return network;
}
/**
* Fetch the list of currently configured networks maintained in WifiConfigManager.
*
* This retrieves a copy of the internal configurations maintained by WifiConfigManager and
* should be used for any public interfaces.
*
* @param savedOnly Retrieve only saved networks.
* @param maskPasswords Mask passwords or not.
* @return List of WifiConfiguration objects representing the networks.
*/
private List<WifiConfiguration> getConfiguredNetworks(
boolean savedOnly, boolean maskPasswords) {
List<WifiConfiguration> networks = new ArrayList<>();
for (WifiConfiguration config : getInternalConfiguredNetworks()) {
if (savedOnly && config.ephemeral) {
continue;
}
networks.add(createExternalWifiConfiguration(config, maskPasswords));
}
return networks;
}
/**
* Retrieves the list of all configured networks with passwords masked.
*
* @return List of WifiConfiguration objects representing the networks.
*/
public List<WifiConfiguration> getConfiguredNetworks() {
return getConfiguredNetworks(false, true);
}
/**
* Retrieves the list of all configured networks with the passwords in plaintext.
*
* WARNING: Don't use this to pass network configurations to external apps. Should only be
* sent to system apps/wifi stack, when there is a need for passwords in plaintext.
* TODO: Need to understand the current use case of this API.
*
* @return List of WifiConfiguration objects representing the networks.
*/
public List<WifiConfiguration> getConfiguredNetworksWithPasswords() {
return getConfiguredNetworks(false, false);
}
/**
* Retrieves the list of all configured networks with the passwords masked.
*
* @return List of WifiConfiguration objects representing the networks.
*/
public List<WifiConfiguration> getSavedNetworks() {
return getConfiguredNetworks(true, true);
}
/**
* Retrieves the configured network corresponding to the provided networkId with password
* masked.
*
* @param networkId networkId of the requested network.
* @return WifiConfiguration object if found, null otherwise.
*/
public WifiConfiguration getConfiguredNetwork(int networkId) {
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
if (config == null) {
return null;
}
// Create a new configuration object with the passwords masked to send out to the external
// world.
return createExternalWifiConfiguration(config, true);
}
/**
* Retrieves the configured network corresponding to the provided config key with password
* masked.
*
* @param configKey configKey of the requested network.
* @return WifiConfiguration object if found, null otherwise.
*/
public WifiConfiguration getConfiguredNetwork(String configKey) {
WifiConfiguration config = getInternalConfiguredNetwork(configKey);
if (config == null) {
return null;
}
// Create a new configuration object with the passwords masked to send out to the external
// world.
return createExternalWifiConfiguration(config, true);
}
/**
* Retrieves the configured network corresponding to the provided networkId with password
* in plaintext.
*
* WARNING: Don't use this to pass network configurations to external apps. Should only be
* sent to system apps/wifi stack, when there is a need for passwords in plaintext.
*
* @param networkId networkId of the requested network.
* @return WifiConfiguration object if found, null otherwise.
*/
public WifiConfiguration getConfiguredNetworkWithPassword(int networkId) {
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
if (config == null) {
return null;
}
// Create a new configuration object without the passwords masked to send out to the
// external world.
return createExternalWifiConfiguration(config, false);
}
/**
* Helper method to retrieve all the internal WifiConfiguration objects corresponding to all
* the networks in our database.
*/
private Collection<WifiConfiguration> getInternalConfiguredNetworks() {
return mConfiguredNetworks.valuesForCurrentUser();
}
/**
* Helper method to retrieve the internal WifiConfiguration object corresponding to the
* provided configuration in our database.
* This first attempts to find the network using the provided network ID in configuration,
* else it attempts to find a matching configuration using the configKey.
*/
private WifiConfiguration getInternalConfiguredNetwork(WifiConfiguration config) {
WifiConfiguration internalConfig = mConfiguredNetworks.getForCurrentUser(config.networkId);
if (internalConfig != null) {
return internalConfig;
}
internalConfig = mConfiguredNetworks.getByConfigKeyForCurrentUser(config.configKey());
if (internalConfig == null) {
Log.e(TAG, "Cannot find network with networkId " + config.networkId
+ " or configKey " + config.configKey());
}
return internalConfig;
}
/**
* Helper method to retrieve the internal WifiConfiguration object corresponding to the
* provided network ID in our database.
*/
private WifiConfiguration getInternalConfiguredNetwork(int networkId) {
if (networkId == WifiConfiguration.INVALID_NETWORK_ID) {
Log.w(TAG, "Looking up network with invalid networkId -1");
return null;
}
WifiConfiguration internalConfig = mConfiguredNetworks.getForCurrentUser(networkId);
if (internalConfig == null) {
Log.e(TAG, "Cannot find network with networkId " + networkId);
}
return internalConfig;
}
/**
* Helper method to retrieve the internal WifiConfiguration object corresponding to the
* provided configKey in our database.
*/
private WifiConfiguration getInternalConfiguredNetwork(String configKey) {
WifiConfiguration internalConfig =
mConfiguredNetworks.getByConfigKeyForCurrentUser(configKey);
if (internalConfig == null) {
Log.e(TAG, "Cannot find network with configKey " + configKey);
}
return internalConfig;
}
/**
* Method to send out the configured networks change broadcast when a single network
* configuration is changed.
*
* @param network WifiConfiguration corresponding to the network that was 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 sendConfiguredNetworkChangedBroadcast(
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);
// Create a new WifiConfiguration with passwords masked before we send it out.
WifiConfiguration broadcastNetwork = new WifiConfiguration(network);
maskPasswordsInWifiConfiguration(broadcastNetwork);
intent.putExtra(WifiManager.EXTRA_WIFI_CONFIGURATION, broadcastNetwork);
intent.putExtra(WifiManager.EXTRA_CHANGE_REASON, reason);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
/**
* Method to send out the configured networks change broadcast when multiple network
* configurations are changed.
*/
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);
}
/**
* Checks if |uid| has permission to modify the provided configuration.
*
* @param config WifiConfiguration object corresponding to the network to be modified.
* @param uid UID of the app requesting the modification.
* @param ignoreLockdown Ignore the configuration lockdown checks for connection attempts.
*/
private boolean canModifyNetwork(WifiConfiguration config, int uid, boolean ignoreLockdown) {
// Passpoint configurations are generated and managed by PasspointManager. They can be
// added by either PasspointNetworkEvaluator (for auto connection) or Settings app
// (for manual connection), and need to be removed once the connection is completed.
// Since it is "owned" by us, so always allow us to modify them.
if (config.isPasspoint() && uid == Process.WIFI_UID) {
return true;
}
// EAP-SIM/AKA/AKA' network needs framework to update the anonymous identity provided
// by authenticator back to the WifiConfiguration object.
// Since it is "owned" by us, so always allow us to modify them.
if (config.enterpriseConfig != null
&& uid == Process.WIFI_UID
&& TelephonyUtil.isSimEapMethod(config.enterpriseConfig.getEapMethod())) {
return true;
}
final DevicePolicyManagerInternal dpmi = LocalServices.getService(
DevicePolicyManagerInternal.class);
final boolean isUidDeviceOwner = dpmi != null && dpmi.isActiveAdminWithPolicy(uid,
DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
// If |uid| corresponds to the device owner, allow all modifications.
if (isUidDeviceOwner) {
return true;
}
final boolean isCreator = (config.creatorUid == uid);
// Check if the |uid| holds the |NETWORK_SETTINGS| permission if the caller asks us to
// bypass the lockdown checks.
if (ignoreLockdown) {
return mWifiPermissionsUtil.checkNetworkSettingsPermission(uid);
}
// Check if device has DPM capability. If it has and |dpmi| is still null, then we
// treat this case with suspicion and bail out.
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)
&& dpmi == null) {
Log.w(TAG, "Error retrieving DPMI service.");
return false;
}
// WiFi config lockdown related logic. At this point we know uid is NOT a Device Owner.
final boolean isConfigEligibleForLockdown = dpmi != null && dpmi.isActiveAdminWithPolicy(
config.creatorUid, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
if (!isConfigEligibleForLockdown) {
return isCreator || mWifiPermissionsUtil.checkNetworkSettingsPermission(uid);
}
final ContentResolver resolver = mContext.getContentResolver();
final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver,
Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0;
return !isLockdownFeatureEnabled
&& mWifiPermissionsUtil.checkNetworkSettingsPermission(uid);
}
/**
* Method to check if the provided UID belongs to the current foreground user or some other
* app (only SysUI today) running on behalf of the user.
* This is used to prevent any background user apps from modifying network configurations.
*
* @param uid uid of the app.
* @return true if the UID belongs to the current foreground app or SystemUI, false otherwise.
*/
private boolean doesUidBelongToCurrentUser(int uid) {
return (WifiConfigurationUtil.doesUidBelongToAnyProfile(
uid, mUserManager.getProfiles(mCurrentUserId)) || (uid == mSystemUiUid));
}
/**
* Copy over public elements from an external WifiConfiguration object to the internal
* configuration object if element has been set in the provided external WifiConfiguration.
* The only exception is the hidden |IpConfiguration| parameters, these need to be copied over
* for every update.
*
* This method updates all elements that are common to both network addition & update.
* The following fields of {@link WifiConfiguration} are not copied from external configs:
* > networkId - These are allocated by Wi-Fi stack internally for any new configurations.
* > status - The status needs to be explicitly updated using
* {@link WifiManager#enableNetwork(int, boolean)} or
* {@link WifiManager#disableNetwork(int)}.
*
* @param internalConfig WifiConfiguration object in our internal map.
* @param externalConfig WifiConfiguration object provided from the external API.
*/
private void mergeWithInternalWifiConfiguration(
WifiConfiguration internalConfig, WifiConfiguration externalConfig) {
if (externalConfig.SSID != null) {
internalConfig.SSID = externalConfig.SSID;
}
if (externalConfig.BSSID != null) {
internalConfig.BSSID = externalConfig.BSSID.toLowerCase();
}
internalConfig.hiddenSSID = externalConfig.hiddenSSID;
if (externalConfig.preSharedKey != null
&& !externalConfig.preSharedKey.equals(PASSWORD_MASK)) {
internalConfig.preSharedKey = externalConfig.preSharedKey;
}
// Modify only wep keys are present in the provided configuration. This is a little tricky
// because there is no easy way to tell if the app is actually trying to null out the
// existing keys or not.
if (externalConfig.wepKeys != null) {
boolean hasWepKey = false;
for (int i = 0; i < internalConfig.wepKeys.length; i++) {
if (externalConfig.wepKeys[i] != null
&& !externalConfig.wepKeys[i].equals(PASSWORD_MASK)) {
internalConfig.wepKeys[i] = externalConfig.wepKeys[i];
hasWepKey = true;
}
}
if (hasWepKey) {
internalConfig.wepTxKeyIndex = externalConfig.wepTxKeyIndex;
}
}
if (externalConfig.FQDN != null) {
internalConfig.FQDN = externalConfig.FQDN;
}
if (externalConfig.providerFriendlyName != null) {
internalConfig.providerFriendlyName = externalConfig.providerFriendlyName;
}
if (externalConfig.roamingConsortiumIds != null) {
internalConfig.roamingConsortiumIds = externalConfig.roamingConsortiumIds.clone();
}
// Copy over all the auth/protocol/key mgmt parameters if set.
if (externalConfig.allowedAuthAlgorithms != null
&& !externalConfig.allowedAuthAlgorithms.isEmpty()) {
internalConfig.allowedAuthAlgorithms =
(BitSet) externalConfig.allowedAuthAlgorithms.clone();
}
if (externalConfig.allowedProtocols != null
&& !externalConfig.allowedProtocols.isEmpty()) {
internalConfig.allowedProtocols = (BitSet) externalConfig.allowedProtocols.clone();
}
if (externalConfig.allowedKeyManagement != null
&& !externalConfig.allowedKeyManagement.isEmpty()) {
internalConfig.allowedKeyManagement =
(BitSet) externalConfig.allowedKeyManagement.clone();
}
if (externalConfig.allowedPairwiseCiphers != null
&& !externalConfig.allowedPairwiseCiphers.isEmpty()) {
internalConfig.allowedPairwiseCiphers =
(BitSet) externalConfig.allowedPairwiseCiphers.clone();
}
if (externalConfig.allowedGroupCiphers != null
&& !externalConfig.allowedGroupCiphers.isEmpty()) {
internalConfig.allowedGroupCiphers =
(BitSet) externalConfig.allowedGroupCiphers.clone();
}
// Copy over the |IpConfiguration| parameters if set.
if (externalConfig.getIpConfiguration() != null) {
IpConfiguration.IpAssignment ipAssignment = externalConfig.getIpAssignment();
if (ipAssignment != IpConfiguration.IpAssignment.UNASSIGNED) {
internalConfig.setIpAssignment(ipAssignment);
if (ipAssignment == IpConfiguration.IpAssignment.STATIC) {
internalConfig.setStaticIpConfiguration(
new StaticIpConfiguration(externalConfig.getStaticIpConfiguration()));
}
}
IpConfiguration.ProxySettings proxySettings = externalConfig.getProxySettings();
if (proxySettings != IpConfiguration.ProxySettings.UNASSIGNED) {
internalConfig.setProxySettings(proxySettings);
if (proxySettings == IpConfiguration.ProxySettings.PAC
|| proxySettings == IpConfiguration.ProxySettings.STATIC) {
internalConfig.setHttpProxy(new ProxyInfo(externalConfig.getHttpProxy()));
}
}
}
// Copy over the |WifiEnterpriseConfig| parameters if set.
if (externalConfig.enterpriseConfig != null) {
internalConfig.enterpriseConfig.copyFromExternal(
externalConfig.enterpriseConfig, PASSWORD_MASK);
}
}
/**
* Set all the exposed defaults in the newly created WifiConfiguration object.
* These fields have a default value advertised in our public documentation. The only exception
* is the hidden |IpConfiguration| parameters, these have a default value even though they're
* hidden.
*
* @param configuration provided WifiConfiguration object.
*/
private void setDefaultsInWifiConfiguration(WifiConfiguration configuration) {
configuration.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
configuration.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
configuration.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);
configuration.setIpAssignment(IpConfiguration.IpAssignment.DHCP);
configuration.setProxySettings(IpConfiguration.ProxySettings.NONE);
configuration.status = WifiConfiguration.Status.DISABLED;
configuration.getNetworkSelectionStatus().setNetworkSelectionStatus(
NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED);
}
/**
* Create a new internal WifiConfiguration object by copying over parameters from the provided
* external configuration and set defaults for the appropriate parameters.
*
* @param externalConfig WifiConfiguration object provided from the external API.
* @return New WifiConfiguration object with parameters merged from the provided external
* configuration.
*/
private WifiConfiguration createNewInternalWifiConfigurationFromExternal(
WifiConfiguration externalConfig, int uid) {
WifiConfiguration newInternalConfig = new WifiConfiguration();
// First allocate a new network ID for the configuration.
newInternalConfig.networkId = mNextNetworkId++;
// First set defaults in the new configuration created.
setDefaultsInWifiConfiguration(newInternalConfig);
// Copy over all the public elements from the provided configuration.
mergeWithInternalWifiConfiguration(newInternalConfig, externalConfig);
// Copy over the hidden configuration parameters. These are the only parameters used by
// system apps to indicate some property about the network being added.
// These are only copied over for network additions and ignored for network updates.
newInternalConfig.requirePMF = externalConfig.requirePMF;
newInternalConfig.noInternetAccessExpected = externalConfig.noInternetAccessExpected;
newInternalConfig.ephemeral = externalConfig.ephemeral;
newInternalConfig.meteredHint = externalConfig.meteredHint;
newInternalConfig.useExternalScores = externalConfig.useExternalScores;
newInternalConfig.shared = externalConfig.shared;
// Add debug information for network addition.
newInternalConfig.creatorUid = newInternalConfig.lastUpdateUid = uid;
newInternalConfig.creatorName = newInternalConfig.lastUpdateName =
mContext.getPackageManager().getNameForUid(uid);
newInternalConfig.creationTime = newInternalConfig.updateTime =
createDebugTimeStampString(mClock.getWallClockMillis());
return newInternalConfig;
}
/**
* Create a new internal WifiConfiguration object by copying over parameters from the provided
* external configuration to a copy of the existing internal WifiConfiguration object.
*
* @param internalConfig WifiConfiguration object in our internal map.
* @param externalConfig WifiConfiguration object provided from the external API.
* @return Copy of existing WifiConfiguration object with parameters merged from the provided
* configuration.
*/
private WifiConfiguration updateExistingInternalWifiConfigurationFromExternal(
WifiConfiguration internalConfig, WifiConfiguration externalConfig, int uid) {
WifiConfiguration newInternalConfig = new WifiConfiguration(internalConfig);
// Copy over all the public elements from the provided configuration.
mergeWithInternalWifiConfiguration(newInternalConfig, externalConfig);
// Add debug information for network update.
newInternalConfig.lastUpdateUid = uid;
newInternalConfig.lastUpdateName = mContext.getPackageManager().getNameForUid(uid);
newInternalConfig.updateTime = createDebugTimeStampString(mClock.getWallClockMillis());
return newInternalConfig;
}
/**
* Add a network or update a network configuration to our database.
* If the supplied networkId is INVALID_NETWORK_ID, we create a new empty
* network configuration. Otherwise, the networkId should refer to an existing configuration.
*
* @param config provided WifiConfiguration object.
* @param uid UID of the app requesting the network addition/deletion.
* @return NetworkUpdateResult object representing status of the update.
*/
private NetworkUpdateResult addOrUpdateNetworkInternal(WifiConfiguration config, int uid) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Adding/Updating network " + config.getPrintableSsid());
}
WifiConfiguration newInternalConfig = null;
// First check if we already have a network with the provided network id or configKey.
WifiConfiguration existingInternalConfig = getInternalConfiguredNetwork(config);
// No existing network found. So, potentially a network add.
if (existingInternalConfig == null) {
if (!WifiConfigurationUtil.validate(config, WifiConfigurationUtil.VALIDATE_FOR_ADD)) {
Log.e(TAG, "Cannot add network with invalid config");
return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
}
newInternalConfig = createNewInternalWifiConfigurationFromExternal(config, uid);
// Since the original config provided may have had an empty
// {@link WifiConfiguration#allowedKeyMgmt} field, check again if we already have a
// network with the the same configkey.
existingInternalConfig = getInternalConfiguredNetwork(newInternalConfig.configKey());
}
// Existing network found. So, a network update.
if (existingInternalConfig != null) {
if (!WifiConfigurationUtil.validate(
config, WifiConfigurationUtil.VALIDATE_FOR_UPDATE)) {
Log.e(TAG, "Cannot update network with invalid config");
return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
}
// Check for the app's permission before we let it update this network.
if (!canModifyNetwork(existingInternalConfig, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
+ config.configKey());
return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
}
newInternalConfig =
updateExistingInternalWifiConfigurationFromExternal(
existingInternalConfig, config, uid);
}
// Only add networks with proxy settings if the user has permission to
if (WifiConfigurationUtil.hasProxyChanged(existingInternalConfig, newInternalConfig)
&& !canModifyProxySettings(uid)) {
Log.e(TAG, "UID " + uid + " does not have permission to modify proxy Settings "
+ config.configKey() + ". Must have NETWORK_SETTINGS,"
+ " or be device or profile owner.");
return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
}
// Update the keys for non-Passpoint enterprise networks. For Passpoint, the certificates
// and keys are installed at the time the provider is installed.
if (config.enterpriseConfig != null
&& config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE
&& !config.isPasspoint()) {
if (!(mWifiKeyStore.updateNetworkKeys(newInternalConfig, existingInternalConfig))) {
return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
}
}
boolean newNetwork = (existingInternalConfig == null);
// This is needed to inform IpManager about any IP configuration changes.
boolean hasIpChanged =
newNetwork || WifiConfigurationUtil.hasIpChanged(
existingInternalConfig, newInternalConfig);
boolean hasProxyChanged =
newNetwork || WifiConfigurationUtil.hasProxyChanged(
existingInternalConfig, newInternalConfig);
// Reset the |hasEverConnected| flag if the credential parameters changed in this update.
boolean hasCredentialChanged =
newNetwork || WifiConfigurationUtil.hasCredentialChanged(
existingInternalConfig, newInternalConfig);
if (hasCredentialChanged) {
newInternalConfig.getNetworkSelectionStatus().setHasEverConnected(false);
}
// Add it to our internal map. This will replace any existing network configuration for
// updates.
try {
mConfiguredNetworks.put(newInternalConfig);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Failed to add network to config map", e);
return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
}
if (mDeletedEphemeralSSIDs.remove(config.SSID)) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Removed from ephemeral blacklist: " + config.SSID);
}
}
// Stage the backup of the SettingsProvider package which backs this up.
mBackupManagerProxy.notifyDataChanged();
NetworkUpdateResult result =
new NetworkUpdateResult(hasIpChanged, hasProxyChanged, hasCredentialChanged);
result.setIsNewNetwork(newNetwork);
result.setNetworkId(newInternalConfig.networkId);
localLog("addOrUpdateNetworkInternal: added/updated config."
+ " netId=" + newInternalConfig.networkId
+ " configKey=" + newInternalConfig.configKey()
+ " uid=" + Integer.toString(newInternalConfig.creatorUid)
+ " name=" + newInternalConfig.creatorName);
return result;
}
/**
* Add a network or update a network configuration to our database.
* If the supplied networkId is INVALID_NETWORK_ID, we create a new empty
* network configuration. Otherwise, the networkId should refer to an existing configuration.
*
* @param config provided WifiConfiguration object.
* @param uid UID of the app requesting the network addition/modification.
* @return NetworkUpdateResult object representing status of the update.
*/
public NetworkUpdateResult addOrUpdateNetwork(WifiConfiguration config, int uid) {
if (!doesUidBelongToCurrentUser(uid)) {
Log.e(TAG, "UID " + uid + " not visible to the current user");
return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
}
if (config == null) {
Log.e(TAG, "Cannot add/update network with null config");
return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
}
if (mPendingStoreRead) {
Log.e(TAG, "Cannot add/update network before store is read!");
return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
}
NetworkUpdateResult result = addOrUpdateNetworkInternal(config, uid);
if (!result.isSuccess()) {
Log.e(TAG, "Failed to add/update network " + config.getPrintableSsid());
return result;
}
WifiConfiguration newConfig = getInternalConfiguredNetwork(result.getNetworkId());
sendConfiguredNetworkChangedBroadcast(
newConfig,
result.isNewNetwork()
? WifiManager.CHANGE_REASON_ADDED
: WifiManager.CHANGE_REASON_CONFIG_CHANGE);
// Unless the added network is ephemeral or Passpoint, persist the network update/addition.
if (!config.ephemeral && !config.isPasspoint()) {
saveToStore(true);
if (mListener != null) {
if (result.isNewNetwork()) {
mListener.onSavedNetworkAdded(newConfig.networkId);
} else {
mListener.onSavedNetworkUpdated(newConfig.networkId);
}
}
}
return result;
}
/**
* Removes the specified network configuration from our database.
*
* @param config provided WifiConfiguration object.
* @return true if successful, false otherwise.
*/
private boolean removeNetworkInternal(WifiConfiguration config) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Removing network " + config.getPrintableSsid());
}
// Remove any associated enterprise keys for non-Passpoint networks.
if (!config.isPasspoint() && config.enterpriseConfig != null
&& config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE) {
mWifiKeyStore.removeKeys(config.enterpriseConfig);
}
removeConnectChoiceFromAllNetworks(config.configKey());
mConfiguredNetworks.remove(config.networkId);
mScanDetailCaches.remove(config.networkId);
// Stage the backup of the SettingsProvider package which backs this up.
mBackupManagerProxy.notifyDataChanged();
localLog("removeNetworkInternal: removed config."
+ " netId=" + config.networkId
+ " configKey=" + config.configKey());
return true;
}
/**
* Removes the specified network configuration from our database.
*
* @param networkId network ID of the provided network.
* @param uid UID of the app requesting the network deletion.
* @return true if successful, false otherwise.
*/
public boolean removeNetwork(int networkId, int uid) {
if (!doesUidBelongToCurrentUser(uid)) {
Log.e(TAG, "UID " + uid + " not visible to the current user");
return false;
}
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
if (config == null) {
return false;
}
if (!canModifyNetwork(config, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
Log.e(TAG, "UID " + uid + " does not have permission to delete configuration "
+ config.configKey());
return false;
}
if (!removeNetworkInternal(config)) {
Log.e(TAG, "Failed to remove network " + config.getPrintableSsid());
return false;
}
if (networkId == mLastSelectedNetworkId) {
clearLastSelectedNetwork();
}
sendConfiguredNetworkChangedBroadcast(config, WifiManager.CHANGE_REASON_REMOVED);
// Unless the removed network is ephemeral or Passpoint, persist the network removal.
if (!config.ephemeral && !config.isPasspoint()) {
saveToStore(true);
if (mListener != null) mListener.onSavedNetworkRemoved(networkId);
}
return true;
}
/**
* Remove all networks associated with an application.
*
* @param app Application info of the package of networks to remove.
* @return the {@link Set} of networks that were removed by this call. Networks which matched
* but failed to remove are omitted from this set.
*/
public Set<Integer> removeNetworksForApp(ApplicationInfo app) {
if (app == null || app.packageName == null) {
return Collections.<Integer>emptySet();
}
Log.d(TAG, "Remove all networks for app " + app);
Set<Integer> removedNetworks = new ArraySet<>();
WifiConfiguration[] copiedConfigs =
mConfiguredNetworks.valuesForAllUsers().toArray(new WifiConfiguration[0]);
for (WifiConfiguration config : copiedConfigs) {
if (app.uid != config.creatorUid || !app.packageName.equals(config.creatorName)) {
continue;
}
localLog("Removing network " + config.SSID
+ ", application \"" + app.packageName + "\" uninstalled"
+ " from user " + UserHandle.getUserId(app.uid));
if (removeNetwork(config.networkId, mSystemUiUid)) {
removedNetworks.add(config.networkId);
}
}
return removedNetworks;
}
/**
* Remove all networks associated with a user.
*
* @param userId The identifier of the user which is being removed.
* @return the {@link Set} of networks that were removed by this call. Networks which matched
* but failed to remove are omitted from this set.
*/
Set<Integer> removeNetworksForUser(int userId) {
Log.d(TAG, "Remove all networks for user " + userId);
Set<Integer> removedNetworks = new ArraySet<>();
WifiConfiguration[] copiedConfigs =
mConfiguredNetworks.valuesForAllUsers().toArray(new WifiConfiguration[0]);
for (WifiConfiguration config : copiedConfigs) {
if (userId != UserHandle.getUserId(config.creatorUid)) {
continue;
}
localLog("Removing network " + config.SSID + ", user " + userId + " removed");
if (removeNetwork(config.networkId, mSystemUiUid)) {
removedNetworks.add(config.networkId);
}
}
return removedNetworks;
}
/**
* Iterates through the internal list of configured networks and removes any ephemeral or
* passpoint network configurations which are transient in nature.
*
* @return true if a network was removed, false otherwise.
*/
public boolean removeAllEphemeralOrPasspointConfiguredNetworks() {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Removing all passpoint or ephemeral configured networks");
}
boolean didRemove = false;
WifiConfiguration[] copiedConfigs =
mConfiguredNetworks.valuesForAllUsers().toArray(new WifiConfiguration[0]);
for (WifiConfiguration config : copiedConfigs) {
if (config.isPasspoint()) {
Log.d(TAG, "Removing passpoint network config " + config.configKey());
removeNetwork(config.networkId, mSystemUiUid);
didRemove = true;
} else if (config.ephemeral) {
Log.d(TAG, "Removing ephemeral network config " + config.configKey());
removeNetwork(config.networkId, mSystemUiUid);
didRemove = true;
}
}
return didRemove;
}
/**
* Helper method to mark a network enabled for network selection.
*/
private void setNetworkSelectionEnabled(WifiConfiguration config) {
NetworkSelectionStatus status = config.getNetworkSelectionStatus();
status.setNetworkSelectionStatus(
NetworkSelectionStatus.NETWORK_SELECTION_ENABLED);
status.setDisableTime(
NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
status.setNetworkSelectionDisableReason(NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
// Clear out all the disable reason counters.
status.clearDisableReasonCounter();
if (mListener != null) mListener.onSavedNetworkEnabled(config.networkId);
}
/**
* Helper method to mark a network temporarily disabled for network selection.
*/
private void setNetworkSelectionTemporarilyDisabled(
WifiConfiguration config, int disableReason) {
NetworkSelectionStatus status = config.getNetworkSelectionStatus();
status.setNetworkSelectionStatus(
NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED);
// Only need a valid time filled in for temporarily disabled networks.
status.setDisableTime(mClock.getElapsedSinceBootMillis());
status.setNetworkSelectionDisableReason(disableReason);
if (mListener != null) mListener.onSavedNetworkTemporarilyDisabled(config.networkId);
}
/**
* Helper method to mark a network permanently disabled for network selection.
*/
private void setNetworkSelectionPermanentlyDisabled(
WifiConfiguration config, int disableReason) {
NetworkSelectionStatus status = config.getNetworkSelectionStatus();
status.setNetworkSelectionStatus(
NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED);
status.setDisableTime(
NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
status.setNetworkSelectionDisableReason(disableReason);
if (mListener != null) mListener.onSavedNetworkPermanentlyDisabled(config.networkId);
}
/**
* Helper method to set the publicly exposed status for the network and send out the network
* status change broadcast.
*/
private void setNetworkStatus(WifiConfiguration config, int status) {
config.status = status;
sendConfiguredNetworkChangedBroadcast(config, WifiManager.CHANGE_REASON_CONFIG_CHANGE);
}
/**
* Sets a network's status (both internal and public) according to the update reason and
* its current state.
*
* This updates the network's {@link WifiConfiguration#mNetworkSelectionStatus} field and the
* public {@link WifiConfiguration#status} field if the network is either enabled or
* permanently disabled.
*
* @param config network to be updated.
* @param reason reason code for update.
* @return true if the input configuration has been updated, false otherwise.
*/
private boolean setNetworkSelectionStatus(WifiConfiguration config, int reason) {
NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
if (reason < 0 || reason >= NetworkSelectionStatus.NETWORK_SELECTION_DISABLED_MAX) {
Log.e(TAG, "Invalid Network disable reason " + reason);
return false;
}
if (reason == NetworkSelectionStatus.NETWORK_SELECTION_ENABLE) {
setNetworkSelectionEnabled(config);
setNetworkStatus(config, WifiConfiguration.Status.ENABLED);
} else if (reason < NetworkSelectionStatus.DISABLED_TLS_VERSION_MISMATCH) {
setNetworkSelectionTemporarilyDisabled(config, reason);
} else {
setNetworkSelectionPermanentlyDisabled(config, reason);
setNetworkStatus(config, WifiConfiguration.Status.DISABLED);
}
localLog("setNetworkSelectionStatus: configKey=" + config.configKey()
+ " networkStatus=" + networkStatus.getNetworkStatusString() + " disableReason="
+ networkStatus.getNetworkDisableReasonString() + " at="
+ createDebugTimeStampString(mClock.getWallClockMillis()));
saveToStore(false);
return true;
}
/**
* Update a network's status (both internal and public) according to the update reason and
* its current state.
*
* @param config network to be updated.
* @param reason reason code for update.
* @return true if the input configuration has been updated, false otherwise.
*/
private boolean updateNetworkSelectionStatus(WifiConfiguration config, int reason) {
NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
if (reason != NetworkSelectionStatus.NETWORK_SELECTION_ENABLE) {
networkStatus.incrementDisableReasonCounter(reason);
// For network disable reasons, we should only update the status if we cross the
// threshold.
int disableReasonCounter = networkStatus.getDisableReasonCounter(reason);
int disableReasonThreshold = NETWORK_SELECTION_DISABLE_THRESHOLD[reason];
if (disableReasonCounter < disableReasonThreshold) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Disable counter for network " + config.getPrintableSsid()
+ " for reason "
+ NetworkSelectionStatus.getNetworkDisableReasonString(reason) + " is "
+ networkStatus.getDisableReasonCounter(reason) + " and threshold is "
+ disableReasonThreshold);
}
return true;
}
}
return setNetworkSelectionStatus(config, reason);
}
/**
* Update a network's status (both internal and public) according to the update reason and
* its current state.
*
* Each network has 2 status:
* 1. NetworkSelectionStatus: This is internal selection status of the network. This is used
* for temporarily disabling a network for Network Selector.
* 2. Status: This is the exposed status for a network. This is mostly set by
* the public API's {@link WifiManager#enableNetwork(int, boolean)} &
* {@link WifiManager#disableNetwork(int)}.
*
* @param networkId network ID of the network that needs the update.
* @param reason reason to update the network.
* @return true if the input configuration has been updated, false otherwise.
*/
public boolean updateNetworkSelectionStatus(int networkId, int reason) {
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
if (config == null) {
return false;
}
return updateNetworkSelectionStatus(config, reason);
}
/**
* Update whether a network is currently not recommended by {@link RecommendedNetworkEvaluator}.
*
* @param networkId network ID of the network to be updated
* @param notRecommended whether this network is not recommended
* @return true if the network is updated, false otherwise
*/
public boolean updateNetworkNotRecommended(int networkId, boolean notRecommended) {
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
if (config == null) {
return false;
}
config.getNetworkSelectionStatus().setNotRecommended(notRecommended);
if (mVerboseLoggingEnabled) {
localLog("updateNetworkRecommendation: configKey=" + config.configKey()
+ " notRecommended=" + notRecommended);
}
saveToStore(false);
return true;
}
/**
* Attempt to re-enable a network for network selection, if this network was either:
* a) Previously temporarily disabled, but its disable timeout has expired, or
* b) Previously disabled because of a user switch, but is now visible to the current
* user.
*
* @param config configuration for the network to be re-enabled for network selection. The
* network corresponding to the config must be visible to the current user.
* @return true if the network identified by {@param config} was re-enabled for qualified
* network selection, false otherwise.
*/
private boolean tryEnableNetwork(WifiConfiguration config) {
NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
if (networkStatus.isNetworkTemporaryDisabled()) {
long timeDifferenceMs =
mClock.getElapsedSinceBootMillis() - networkStatus.getDisableTime();
int disableReason = networkStatus.getNetworkSelectionDisableReason();
long disableTimeoutMs = NETWORK_SELECTION_DISABLE_TIMEOUT_MS[disableReason];
if (timeDifferenceMs >= disableTimeoutMs) {
return updateNetworkSelectionStatus(
config, NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
}
} else if (networkStatus.isDisabledByReason(
NetworkSelectionStatus.DISABLED_DUE_TO_USER_SWITCH)) {
return updateNetworkSelectionStatus(
config, NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
}
return false;
}
/**
* Attempt to re-enable a network for network selection, if this network was either:
* a) Previously temporarily disabled, but its disable timeout has expired, or
* b) Previously disabled because of a user switch, but is now visible to the current
* user.
*
* @param networkId the id of the network to be checked for possible unblock (due to timeout)
* @return true if the network identified by {@param networkId} was re-enabled for qualified
* network selection, false otherwise.
*/
public boolean tryEnableNetwork(int networkId) {
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
if (config == null) {
return false;
}
return tryEnableNetwork(config);
}
/**
* Enable a network using the public {@link WifiManager#enableNetwork(int, boolean)} API.
*
* @param networkId network ID of the network that needs the update.
* @param disableOthers Whether to disable all other networks or not. This is used to indicate
* that the app requested connection to a specific network.
* @param uid uid of the app requesting the update.
* @return true if it succeeds, false otherwise
*/
public boolean enableNetwork(int networkId, boolean disableOthers, int uid) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Enabling network " + networkId + " (disableOthers " + disableOthers + ")");
}
if (!doesUidBelongToCurrentUser(uid)) {
Log.e(TAG, "UID " + uid + " not visible to the current user");
return false;
}
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
if (config == null) {
return false;
}
if (!canModifyNetwork(config, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
+ config.configKey());
return false;
}
if (!updateNetworkSelectionStatus(
networkId, WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE)) {
return false;
}
if (disableOthers) {
setLastSelectedNetwork(networkId);
}
saveToStore(true);
return true;
}
/**
* Disable a network using the public {@link WifiManager#disableNetwork(int)} API.
*
* @param networkId network ID of the network that needs the update.
* @param uid uid of the app requesting the update.
* @return true if it succeeds, false otherwise
*/
public boolean disableNetwork(int networkId, int uid) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Disabling network " + networkId);
}
if (!doesUidBelongToCurrentUser(uid)) {
Log.e(TAG, "UID " + uid + " not visible to the current user");
return false;
}
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
if (config == null) {
return false;
}
if (!canModifyNetwork(config, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
+ config.configKey());
return false;
}
if (!updateNetworkSelectionStatus(
networkId, NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER)) {
return false;
}
if (networkId == mLastSelectedNetworkId) {
clearLastSelectedNetwork();
}
saveToStore(true);
return true;
}
/**
* Checks if the |uid| has the necessary permission to force a connection to a network
* and updates the last connected UID for the provided configuration.
*
* @param networkId network ID corresponding to the network.
* @param uid uid of the app requesting the connection.
* @return true if |uid| has the necessary permission to trigger explicit connection to the
* network, false otherwise.
* Note: This returns true only for the system settings/sysui app which holds the
* {@link android.Manifest.permission#NETWORK_SETTINGS} permission. We don't want to let
* any other app force connection to a network.
*/
public boolean checkAndUpdateLastConnectUid(int networkId, int uid) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Update network last connect UID for " + networkId);
}
if (!doesUidBelongToCurrentUser(uid)) {
Log.e(TAG, "UID " + uid + " not visible to the current user");
return false;
}
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
if (config == null) {
return false;
}
if (!canModifyNetwork(config, uid, ALLOW_LOCKDOWN_CHECK_BYPASS)) {
Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
+ config.configKey());
return false;
}
config.lastConnectUid = uid;
return true;
}
/**
* Updates a network configuration after a successful connection to it.
*
* This method updates the following WifiConfiguration elements:
* 1. Set the |lastConnected| timestamp.
* 2. Increment |numAssociation| counter.
* 3. Clear the disable reason counters in the associated |NetworkSelectionStatus|.
* 4. Set the hasEverConnected| flag in the associated |NetworkSelectionStatus|.
* 5. Sets the status of network as |CURRENT|.
*
* @param networkId network ID corresponding to the network.
* @return true if the network was found, false otherwise.
*/
public boolean updateNetworkAfterConnect(int networkId) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Update network after connect for " + networkId);
}
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
if (config == null) {
return false;
}
config.lastConnected = mClock.getWallClockMillis();
config.numAssociation++;
config.getNetworkSelectionStatus().clearDisableReasonCounter();
config.getNetworkSelectionStatus().setHasEverConnected(true);
setNetworkStatus(config, WifiConfiguration.Status.CURRENT);
saveToStore(false);
return true;
}
/**
* Updates a network configuration after disconnection from it.
*
* This method updates the following WifiConfiguration elements:
* 1. Set the |lastDisConnected| timestamp.
* 2. Sets the status of network back to |ENABLED|.
*
* @param networkId network ID corresponding to the network.
* @return true if the network was found, false otherwise.
*/
public boolean updateNetworkAfterDisconnect(int networkId) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Update network after disconnect for " + networkId);
}
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
if (config == null) {
return false;
}
config.lastDisconnected = mClock.getWallClockMillis();
// If the network hasn't been disabled, mark it back as
// enabled after disconnection.
if (config.status == WifiConfiguration.Status.CURRENT) {
setNetworkStatus(config, WifiConfiguration.Status.ENABLED);
}
saveToStore(false);
return true;
}
/**
* Set default GW MAC address for the provided network.
*
* @param networkId network ID corresponding to the network.
* @param macAddress MAC address of the gateway to be set.
* @return true if the network was found, false otherwise.
*/
public boolean setNetworkDefaultGwMacAddress(int networkId, String macAddress) {
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
if (config == null) {
return false;
}
config.defaultGwMacAddress = macAddress;
return true;
}
/**
* Clear the {@link NetworkSelectionStatus#mCandidate},
* {@link NetworkSelectionStatus#mCandidateScore} &
* {@link NetworkSelectionStatus#mSeenInLastQualifiedNetworkSelection} for the provided network.
*
* This is invoked by Network Selector at the start of every selection procedure to clear all
* configured networks' scan-result-candidates.
*
* @param networkId network ID corresponding to the network.
* @return true if the network was found, false otherwise.
*/
public boolean clearNetworkCandidateScanResult(int networkId) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Clear network candidate scan result for " + networkId);
}
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
if (config == null) {
return false;
}
config.getNetworkSelectionStatus().setCandidate(null);
config.getNetworkSelectionStatus().setCandidateScore(Integer.MIN_VALUE);
config.getNetworkSelectionStatus().setSeenInLastQualifiedNetworkSelection(false);
return true;
}
/**
* Set the {@link NetworkSelectionStatus#mCandidate},
* {@link NetworkSelectionStatus#mCandidateScore} &
* {@link NetworkSelectionStatus#mSeenInLastQualifiedNetworkSelection} for the provided network.
*
* This is invoked by Network Selector when it sees a network during network selection procedure
* to set the scan result candidate.
*
* @param networkId network ID corresponding to the network.
* @param scanResult Candidate ScanResult associated with this network.
* @param score Score assigned to the candidate.
* @return true if the network was found, false otherwise.
*/
public boolean setNetworkCandidateScanResult(int networkId, ScanResult scanResult, int score) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Set network candidate scan result " + scanResult + " for " + networkId);
}
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
if (config == null) {
return false;
}
config.getNetworkSelectionStatus().setCandidate(scanResult);
config.getNetworkSelectionStatus().setCandidateScore(score);
config.getNetworkSelectionStatus().setSeenInLastQualifiedNetworkSelection(true);
return true;
}
/**
* Iterate through all the saved networks and remove the provided configuration from the
* {@link NetworkSelectionStatus#mConnectChoice} from them.
*
* This is invoked when a network is removed from our records.
*
* @param connectChoiceConfigKey ConfigKey corresponding to the network that is being removed.
*/
private void removeConnectChoiceFromAllNetworks(String connectChoiceConfigKey) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Removing connect choice from all networks " + connectChoiceConfigKey);
}
if (connectChoiceConfigKey == null) {
return;
}
for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) {
WifiConfiguration.NetworkSelectionStatus status = config.getNetworkSelectionStatus();
String connectChoice = status.getConnectChoice();
if (TextUtils.equals(connectChoice, connectChoiceConfigKey)) {
Log.d(TAG, "remove connect choice:" + connectChoice + " from " + config.SSID
+ " : " + config.networkId);
clearNetworkConnectChoice(config.networkId);
}
}
}
/**
* Clear the {@link NetworkSelectionStatus#mConnectChoice} &
* {@link NetworkSelectionStatus#mConnectChoiceTimestamp} for the provided network.
*
* @param networkId network ID corresponding to the network.
* @return true if the network was found, false otherwise.
*/
public boolean clearNetworkConnectChoice(int networkId) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Clear network connect choice for " + networkId);
}
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
if (config == null) {
return false;
}
config.getNetworkSelectionStatus().setConnectChoice(null);
config.getNetworkSelectionStatus().setConnectChoiceTimestamp(
NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
saveToStore(false);
return true;
}
/**
* Set the {@link NetworkSelectionStatus#mConnectChoice} &
* {@link NetworkSelectionStatus#mConnectChoiceTimestamp} for the provided network.
*
* This is invoked by Network Selector when the user overrides the currently connected network
* choice.
*
* @param networkId network ID corresponding to the network.
* @param connectChoiceConfigKey ConfigKey corresponding to the network which was chosen over
* this network.
* @param timestamp timestamp at which the choice was made.
* @return true if the network was found, false otherwise.
*/
public boolean setNetworkConnectChoice(
int networkId, String connectChoiceConfigKey, long timestamp) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Set network connect choice " + connectChoiceConfigKey + " for " + networkId);
}
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
if (config == null) {
return false;
}
config.getNetworkSelectionStatus().setConnectChoice(connectChoiceConfigKey);
config.getNetworkSelectionStatus().setConnectChoiceTimestamp(timestamp);
saveToStore(false);
return true;
}
/**
* Increments the number of no internet access reports in the provided network.
*
* @param networkId network ID corresponding to the network.
* @return true if the network was found, false otherwise.
*/
public boolean incrementNetworkNoInternetAccessReports(int networkId) {
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
if (config == null) {
return false;
}
config.numNoInternetAccessReports++;
return true;
}
/**
* Sets the internet access is validated or not in the provided network.
*
* @param networkId network ID corresponding to the network.
* @param validated Whether access is validated or not.
* @return true if the network was found, false otherwise.
*/
public boolean setNetworkValidatedInternetAccess(int networkId, boolean validated) {
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
if (config == null) {
return false;
}
config.validatedInternetAccess = validated;
config.numNoInternetAccessReports = 0;
saveToStore(false);
return true;
}
/**
* Sets whether the internet access is expected or not in the provided network.
*
* @param networkId network ID corresponding to the network.
* @param expected Whether access is expected or not.
* @return true if the network was found, false otherwise.
*/
public boolean setNetworkNoInternetAccessExpected(int networkId, boolean expected) {
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
if (config == null) {
return false;
}
config.noInternetAccessExpected = expected;
return true;
}
/**
* Helper method to clear out the {@link #mNextNetworkId} user/app network selection. This
* is done when either the corresponding network is either removed or disabled.
*/
private void clearLastSelectedNetwork() {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Clearing last selected network");
}
mLastSelectedNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
mLastSelectedTimeStamp = NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP;
}
/**
* Helper method to mark a network as the last selected one by an app/user. This is set
* when an app invokes {@link #enableNetwork(int, boolean, int)} with |disableOthers| flag set.
* This is used by network selector to assign a special bonus during network selection.
*/
private void setLastSelectedNetwork(int networkId) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Setting last selected network to " + networkId);
}
mLastSelectedNetworkId = networkId;
mLastSelectedTimeStamp = mClock.getElapsedSinceBootMillis();
}
/**
* Retrieve the network Id corresponding to the last network that was explicitly selected by
* an app/user.
*
* @return network Id corresponding to the last selected network.
*/
public int getLastSelectedNetwork() {
return mLastSelectedNetworkId;
}
/**
* Retrieve the configKey corresponding to the last network that was explicitly selected by
* an app/user.
*
* @return network Id corresponding to the last selected network.
*/
public String getLastSelectedNetworkConfigKey() {
if (mLastSelectedNetworkId == WifiConfiguration.INVALID_NETWORK_ID) {
return "";
}
WifiConfiguration config = getInternalConfiguredNetwork(mLastSelectedNetworkId);
if (config == null) {
return "";
}
return config.configKey();
}
/**
* Retrieve the time stamp at which a network was explicitly selected by an app/user.
*
* @return timestamp in milliseconds from boot when this was set.
*/
public long getLastSelectedTimeStamp() {
return mLastSelectedTimeStamp;
}
/**
* Helper method to get the scan detail cache entry {@link #mScanDetailCaches} for the provided
* network.
*
* @param networkId network ID corresponding to the network.
* @return existing {@link ScanDetailCache} entry if one exists or null.
*/
public ScanDetailCache getScanDetailCacheForNetwork(int networkId) {
return mScanDetailCaches.get(networkId);
}
/**
* Helper method to get or create a scan detail cache entry {@link #mScanDetailCaches} for
* the provided network.
*
* @param config configuration corresponding to the the network.
* @return existing {@link ScanDetailCache} entry if one exists or a new instance created for
* this network.
*/
private ScanDetailCache getOrCreateScanDetailCacheForNetwork(WifiConfiguration config) {
if (config == null) return null;
ScanDetailCache cache = getScanDetailCacheForNetwork(config.networkId);
if (cache == null && config.networkId != WifiConfiguration.INVALID_NETWORK_ID) {
cache = new ScanDetailCache(
config, SCAN_CACHE_ENTRIES_MAX_SIZE, SCAN_CACHE_ENTRIES_TRIM_SIZE);
mScanDetailCaches.put(config.networkId, cache);
}
return cache;
}
/**
* Saves the provided ScanDetail into the corresponding scan detail cache entry
* {@link #mScanDetailCaches} for the provided network.
*
* @param config configuration corresponding to the the network.
* @param scanDetail new scan detail instance to be saved into the cache.
*/
private void saveToScanDetailCacheForNetwork(
WifiConfiguration config, ScanDetail scanDetail) {
ScanResult scanResult = scanDetail.getScanResult();
ScanDetailCache scanDetailCache = getOrCreateScanDetailCacheForNetwork(config);
if (scanDetailCache == null) {
Log.e(TAG, "Could not allocate scan cache for " + config.getPrintableSsid());
return;
}
// Adding a new BSSID
ScanResult result = scanDetailCache.get(scanResult.BSSID);
if (result != null) {
// transfer the black list status
scanResult.blackListTimestamp = result.blackListTimestamp;
scanResult.numIpConfigFailures = result.numIpConfigFailures;
scanResult.numConnection = result.numConnection;
}
if (config.ephemeral) {
// For an ephemeral Wi-Fi config, the ScanResult should be considered
// untrusted.
scanResult.untrusted = true;
}
// Add the scan detail to this network's scan detail cache.
scanDetailCache.put(scanDetail);
// Since we added a scan result to this configuration, re-attempt linking.
// TODO: Do we really need to do this after every scan result?
attemptNetworkLinking(config);
}
/**
* Retrieves a configured network corresponding to the provided scan detail if one exists.
*
* @param scanDetail ScanDetail instance to use for looking up the network.
* @return WifiConfiguration object representing the network corresponding to the scanDetail,
* null if none exists.
*/
public WifiConfiguration getConfiguredNetworkForScanDetail(ScanDetail scanDetail) {
ScanResult scanResult = scanDetail.getScanResult();
if (scanResult == null) {
Log.e(TAG, "No scan result found in scan detail");
return null;
}
WifiConfiguration config = null;
try {
config = mConfiguredNetworks.getByScanResultForCurrentUser(scanResult);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Failed to lookup network from config map", e);
}
if (config != null) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "getSavedNetworkFromScanDetail Found " + config.configKey()
+ " for " + scanResult.SSID + "[" + scanResult.capabilities + "]");
}
}
return config;
}
/**
* Retrieves a configured network corresponding to the provided scan detail if one exists and
* caches the provided |scanDetail| into the corresponding scan detail cache entry
* {@link #mScanDetailCaches} for the retrieved network.
*
* @param scanDetail input a scanDetail from the scan result
* @return WifiConfiguration object representing the network corresponding to the scanDetail,
* null if none exists.
*/
public WifiConfiguration getConfiguredNetworkForScanDetailAndCache(ScanDetail scanDetail) {
WifiConfiguration network = getConfiguredNetworkForScanDetail(scanDetail);
if (network == null) {
return null;
}
saveToScanDetailCacheForNetwork(network, scanDetail);
// Cache DTIM values parsed from the beacon frame Traffic Indication Map (TIM)
// Information Element (IE), into the associated WifiConfigurations. Most of the
// time there is no TIM IE in the scan result (Probe Response instead of Beacon
// Frame), these scanResult DTIM's are negative and ignored.
// Used for metrics collection.
if (scanDetail.getNetworkDetail() != null
&& scanDetail.getNetworkDetail().getDtimInterval() > 0) {
network.dtimInterval = scanDetail.getNetworkDetail().getDtimInterval();
}
return createExternalWifiConfiguration(network, true);
}
/**
* Update the scan detail cache associated with current connected network with latest
* RSSI value in the provided WifiInfo.
* This is invoked when we get an RSSI poll update after connection.
*
* @param info WifiInfo instance pointing to the current connected network.
*/
public void updateScanDetailCacheFromWifiInfo(WifiInfo info) {
WifiConfiguration config = getInternalConfiguredNetwork(info.getNetworkId());
ScanDetailCache scanDetailCache = getScanDetailCacheForNetwork(info.getNetworkId());
if (config != null && scanDetailCache != null) {
ScanDetail scanDetail = scanDetailCache.getScanDetail(info.getBSSID());
if (scanDetail != null) {
ScanResult result = scanDetail.getScanResult();
long previousSeen = result.seen;
int previousRssi = result.level;
// Update the scan result
scanDetail.setSeen();
result.level = info.getRssi();
// Average the RSSI value
result.averageRssi(previousRssi, previousSeen, SCAN_RESULT_MAXIMUM_AGE_MS);
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Updating scan detail cache freq=" + result.frequency
+ " BSSID=" + result.BSSID
+ " RSSI=" + result.level
+ " for " + config.configKey());
}
}
}
}
/**
* Save the ScanDetail to the ScanDetailCache of the given network. This is used
* by {@link com.android.server.wifi.hotspot2.PasspointNetworkEvaluator} for caching
* ScanDetail for newly created {@link WifiConfiguration} for Passpoint network.
*
* @param networkId The ID of the network to save ScanDetail to
* @param scanDetail The ScanDetail to cache
*/
public void updateScanDetailForNetwork(int networkId, ScanDetail scanDetail) {
WifiConfiguration network = getInternalConfiguredNetwork(networkId);
if (network == null) {
return;
}
saveToScanDetailCacheForNetwork(network, scanDetail);
}
/**
* Helper method to check if the 2 provided networks can be linked or not.
* Networks are considered for linking if:
* 1. Share the same GW MAC address.
* 2. Scan results for the networks have AP's with MAC address which differ only in the last
* nibble.
*
* @param network1 WifiConfiguration corresponding to network 1.
* @param network2 WifiConfiguration corresponding to network 2.
* @param scanDetailCache1 ScanDetailCache entry for network 1.
* @param scanDetailCache1 ScanDetailCache entry for network 2.
* @return true if the networks should be linked, false if the networks should be unlinked.
*/
private boolean shouldNetworksBeLinked(
WifiConfiguration network1, WifiConfiguration network2,
ScanDetailCache scanDetailCache1, ScanDetailCache scanDetailCache2) {
// TODO (b/30706406): Link networks only with same passwords if the
// |mOnlyLinkSameCredentialConfigurations| flag is set.
if (mOnlyLinkSameCredentialConfigurations) {
if (!TextUtils.equals(network1.preSharedKey, network2.preSharedKey)) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "shouldNetworksBeLinked unlink due to password mismatch");
}
return false;
}
}
if (network1.defaultGwMacAddress != null && network2.defaultGwMacAddress != null) {
// If both default GW are known, link only if they are equal
if (network1.defaultGwMacAddress.equals(network2.defaultGwMacAddress)) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "shouldNetworksBeLinked link due to same gw " + network2.SSID
+ " and " + network1.SSID + " GW " + network1.defaultGwMacAddress);
}
return true;
}
} else {
// We do not know BOTH default gateways hence we will try to link
// hoping that WifiConfigurations are indeed behind the same gateway.
// once both WifiConfiguration have been tried and thus once both default gateways
// are known we will revisit the choice of linking them.
if (scanDetailCache1 != null && scanDetailCache2 != null) {
for (String abssid : scanDetailCache1.keySet()) {
for (String bbssid : scanDetailCache2.keySet()) {
if (abssid.regionMatches(
true, 0, bbssid, 0, LINK_CONFIGURATION_BSSID_MATCH_LENGTH)) {
// If first 16 ASCII characters of BSSID matches,
// we assume this is a DBDC.
if (mVerboseLoggingEnabled) {
Log.v(TAG, "shouldNetworksBeLinked link due to DBDC BSSID match "
+ network2.SSID + " and " + network1.SSID
+ " bssida " + abssid + " bssidb " + bbssid);
}
return true;
}
}
}
}
}
return false;
}
/**
* Helper methods to link 2 networks together.
*
* @param network1 WifiConfiguration corresponding to network 1.
* @param network2 WifiConfiguration corresponding to network 2.
*/
private void linkNetworks(WifiConfiguration network1, WifiConfiguration network2) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "linkNetworks will link " + network2.configKey()
+ " and " + network1.configKey());
}
if (network2.linkedConfigurations == null) {
network2.linkedConfigurations = new HashMap<>();
}
if (network1.linkedConfigurations == null) {
network1.linkedConfigurations = new HashMap<>();
}
// TODO (b/30638473): This needs to become a set instead of map, but it will need
// public interface changes and need some migration of existing store data.
network2.linkedConfigurations.put(network1.configKey(), 1);
network1.linkedConfigurations.put(network2.configKey(), 1);
}
/**
* Helper methods to unlink 2 networks from each other.
*
* @param network1 WifiConfiguration corresponding to network 1.
* @param network2 WifiConfiguration corresponding to network 2.
*/
private void unlinkNetworks(WifiConfiguration network1, WifiConfiguration network2) {
if (network2.linkedConfigurations != null
&& (network2.linkedConfigurations.get(network1.configKey()) != null)) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "unlinkNetworks un-link " + network1.configKey()
+ " from " + network2.configKey());
}
network2.linkedConfigurations.remove(network1.configKey());
}
if (network1.linkedConfigurations != null
&& (network1.linkedConfigurations.get(network2.configKey()) != null)) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "unlinkNetworks un-link " + network2.configKey()
+ " from " + network1.configKey());
}
network1.linkedConfigurations.remove(network2.configKey());
}
}
/**
* This method runs through all the saved networks and checks if the provided network can be
* linked with any of them.
*
* @param config WifiConfiguration object corresponding to the network that needs to be
* checked for potential links.
*/
private void attemptNetworkLinking(WifiConfiguration config) {
// Only link WPA_PSK config.
if (!config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
return;
}
ScanDetailCache scanDetailCache = getScanDetailCacheForNetwork(config.networkId);
// Ignore configurations with large number of BSSIDs.
if (scanDetailCache != null
&& scanDetailCache.size() > LINK_CONFIGURATION_MAX_SCAN_CACHE_ENTRIES) {
return;
}
for (WifiConfiguration linkConfig : getInternalConfiguredNetworks()) {
if (linkConfig.configKey().equals(config.configKey())) {
continue;
}
if (linkConfig.ephemeral) {
continue;
}
// Network Selector will be allowed to dynamically jump from a linked configuration
// to another, hence only link configurations that have WPA_PSK security type.
if (!linkConfig.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
continue;
}
ScanDetailCache linkScanDetailCache =
getScanDetailCacheForNetwork(linkConfig.networkId);
// Ignore configurations with large number of BSSIDs.
if (linkScanDetailCache != null
&& linkScanDetailCache.size() > LINK_CONFIGURATION_MAX_SCAN_CACHE_ENTRIES) {
continue;
}
// Check if the networks should be linked/unlinked.
if (shouldNetworksBeLinked(
config, linkConfig, scanDetailCache, linkScanDetailCache)) {
linkNetworks(config, linkConfig);
} else {
unlinkNetworks(config, linkConfig);
}
}
}
/**
* Helper method to fetch list of channels for a network from the associated ScanResult's cache
* and add it to the provided channel as long as the size of the set is less than
* |maxChannelSetSize|.
*
* @param channelSet Channel set holding all the channels for the network.
* @param scanDetailCache ScanDetailCache entry associated with the network.
* @param nowInMillis current timestamp to be used for age comparison.
* @param ageInMillis only consider scan details whose timestamps are earlier than this
* value.
* @param maxChannelSetSize Maximum number of channels to be added to the set.
* @return false if the list is full, true otherwise.
*/
private boolean addToChannelSetForNetworkFromScanDetailCache(
Set<Integer> channelSet, ScanDetailCache scanDetailCache,
long nowInMillis, long ageInMillis, int maxChannelSetSize) {
if (scanDetailCache != null && scanDetailCache.size() > 0) {
for (ScanDetail scanDetail : scanDetailCache.values()) {
ScanResult result = scanDetail.getScanResult();
boolean valid = (nowInMillis - result.seen) < ageInMillis;
if (mVerboseLoggingEnabled) {
Log.v(TAG, "fetchChannelSetForNetwork has " + result.BSSID + " freq "
+ result.frequency + " age " + (nowInMillis - result.seen)
+ " ?=" + valid);
}
if (valid) {
channelSet.add(result.frequency);
}
if (channelSet.size() >= maxChannelSetSize) {
return false;
}
}
}
return true;
}
/**
* Retrieve a set of channels on which AP's for the provided network was seen using the
* internal ScanResult's cache {@link #mScanDetailCaches}. This is used for initiating partial
* scans for the currently connected network.
*
* @param networkId network ID corresponding to the network.
* @param ageInMillis only consider scan details whose timestamps are earlier than this value.
* @param homeChannelFreq frequency of the currently connected network.
* @return Set containing the frequencies on which this network was found, null if the network
* was not found or there are no associated scan details in the cache.
*/
public Set<Integer> fetchChannelSetForNetworkForPartialScan(int networkId, long ageInMillis,
int homeChannelFreq) {
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
if (config == null) {
return null;
}
ScanDetailCache scanDetailCache = getScanDetailCacheForNetwork(networkId);
if (scanDetailCache == null && config.linkedConfigurations == null) {
Log.i(TAG, "No scan detail and linked configs associated with networkId " + networkId);
return null;
}
if (mVerboseLoggingEnabled) {
StringBuilder dbg = new StringBuilder();
dbg.append("fetchChannelSetForNetworkForPartialScan ageInMillis ")
.append(ageInMillis)
.append(" for ")
.append(config.configKey())
.append(" max ")
.append(mMaxNumActiveChannelsForPartialScans);
if (scanDetailCache != null) {
dbg.append(" bssids " + scanDetailCache.size());
}
if (config.linkedConfigurations != null) {
dbg.append(" linked " + config.linkedConfigurations.size());
}
Log.v(TAG, dbg.toString());
}
Set<Integer> channelSet = new HashSet<>();
// First add the currently connected network channel.
if (homeChannelFreq > 0) {
channelSet.add(homeChannelFreq);
if (channelSet.size() >= mMaxNumActiveChannelsForPartialScans) {
return channelSet;
}
}
long nowInMillis = mClock.getWallClockMillis();
// Then get channels for the network.
if (!addToChannelSetForNetworkFromScanDetailCache(
channelSet, scanDetailCache, nowInMillis, ageInMillis,
mMaxNumActiveChannelsForPartialScans)) {
return channelSet;
}
// Lastly get channels for linked networks.
if (config.linkedConfigurations != null) {
for (String configKey : config.linkedConfigurations.keySet()) {
WifiConfiguration linkedConfig = getInternalConfiguredNetwork(configKey);
if (linkedConfig == null) {
continue;
}
ScanDetailCache linkedScanDetailCache =
getScanDetailCacheForNetwork(linkedConfig.networkId);
if (!addToChannelSetForNetworkFromScanDetailCache(
channelSet, linkedScanDetailCache, nowInMillis, ageInMillis,
mMaxNumActiveChannelsForPartialScans)) {
break;
}
}
}
return channelSet;
}
/**
* Retrieves a list of all the saved networks before enabling disconnected/connected PNO.
*
* PNO network list sent to the firmware has limited size. If there are a lot of saved
* networks, this list will be truncated and we might end up not sending the networks
* with the highest chance of connecting to the firmware.
* So, re-sort the network list based on the frequency of connection to those networks
* and whether it was last seen in the scan results.
*
* TODO (b/30399964): Recalculate the list whenever network status changes.
* @return list of networks with updated priorities.
*/
public List<WifiScanner.PnoSettings.PnoNetwork> retrievePnoNetworkList() {
List<WifiScanner.PnoSettings.PnoNetwork> pnoList = new ArrayList<>();
List<WifiConfiguration> networks = new ArrayList<>(getInternalConfiguredNetworks());
// Remove any permanently disabled networks.
Iterator<WifiConfiguration> iter = networks.iterator();
while (iter.hasNext()) {
WifiConfiguration config = iter.next();
if (config.ephemeral || config.isPasspoint()
|| config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()) {
iter.remove();
}
}
Collections.sort(networks, sScanListComparator);
// Let's use the network list size - 1 as the highest priority and then go down from there.
// So, the most frequently connected network has the highest priority now.
int priority = networks.size() - 1;
for (WifiConfiguration config : networks) {
pnoList.add(WifiConfigurationUtil.createPnoNetwork(config, priority));
priority--;
}
return pnoList;
}
/**
* Retrieves a list of all the saved hidden networks for scans.
*
* Hidden network list sent to the firmware has limited size. If there are a lot of saved
* networks, this list will be truncated and we might end up not sending the networks
* with the highest chance of connecting to the firmware.
* So, re-sort the network list based on the frequency of connection to those networks
* and whether it was last seen in the scan results.
*
* @return list of networks with updated priorities.
*/
public List<WifiScanner.ScanSettings.HiddenNetwork> retrieveHiddenNetworkList() {
List<WifiScanner.ScanSettings.HiddenNetwork> hiddenList = new ArrayList<>();
List<WifiConfiguration> networks = new ArrayList<>(getInternalConfiguredNetworks());
// Remove any permanently disabled networks or non hidden networks.
Iterator<WifiConfiguration> iter = networks.iterator();
while (iter.hasNext()) {
WifiConfiguration config = iter.next();
if (!config.hiddenSSID ||
config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()) {
iter.remove();
}
}
Collections.sort(networks, sScanListComparator);
// Let's use the network list size - 1 as the highest priority and then go down from there.
// So, the most frequently connected network has the highest priority now.
int priority = networks.size() - 1;
for (WifiConfiguration config : networks) {
hiddenList.add(
new WifiScanner.ScanSettings.HiddenNetwork(config.SSID));
priority--;
}
return hiddenList;
}
/**
* Check if the provided ephemeral network was deleted by the user or not.
*
* @param ssid caller must ensure that the SSID passed thru this API match
* the WifiConfiguration.SSID rules, and thus be surrounded by quotes.
* @return true if network was deleted, false otherwise.
*/
public boolean wasEphemeralNetworkDeleted(String ssid) {
return mDeletedEphemeralSSIDs.contains(ssid);
}
/**
* Disable an ephemeral SSID for the purpose of network selection.
*
* The only way to "un-disable it" is if the user create a network for that SSID and then
* forget it.
*
* @param ssid caller must ensure that the SSID passed thru this API match
* the WifiConfiguration.SSID rules, and thus be surrounded by quotes.
* @return the {@link WifiConfiguration} corresponding to this SSID, if any, so that we can
* disconnect if this is the current network.
*/
public WifiConfiguration disableEphemeralNetwork(String ssid) {
if (ssid == null) {
return null;
}
WifiConfiguration foundConfig = null;
for (WifiConfiguration config : getInternalConfiguredNetworks()) {
if (config.ephemeral && TextUtils.equals(config.SSID, ssid)) {
foundConfig = config;
break;
}
}
mDeletedEphemeralSSIDs.add(ssid);
Log.d(TAG, "Forget ephemeral SSID " + ssid + " num=" + mDeletedEphemeralSSIDs.size());
if (foundConfig != null) {
Log.d(TAG, "Found ephemeral config in disableEphemeralNetwork: "
+ foundConfig.networkId);
}
return foundConfig;
}
/**
* Resets all sim networks state.
*/
public void resetSimNetworks(boolean simPresent) {
if (mVerboseLoggingEnabled) localLog("resetSimNetworks");
for (WifiConfiguration config : getInternalConfiguredNetworks()) {
if (TelephonyUtil.isSimConfig(config)) {
String currentIdentity = null;
if (simPresent) {
currentIdentity = TelephonyUtil.getSimIdentity(mTelephonyManager, config);
}
// Update the loaded config
config.enterpriseConfig.setIdentity(currentIdentity);
if (config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.PEAP) {
config.enterpriseConfig.setAnonymousIdentity("");
}
}
}
mSimPresent = simPresent;
}
/**
* Check if SIM is present.
*
* @return True if SIM is present, otherwise false.
*/
public boolean isSimPresent() {
return mSimPresent;
}
/**
* Any network using certificates to authenticate access requires unlocked key store; unless
* the certificates can be stored with hardware encryption
*
* @return true if we need an unlocked keystore, false otherwise.
*/
public boolean needsUnlockedKeyStore() {
for (WifiConfiguration config : getInternalConfiguredNetworks()) {
if (WifiConfigurationUtil.isConfigForEapNetwork(config)) {
if (mWifiKeyStore.needsSoftwareBackedKeyStore(config.enterpriseConfig)) {
return true;
}
}
}
return false;
}
/**
* Helper method to perform the following operations during user switch/unlock:
* - Remove private networks of the old user.
* - Load from the new user store file.
* - Save the store files again to migrate any user specific networks from the shared store
* to user store.
* This method assumes the user store is visible (i.e CE storage is unlocked). So, the caller
* should ensure that the stores are accessible before invocation.
*
* @param userId The identifier of the new foreground user, after the unlock or switch.
*/
private void handleUserUnlockOrSwitch(int userId) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Loading from store after user switch/unlock for " + userId);
}
// Switch out the user store file.
if (loadFromUserStoreAfterUnlockOrSwitch(userId)) {
saveToStore(true);
mPendingUnlockStoreRead = false;
}
}
/**
* Handles the switch to a different foreground user:
* - Flush the current state to the old user's store file.
* - Switch the user specific store file.
* - Reload the networks from the store files (shared & user).
* - Write the store files to move any user specific private networks from shared store to user
* store.
*
* Need to be called when {@link com.android.server.SystemService#onSwitchUser(int)} is invoked.
*
* @param userId The identifier of the new foreground user, after the switch.
* @return List of network ID's of all the private networks of the old user which will be
* removed from memory.
*/
public Set<Integer> handleUserSwitch(int userId) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Handling user switch for " + userId);
}
if (userId == mCurrentUserId) {
Log.w(TAG, "User already in foreground " + userId);
return new HashSet<>();
}
if (mPendingStoreRead) {
Log.wtf(TAG, "Unexpected user switch before store is read!");
return new HashSet<>();
}
if (mUserManager.isUserUnlockingOrUnlocked(mCurrentUserId)) {
saveToStore(true);
}
// Remove any private networks of the old user before switching the userId.
Set<Integer> removedNetworkIds = clearInternalUserData(mCurrentUserId);
mConfiguredNetworks.setNewUser(userId);
mCurrentUserId = userId;
if (mUserManager.isUserUnlockingOrUnlocked(mCurrentUserId)) {
handleUserUnlockOrSwitch(mCurrentUserId);
} else {
// Cannot read data from new user's CE store file before they log-in.
mPendingUnlockStoreRead = true;
Log.i(TAG, "Waiting for user unlock to load from store");
}
return removedNetworkIds;
}
/**
* Handles the unlock of foreground user. This maybe needed to read the store file if the user's
* CE storage is not visible when {@link #handleUserSwitch(int)} is invoked.
*
* Need to be called when {@link com.android.server.SystemService#onUnlockUser(int)} is invoked.
*
* @param userId The identifier of the user that unlocked.
*/
public void handleUserUnlock(int userId) {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Handling user unlock for " + userId);
}
if (mPendingStoreRead) {
Log.w(TAG, "Ignore user unlock until store is read!");
mDeferredUserUnlockRead = true;
return;
}
if (userId == mCurrentUserId && mPendingUnlockStoreRead) {
handleUserUnlockOrSwitch(mCurrentUserId);
}
}
/**
* Handles the stop of foreground user. This is needed to write the store file to flush
* out any pending data before the user's CE store storage is unavailable.
*
* Need to be called when {@link com.android.server.SystemService#onStopUser(int)} is invoked.
*
* @param userId The identifier of the user that stopped.
*/
public void handleUserStop(int userId) {
if (userId == mCurrentUserId && mUserManager.isUserUnlockingOrUnlocked(mCurrentUserId)) {
saveToStore(true);
clearInternalData();
mCurrentUserId = UserHandle.USER_SYSTEM;
}
}
/**
* Helper method to clear internal databases.
* This method clears the:
* - List of configured networks.
* - Map of scan detail caches.
* - List of deleted ephemeral networks.
*/
private void clearInternalData() {
mConfiguredNetworks.clear();
mDeletedEphemeralSSIDs.clear();
mScanDetailCaches.clear();
clearLastSelectedNetwork();
}
/**
* Helper method to clear internal databases of the specified user.
* This method clears the:
* - Private configured configured networks of the specified user.
* - Map of scan detail caches.
* - List of deleted ephemeral networks.
*
* @param userId The identifier of the current foreground user, before the switch.
* @return List of network ID's of all the private networks of the old user which will be
* removed from memory.
*/
private Set<Integer> clearInternalUserData(int userId) {
Set<Integer> removedNetworkIds = new HashSet<>();
// Remove any private networks of the old user before switching the userId.
for (WifiConfiguration config : getInternalConfiguredNetworks()) {
if (!config.shared && WifiConfigurationUtil.doesUidBelongToAnyProfile(
config.creatorUid, mUserManager.getProfiles(userId))) {
removedNetworkIds.add(config.networkId);
mConfiguredNetworks.remove(config.networkId);
}
}
mDeletedEphemeralSSIDs.clear();
mScanDetailCaches.clear();
clearLastSelectedNetwork();
return removedNetworkIds;
}
/**
* Helper function to populate the internal (in-memory) data from the retrieved shared store
* (file) data.
*
* @param configurations list of configurations retrieved from store.
*/
private void loadInternalDataFromSharedStore(
List<WifiConfiguration> configurations) {
for (WifiConfiguration configuration : configurations) {
configuration.networkId = mNextNetworkId++;
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Adding network from shared store " + configuration.configKey());
}
try {
mConfiguredNetworks.put(configuration);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Failed to add network to config map", e);
}
}
}
/**
* Helper function to populate the internal (in-memory) data from the retrieved user store
* (file) data.
*
* @param configurations list of configurations retrieved from store.
* @param deletedEphemeralSSIDs list of ssid's representing the ephemeral networks deleted by
* the user.
*/
private void loadInternalDataFromUserStore(
List<WifiConfiguration> configurations, Set<String> deletedEphemeralSSIDs) {
for (WifiConfiguration configuration : configurations) {
configuration.networkId = mNextNetworkId++;
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Adding network from user store " + configuration.configKey());
}
try {
mConfiguredNetworks.put(configuration);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Failed to add network to config map", e);
}
}
for (String ssid : deletedEphemeralSSIDs) {
mDeletedEphemeralSSIDs.add(ssid);
}
}
/**
* Helper function to populate the internal (in-memory) data from the retrieved stores (file)
* data.
* This method:
* 1. Clears all existing internal data.
* 2. Sends out the networks changed broadcast after loading all the data.
*
* @param sharedConfigurations list of network configurations retrieved from shared store.
* @param userConfigurations list of network configurations retrieved from user store.
* @param deletedEphemeralSSIDs list of ssid's representing the ephemeral networks deleted by
* the user.
*/
private void loadInternalData(
List<WifiConfiguration> sharedConfigurations,
List<WifiConfiguration> userConfigurations, Set<String> deletedEphemeralSSIDs) {
// Clear out all the existing in-memory lists and load the lists from what was retrieved
// from the config store.
clearInternalData();
loadInternalDataFromSharedStore(sharedConfigurations);
loadInternalDataFromUserStore(userConfigurations, deletedEphemeralSSIDs);
if (mConfiguredNetworks.sizeForAllUsers() == 0) {
Log.w(TAG, "No stored networks found.");
}
sendConfiguredNetworksChangedBroadcast();
mPendingStoreRead = false;
}
/**
* Migrate data from legacy store files. The function performs the following operations:
* 1. Check if the legacy store files are present.
* 2. If yes, read all the data from the store files.
* 3. Save it to the new store files.
* 4. Delete the legacy store file.
*
* @return true if migration was successful or not needed (fresh install), false if it failed.
*/
public boolean migrateFromLegacyStore() {
if (!mWifiConfigStoreLegacy.areStoresPresent()) {
Log.d(TAG, "Legacy store files not found. No migration needed!");
return true;
}
WifiConfigStoreDataLegacy storeData = mWifiConfigStoreLegacy.read();
Log.d(TAG, "Reading from legacy store completed");
loadInternalData(storeData.getConfigurations(), new ArrayList<WifiConfiguration>(),
storeData.getDeletedEphemeralSSIDs());
// Setup user store for the current user in case it have not setup yet, so that data
// owned by the current user will be backed to the user store.
if (mDeferredUserUnlockRead) {
mWifiConfigStore.setUserStore(WifiConfigStore.createUserFile(mCurrentUserId));
mDeferredUserUnlockRead = false;
}
if (!saveToStore(true)) {
return false;
}
mWifiConfigStoreLegacy.removeStores();
Log.d(TAG, "Migration from legacy store completed");
return true;
}
/**
* Read the config store and load the in-memory lists from the store data retrieved and sends
* out the networks changed broadcast.
*
* This reads all the network configurations from:
* 1. Shared WifiConfigStore.xml
* 2. User WifiConfigStore.xml
*
* @return true on success or not needed (fresh install/pending legacy store migration),
* false otherwise.
*/
public boolean loadFromStore() {
if (!mWifiConfigStore.areStoresPresent()) {
Log.d(TAG, "New store files not found. No saved networks loaded!");
if (!mWifiConfigStoreLegacy.areStoresPresent()) {
// No legacy store files either, so reset the pending store read flag.
mPendingStoreRead = false;
}
return true;
}
// If the user unlock comes in before we load from store, which means the user store have
// not been setup yet for the current user. Setup the user store before the read so that
// configurations for the current user will also being loaded.
if (mDeferredUserUnlockRead) {
Log.i(TAG, "Handling user unlock before loading from store.");
mWifiConfigStore.setUserStore(WifiConfigStore.createUserFile(mCurrentUserId));
mDeferredUserUnlockRead = false;
}
try {
mWifiConfigStore.read();
} catch (IOException e) {
Log.wtf(TAG, "Reading from new store failed. All saved networks are lost!", e);
return false;
} catch (XmlPullParserException e) {
Log.wtf(TAG, "XML deserialization of store failed. All saved networks are lost!", e);
return false;
}
loadInternalData(mNetworkListStoreData.getSharedConfigurations(),
mNetworkListStoreData.getUserConfigurations(),
mDeletedEphemeralSsidsStoreData.getSsidList());
return true;
}
/**
* Read the user config store and load the in-memory lists from the store data retrieved and
* sends out the networks changed broadcast.
* This should be used for all user switches/unlocks to only load networks from the user
* specific store and avoid reloading the shared networks.
*
* This reads all the network configurations from:
* 1. User WifiConfigStore.xml
*
* @param userId The identifier of the foreground user.
* @return true on success, false otherwise.
*/
public boolean loadFromUserStoreAfterUnlockOrSwitch(int userId) {
try {
mWifiConfigStore.switchUserStoreAndRead(WifiConfigStore.createUserFile(userId));
} catch (IOException e) {
Log.wtf(TAG, "Reading from new store failed. All saved private networks are lost!", e);
return false;
} catch (XmlPullParserException e) {
Log.wtf(TAG, "XML deserialization of store failed. All saved private networks are" +
"lost!", e);
return false;
}
loadInternalDataFromUserStore(mNetworkListStoreData.getUserConfigurations(),
mDeletedEphemeralSsidsStoreData.getSsidList());
return true;
}
/**
* Save the current snapshot of the in-memory lists to the config store.
*
* @param forceWrite Whether the write needs to be forced or not.
* @return Whether the write was successful or not, this is applicable only for force writes.
*/
public boolean saveToStore(boolean forceWrite) {
ArrayList<WifiConfiguration> sharedConfigurations = new ArrayList<>();
ArrayList<WifiConfiguration> userConfigurations = new ArrayList<>();
// List of network IDs for legacy Passpoint configuration to be removed.
List<Integer> legacyPasspointNetId = new ArrayList<>();
for (WifiConfiguration config : mConfiguredNetworks.valuesForAllUsers()) {
// Ignore ephemeral networks and non-legacy Passpoint configurations.
if (config.ephemeral || (config.isPasspoint() && !config.isLegacyPasspointConfig)) {
continue;
}
// Migrate the legacy Passpoint configurations owned by the current user to
// {@link PasspointManager}.
if (config.isLegacyPasspointConfig && WifiConfigurationUtil.doesUidBelongToAnyProfile(
config.creatorUid, mUserManager.getProfiles(mCurrentUserId))) {
legacyPasspointNetId.add(config.networkId);
// Migrate the legacy Passpoint configuration and add it to PasspointManager.
if (!PasspointManager.addLegacyPasspointConfig(config)) {
Log.e(TAG, "Failed to migrate legacy Passpoint config: " + config.FQDN);
}
// This will prevent adding |config| to the |sharedConfigurations|.
continue;
}
// We push all shared networks & private networks not belonging to the current
// user to the shared store. Ideally, private networks for other users should
// not even be in memory,
// But, this logic is in place to deal with store migration from N to O
// because all networks were previously stored in a central file. We cannot
// write these private networks to the user specific store until the corresponding
// user logs in.
if (config.shared || !WifiConfigurationUtil.doesUidBelongToAnyProfile(
config.creatorUid, mUserManager.getProfiles(mCurrentUserId))) {
sharedConfigurations.add(config);
} else {
userConfigurations.add(config);
}
}
// Remove the configurations for migrated Passpoint configurations.
for (int networkId : legacyPasspointNetId) {
mConfiguredNetworks.remove(networkId);
}
// Setup store data for write.
mNetworkListStoreData.setSharedConfigurations(sharedConfigurations);
mNetworkListStoreData.setUserConfigurations(userConfigurations);
mDeletedEphemeralSsidsStoreData.setSsidList(mDeletedEphemeralSSIDs);
try {
mWifiConfigStore.write(forceWrite);
} catch (IOException e) {
Log.wtf(TAG, "Writing to store failed. Saved networks maybe lost!", e);
return false;
} catch (XmlPullParserException e) {
Log.wtf(TAG, "XML serialization for store failed. Saved networks maybe lost!", e);
return false;
}
return true;
}
/**
* Helper method for logging into local log buffer.
*/
private void localLog(String s) {
if (mLocalLog != null) {
mLocalLog.log(s);
}
}
/**
* Dump the local log buffer and other internal state of WifiConfigManager.
*/
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("Dump of WifiConfigManager");
pw.println("WifiConfigManager - Log Begin ----");
mLocalLog.dump(fd, pw, args);
pw.println("WifiConfigManager - Log End ----");
pw.println("WifiConfigManager - Configured networks Begin ----");
for (WifiConfiguration network : getInternalConfiguredNetworks()) {
pw.println(network);
}
pw.println("WifiConfigManager - Configured networks End ----");
pw.println("WifiConfigManager - Next network ID to be allocated " + mNextNetworkId);
pw.println("WifiConfigManager - Last selected network ID " + mLastSelectedNetworkId);
}
/**
* Returns true if the given uid has permission to add, update or remove proxy settings
*/
private boolean canModifyProxySettings(int uid) {
final DevicePolicyManagerInternal dpmi =
mWifiPermissionsWrapper.getDevicePolicyManagerInternal();
final boolean isUidProfileOwner = dpmi != null && dpmi.isActiveAdminWithPolicy(uid,
DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
final boolean isUidDeviceOwner = dpmi != null && dpmi.isActiveAdminWithPolicy(uid,
DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
final boolean hasNetworkSettingsPermission =
mWifiPermissionsUtil.checkNetworkSettingsPermission(uid);
// If |uid| corresponds to the device owner, allow all modifications.
if (isUidDeviceOwner || isUidProfileOwner || hasNetworkSettingsPermission) {
return true;
}
if (mVerboseLoggingEnabled) {
Log.v(TAG, "UID: " + uid + " cannot modify WifiConfiguration proxy settings."
+ " ConfigOverride=" + hasNetworkSettingsPermission
+ " DeviceOwner=" + isUidDeviceOwner
+ " ProfileOwner=" + isUidProfileOwner);
}
return false;
}
/**
* Set the saved network update event listener
*/
public void setOnSavedNetworkUpdateListener(OnSavedNetworkUpdateListener listener) {
mListener = listener;
}
}