4281 lines
193 KiB
Java
4281 lines
193 KiB
Java
/*
|
|
* Copyright (C) 2016 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.server.wifi;
|
|
|
|
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
|
import static android.net.wifi.WifiManager.WIFI_FEATURE_TRUST_ON_FIRST_USE;
|
|
|
|
import android.Manifest;
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.app.ActivityManager;
|
|
import android.content.ContentResolver;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.pm.ApplicationInfo;
|
|
import android.net.DhcpOption;
|
|
import android.net.IpConfiguration;
|
|
import android.net.MacAddress;
|
|
import android.net.ProxyInfo;
|
|
import android.net.StaticIpConfiguration;
|
|
import android.net.wifi.ScanResult;
|
|
import android.net.wifi.SecurityParams;
|
|
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.net.wifi.WifiSsid;
|
|
import android.os.Process;
|
|
import android.os.UserHandle;
|
|
import android.os.UserManager;
|
|
import android.provider.Settings;
|
|
import android.telephony.SubscriptionManager;
|
|
import android.telephony.TelephonyManager;
|
|
import android.text.TextUtils;
|
|
import android.util.ArraySet;
|
|
import android.util.LocalLog;
|
|
import android.util.Log;
|
|
import android.util.Pair;
|
|
|
|
import com.android.internal.annotations.VisibleForTesting;
|
|
import com.android.net.module.util.MacAddressUtils;
|
|
import com.android.server.wifi.hotspot2.PasspointManager;
|
|
import com.android.server.wifi.proto.nano.WifiMetricsProto.UserActionEvent;
|
|
import com.android.server.wifi.util.CertificateSubjectInfo;
|
|
import com.android.server.wifi.util.LruConnectionTracker;
|
|
import com.android.server.wifi.util.MissingCounterTimerLockList;
|
|
import com.android.server.wifi.util.WifiPermissionsUtil;
|
|
import com.android.wifi.resources.R;
|
|
|
|
import org.xmlpull.v1.XmlPullParserException;
|
|
|
|
import java.io.FileDescriptor;
|
|
import java.io.IOException;
|
|
import java.io.PrintWriter;
|
|
import java.security.cert.CertificateParsingException;
|
|
import java.security.cert.X509Certificate;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.Comparator;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.Set;
|
|
import java.util.stream.Collectors;
|
|
|
|
/**
|
|
* 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 the main Wifi thread.
|
|
*/
|
|
public class WifiConfigManager {
|
|
/**
|
|
* String used to mask passwords to public interface.
|
|
*/
|
|
@VisibleForTesting
|
|
public static final String PASSWORD_MASK = "*";
|
|
|
|
/**
|
|
* Interface for other modules to listen to the network updated events.
|
|
* Note: Credentials are masked to avoid accidentally sending credentials outside the stack.
|
|
* Use WifiConfigManager#getConfiguredNetworkWithPassword() to retrieve credentials.
|
|
*/
|
|
public interface OnNetworkUpdateListener {
|
|
/**
|
|
* Invoked on network being added.
|
|
*/
|
|
default void onNetworkAdded(@NonNull WifiConfiguration config) { };
|
|
/**
|
|
* Invoked on network being enabled.
|
|
*/
|
|
default void onNetworkEnabled(@NonNull WifiConfiguration config) { };
|
|
/**
|
|
* Invoked on network being permanently disabled.
|
|
*/
|
|
default void onNetworkPermanentlyDisabled(@NonNull WifiConfiguration config,
|
|
int disableReason) { };
|
|
/**
|
|
* Invoked on network being removed.
|
|
*/
|
|
default void onNetworkRemoved(@NonNull WifiConfiguration config) { };
|
|
/**
|
|
* Invoked on network being temporarily disabled.
|
|
*/
|
|
default void onNetworkTemporarilyDisabled(@NonNull WifiConfiguration config,
|
|
int disableReason) { };
|
|
/**
|
|
* Invoked on network being updated.
|
|
*
|
|
* @param newConfig Updated WifiConfiguration object.
|
|
* @param oldConfig Prev WifiConfiguration object.
|
|
*/
|
|
default void onNetworkUpdated(
|
|
@NonNull WifiConfiguration newConfig, @NonNull WifiConfiguration oldConfig) { };
|
|
|
|
/**
|
|
* Invoked when user connect choice is set.
|
|
* @param networks List of network profiles to set user connect choice.
|
|
* @param choiceKey Network key {@link WifiConfiguration#getProfileKey()}
|
|
* corresponding to the network which the user chose.
|
|
* @param rssi the signal strength of the user selected network
|
|
*/
|
|
default void onConnectChoiceSet(@NonNull List<WifiConfiguration> networks,
|
|
String choiceKey, int rssi) { }
|
|
|
|
/**
|
|
* Invoked when user connect choice is removed.
|
|
* @param choiceKey The network profile key of the user connect choice that was removed.
|
|
*/
|
|
default void onConnectChoiceRemoved(String choiceKey){ }
|
|
|
|
/**
|
|
* Invoke when security params changed, especially when NetworkTransitionDisable event
|
|
* received
|
|
* @param oldConfig The original WifiConfiguration
|
|
* @param securityParams the updated securityParams
|
|
*/
|
|
default void onSecurityParamsUpdate(@NonNull WifiConfiguration oldConfig,
|
|
List<SecurityParams> securityParams) { }
|
|
}
|
|
/**
|
|
* 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;
|
|
/**
|
|
* 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;
|
|
|
|
/**
|
|
* Enforce a minimum time to wait after the last disconnect to generate a new randomized MAC,
|
|
* since IPv6 networks don't provide the DHCP lease duration.
|
|
* 4 hours.
|
|
*/
|
|
@VisibleForTesting
|
|
protected static final long NON_PERSISTENT_MAC_WAIT_AFTER_DISCONNECT_MS = 4 * 60 * 60 * 1000;
|
|
@VisibleForTesting
|
|
protected static final long NON_PERSISTENT_MAC_REFRESH_MS_MIN = 30 * 60 * 1000; // 30 minutes
|
|
@VisibleForTesting
|
|
protected static final long NON_PERSISTENT_MAC_REFRESH_MS_MAX = 24 * 60 * 60 * 1000; // 24 hours
|
|
|
|
private static final MacAddress DEFAULT_MAC_ADDRESS =
|
|
MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS);
|
|
|
|
private static final String VRRP_MAC_ADDRESS_PREFIX = "00:00:5E:00:01";
|
|
|
|
/**
|
|
* Expiration timeout for user disconnect network. (1 hour)
|
|
*/
|
|
@VisibleForTesting
|
|
public static final long USER_DISCONNECT_NETWORK_BLOCK_EXPIRY_MS = (long) 1000 * 60 * 60;
|
|
|
|
@VisibleForTesting
|
|
public static final int SCAN_RESULT_MISSING_COUNT_THRESHOLD = 1;
|
|
@VisibleForTesting
|
|
protected static final String NON_PERSISTENT_MAC_RANDOMIZATION_FEATURE_FORCE_ENABLE_FLAG =
|
|
"non_persistent_mac_randomization_force_enabled";
|
|
private static final int NON_CARRIER_MERGED_NETWORKS_SCAN_CACHE_QUERY_DURATION_MS =
|
|
10 * 60 * 1000; // 10 minutes
|
|
|
|
/**
|
|
* General sorting algorithm of all networks for scanning purposes:
|
|
* Place the configurations in ascending order of their AgeIndex. AgeIndex is based on most
|
|
* recently connected order. The lower the more recently connected.
|
|
* If networks have the same AgeIndex, place the configurations with
|
|
* |lastSeenInQualifiedNetworkSelection| set first.
|
|
*/
|
|
private final WifiConfigurationUtil.WifiConfigurationComparator mScanListComparator =
|
|
new WifiConfigurationUtil.WifiConfigurationComparator() {
|
|
@Override
|
|
public int compareNetworksWithSameStatus(WifiConfiguration a, WifiConfiguration b) {
|
|
int indexA = mLruConnectionTracker.getAgeIndexOfNetwork(a);
|
|
int indexB = mLruConnectionTracker.getAgeIndexOfNetwork(b);
|
|
if (indexA != indexB) {
|
|
return Integer.compare(indexA, indexB);
|
|
} 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 WifiInjector mWifiInjector;
|
|
private final Clock mClock;
|
|
private final UserManager mUserManager;
|
|
private final BackupManagerProxy mBackupManagerProxy;
|
|
private final WifiKeyStore mWifiKeyStore;
|
|
private final WifiConfigStore mWifiConfigStore;
|
|
private final WifiPermissionsUtil mWifiPermissionsUtil;
|
|
private final MacAddressUtil mMacAddressUtil;
|
|
private final WifiMetrics mWifiMetrics;
|
|
private final WifiBlocklistMonitor mWifiBlocklistMonitor;
|
|
private final WifiLastResortWatchdog mWifiLastResortWatchdog;
|
|
private final WifiCarrierInfoManager mWifiCarrierInfoManager;
|
|
private final WifiScoreCard mWifiScoreCard;
|
|
// Keep order of network connection.
|
|
private final LruConnectionTracker mLruConnectionTracker;
|
|
private final BuildProperties mBuildProperties;
|
|
|
|
/**
|
|
* Local log used for debugging any WifiConfigManager issues.
|
|
*/
|
|
private final LocalLog mLocalLog;
|
|
/**
|
|
* 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 networks that where temporarily disabled by user,
|
|
* framework knows not to autoconnect again even if the app/scorer recommends it.
|
|
* Network will be based on FQDN for passpoint and SSID for non-passpoint.
|
|
* List will be deleted when Wifi turn off, device restart or network settings reset.
|
|
* Also when user manfully select to connect network will unblock that network.
|
|
*/
|
|
private final MissingCounterTimerLockList<String> mUserTemporarilyDisabledList;
|
|
private final NonCarrierMergedNetworksStatusTracker mNonCarrierMergedNetworksStatusTracker;
|
|
|
|
|
|
/**
|
|
* Framework keeps a mapping from configKey to the randomized MAC address so that
|
|
* when a user forgets a network and thne adds it back, the same randomized MAC address
|
|
* will get used.
|
|
*/
|
|
private final Map<String, String> mRandomizedMacAddressMapping;
|
|
|
|
/**
|
|
* Store the network update listeners.
|
|
*/
|
|
private final Set<OnNetworkUpdateListener> mListeners;
|
|
|
|
private final FrameworkFacade mFrameworkFacade;
|
|
private final DeviceConfigFacade mDeviceConfigFacade;
|
|
|
|
/**
|
|
* Verbose logging flag. Toggled by developer options.
|
|
*/
|
|
private boolean mVerboseLoggingEnabled = false;
|
|
/**
|
|
* Current logged in user ID.
|
|
*/
|
|
private int mCurrentUserId = UserHandle.SYSTEM.getIdentifier();
|
|
/**
|
|
* 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;
|
|
/**
|
|
* 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;
|
|
/**
|
|
* 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 NetworkListSharedStoreData mNetworkListSharedStoreData;
|
|
private final NetworkListUserStoreData mNetworkListUserStoreData;
|
|
private final RandomizedMacStoreData mRandomizedMacStoreData;
|
|
|
|
private static class NetworkIdentifier {
|
|
private WifiSsid mSsid;
|
|
private byte[] mOui;
|
|
NetworkIdentifier(WifiSsid ssid, byte[] oui) {
|
|
mSsid = ssid;
|
|
mOui = oui;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return Objects.hash(mSsid, Arrays.hashCode(mOui));
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object otherObj) {
|
|
if (this == otherObj) {
|
|
return true;
|
|
} else if (!(otherObj instanceof NetworkIdentifier)) {
|
|
return false;
|
|
}
|
|
NetworkIdentifier other = (NetworkIdentifier) otherObj;
|
|
return Objects.equals(mSsid, other.mSsid) && Arrays.equals(mOui, other.mOui);
|
|
}
|
|
}
|
|
private final Map<NetworkIdentifier, List<DhcpOption>> mCustomDhcpOptions = new HashMap<>();
|
|
|
|
/**
|
|
* Create new instance of WifiConfigManager.
|
|
*/
|
|
WifiConfigManager(
|
|
Context context,
|
|
WifiKeyStore wifiKeyStore,
|
|
WifiConfigStore wifiConfigStore,
|
|
NetworkListSharedStoreData networkListSharedStoreData,
|
|
NetworkListUserStoreData networkListUserStoreData,
|
|
RandomizedMacStoreData randomizedMacStoreData,
|
|
LruConnectionTracker lruConnectionTracker,
|
|
WifiInjector wifiInjector) {
|
|
mContext = context;
|
|
mWifiInjector = wifiInjector;
|
|
mClock = wifiInjector.getClock();
|
|
mUserManager = wifiInjector.getUserManager();
|
|
mWifiCarrierInfoManager = wifiInjector.getWifiCarrierInfoManager();
|
|
mWifiMetrics = wifiInjector.getWifiMetrics();
|
|
mWifiBlocklistMonitor = wifiInjector.getWifiBlocklistMonitor();
|
|
mWifiLastResortWatchdog = wifiInjector.getWifiLastResortWatchdog();
|
|
mWifiScoreCard = wifiInjector.getWifiScoreCard();
|
|
mWifiPermissionsUtil = wifiInjector.getWifiPermissionsUtil();
|
|
mFrameworkFacade = wifiInjector.getFrameworkFacade();
|
|
mDeviceConfigFacade = wifiInjector.getDeviceConfigFacade();
|
|
mMacAddressUtil = wifiInjector.getMacAddressUtil();
|
|
mBuildProperties = wifiInjector.getBuildProperties();
|
|
|
|
mBackupManagerProxy = new BackupManagerProxy();
|
|
mWifiKeyStore = wifiKeyStore;
|
|
mWifiConfigStore = wifiConfigStore;
|
|
mConfiguredNetworks = new ConfigurationMap(mWifiPermissionsUtil);
|
|
mScanDetailCaches = new HashMap<>(16, 0.75f);
|
|
mUserTemporarilyDisabledList =
|
|
new MissingCounterTimerLockList<>(SCAN_RESULT_MISSING_COUNT_THRESHOLD, mClock);
|
|
mNonCarrierMergedNetworksStatusTracker = new NonCarrierMergedNetworksStatusTracker(mClock);
|
|
mRandomizedMacAddressMapping = new HashMap<>();
|
|
mListeners = new ArraySet<>();
|
|
|
|
// Register store data for network list and deleted ephemeral SSIDs.
|
|
mNetworkListSharedStoreData = networkListSharedStoreData;
|
|
mNetworkListUserStoreData = networkListUserStoreData;
|
|
mRandomizedMacStoreData = randomizedMacStoreData;
|
|
mWifiConfigStore.registerStoreData(mNetworkListSharedStoreData);
|
|
mWifiConfigStore.registerStoreData(mNetworkListUserStoreData);
|
|
mWifiConfigStore.registerStoreData(mRandomizedMacStoreData);
|
|
|
|
mLocalLog = new LocalLog(
|
|
context.getSystemService(ActivityManager.class).isLowRamDevice() ? 128 : 256);
|
|
mLruConnectionTracker = lruConnectionTracker;
|
|
}
|
|
|
|
/**
|
|
* Update the cellular data availability of the default data SIM.
|
|
*/
|
|
public void onCellularConnectivityChanged(@WifiDataStall.CellularDataStatusCode int status) {
|
|
localLog("onCellularConnectivityChanged:" + status);
|
|
if (status == WifiDataStall.CELLULAR_DATA_NOT_AVAILABLE) {
|
|
stopRestrictingAutoJoinToSubscriptionId();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determine if the framework should perform non-persistent MAC randomization when connecting
|
|
* to the SSID or FQDN in the input WifiConfiguration.
|
|
* @param config
|
|
* @return
|
|
*/
|
|
public boolean shouldUseNonPersistentRandomization(WifiConfiguration config) {
|
|
// If this is the secondary STA for multi internet for DBS AP, use non persistent mac
|
|
// randomization, as the primary and secondary STAs could connect to the same SSID.
|
|
if (isMacRandomizationSupported() && config.dbsSecondaryInternet) {
|
|
return true;
|
|
}
|
|
|
|
if (!isMacRandomizationSupported()
|
|
|| config.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_NONE) {
|
|
return false;
|
|
}
|
|
|
|
// Use non-persistent randomization if it's forced on by dev option
|
|
if (mFrameworkFacade.getIntegerSetting(mContext,
|
|
NON_PERSISTENT_MAC_RANDOMIZATION_FEATURE_FORCE_ENABLE_FLAG, 0) == 1) {
|
|
return true;
|
|
}
|
|
|
|
// use non-persistent or persistent randomization if configured to do so.
|
|
if (config.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_NON_PERSISTENT) {
|
|
return true;
|
|
}
|
|
if (config.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_PERSISTENT) {
|
|
return false;
|
|
}
|
|
|
|
// otherwise the wifi frameworks should decide automatically
|
|
if (config.getIpConfiguration().getIpAssignment() == IpConfiguration.IpAssignment.STATIC) {
|
|
return false;
|
|
}
|
|
if (config.isOpenNetwork() && shouldEnableNonPersistentRandomizationOnOpenNetwork(config)) {
|
|
return true;
|
|
}
|
|
if (config.isPasspoint()) {
|
|
return isNetworkOptInForNonPersistentRandomization(config.FQDN);
|
|
} else {
|
|
return isNetworkOptInForNonPersistentRandomization(config.SSID);
|
|
}
|
|
}
|
|
|
|
private boolean shouldEnableNonPersistentRandomizationOnOpenNetwork(WifiConfiguration config) {
|
|
if (!mDeviceConfigFacade.allowNonPersistentMacRandomizationOnOpenSsids()
|
|
&& !mContext.getResources().getBoolean(
|
|
R.bool.config_wifiAllowNonPersistentMacRandomizationOnOpenSsids)) {
|
|
return false;
|
|
}
|
|
return config.getNetworkSelectionStatus().hasEverConnected()
|
|
&& config.getNetworkSelectionStatus().hasNeverDetectedCaptivePortal();
|
|
}
|
|
|
|
private boolean isNetworkOptInForNonPersistentRandomization(String ssidOrFqdn) {
|
|
Set<String> perDeviceSsidBlocklist = new ArraySet<>(mContext.getResources().getStringArray(
|
|
R.array.config_wifi_non_persistent_randomization_ssid_blocklist));
|
|
if (mDeviceConfigFacade.getNonPersistentMacRandomizationSsidBlocklist().contains(ssidOrFqdn)
|
|
|| perDeviceSsidBlocklist.contains(ssidOrFqdn)) {
|
|
return false;
|
|
}
|
|
Set<String> perDeviceSsidAllowlist = new ArraySet<>(mContext.getResources().getStringArray(
|
|
R.array.config_wifi_non_persistent_randomization_ssid_allowlist));
|
|
return mDeviceConfigFacade.getNonPersistentMacRandomizationSsidAllowlist()
|
|
.contains(ssidOrFqdn) || perDeviceSsidAllowlist.contains(ssidOrFqdn);
|
|
}
|
|
|
|
@VisibleForTesting
|
|
protected int getRandomizedMacAddressMappingSize() {
|
|
return mRandomizedMacAddressMapping.size();
|
|
}
|
|
|
|
/**
|
|
* The persistent randomized MAC address is locally generated for each SSID and does not
|
|
* change until factory reset of the device. In the initial Q release the per-SSID randomized
|
|
* MAC is saved on the device, but in an update the storing of randomized MAC is removed.
|
|
* Instead, the randomized MAC is calculated directly from the SSID and a on device secret.
|
|
* For backward compatibility, this method first checks the device storage for saved
|
|
* randomized MAC. If it is not found or the saved MAC is invalid then it will calculate the
|
|
* randomized MAC directly.
|
|
*
|
|
* In the future as devices launched on Q no longer get supported, this method should get
|
|
* simplified to return the calculated MAC address directly.
|
|
* @param config the WifiConfiguration to obtain MAC address for.
|
|
* @return persistent MAC address for this WifiConfiguration
|
|
*/
|
|
@VisibleForTesting
|
|
public MacAddress getPersistentMacAddress(WifiConfiguration config) {
|
|
// mRandomizedMacAddressMapping had been the location to save randomized MAC addresses.
|
|
String persistentMacString = mRandomizedMacAddressMapping.get(
|
|
config.getNetworkKey());
|
|
// Use the MAC address stored in the storage if it exists and is valid. Otherwise
|
|
// use the MAC address calculated from a hash function as the persistent MAC.
|
|
if (persistentMacString != null) {
|
|
try {
|
|
return MacAddress.fromString(persistentMacString);
|
|
} catch (IllegalArgumentException e) {
|
|
Log.e(TAG, "Error creating randomized MAC address from stored value.");
|
|
mRandomizedMacAddressMapping.remove(config.getNetworkKey());
|
|
}
|
|
}
|
|
MacAddress result = mMacAddressUtil.calculatePersistentMac(config.getNetworkKey(),
|
|
mMacAddressUtil.obtainMacRandHashFunction(Process.WIFI_UID));
|
|
if (result == null) {
|
|
result = mMacAddressUtil.calculatePersistentMac(config.getNetworkKey(),
|
|
mMacAddressUtil.obtainMacRandHashFunction(Process.WIFI_UID));
|
|
}
|
|
if (result == null) {
|
|
Log.wtf(TAG, "Failed to generate MAC address from KeyStore even after retrying. "
|
|
+ "Using locally generated MAC address instead.");
|
|
result = config.getRandomizedMacAddress();
|
|
if (DEFAULT_MAC_ADDRESS.equals(result)) {
|
|
result = MacAddressUtils.createRandomUnicastAddress();
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Sets the randomized MAC expiration time based on the DHCP lease duration.
|
|
* This should be called every time DHCP lease information is obtained.
|
|
*/
|
|
public void updateRandomizedMacExpireTime(WifiConfiguration config, long dhcpLeaseSeconds) {
|
|
WifiConfiguration internalConfig = getInternalConfiguredNetwork(config.networkId);
|
|
if (internalConfig == null) {
|
|
return;
|
|
}
|
|
long expireDurationMs = (dhcpLeaseSeconds & 0xffffffffL) * 1000;
|
|
expireDurationMs = Math.max(NON_PERSISTENT_MAC_REFRESH_MS_MIN, expireDurationMs);
|
|
expireDurationMs = Math.min(NON_PERSISTENT_MAC_REFRESH_MS_MAX, expireDurationMs);
|
|
internalConfig.randomizedMacExpirationTimeMs = mClock.getWallClockMillis()
|
|
+ expireDurationMs;
|
|
}
|
|
|
|
private void setRandomizedMacAddress(WifiConfiguration config, MacAddress mac) {
|
|
config.setRandomizedMacAddress(mac);
|
|
config.randomizedMacLastModifiedTimeMs = mClock.getWallClockMillis();
|
|
}
|
|
|
|
/**
|
|
* Obtain the persistent MAC address by first reading from an internal database. If non exists
|
|
* then calculate the persistent MAC using HMAC-SHA256.
|
|
* Finally set the randomized MAC of the configuration to the randomized MAC obtained.
|
|
* @param config the WifiConfiguration to make the update
|
|
* @return the persistent MacAddress or null if the operation is unsuccessful
|
|
*/
|
|
private MacAddress setRandomizedMacToPersistentMac(WifiConfiguration config) {
|
|
MacAddress persistentMac = getPersistentMacAddress(config);
|
|
if (persistentMac == null || persistentMac.equals(config.getRandomizedMacAddress())) {
|
|
return persistentMac;
|
|
}
|
|
WifiConfiguration internalConfig = getInternalConfiguredNetwork(config.networkId);
|
|
setRandomizedMacAddress(internalConfig, persistentMac);
|
|
return persistentMac;
|
|
}
|
|
|
|
/**
|
|
* This method is called before connecting to a network that has non-persistent randomization
|
|
* enabled, and will re-randomize the MAC address if needed.
|
|
* @param config the WifiConfiguration to make the update
|
|
* @return the updated MacAddress
|
|
*/
|
|
private MacAddress updateRandomizedMacIfNeeded(WifiConfiguration config) {
|
|
boolean shouldUpdateMac = config.randomizedMacExpirationTimeMs
|
|
< mClock.getWallClockMillis() || mClock.getWallClockMillis()
|
|
- config.randomizedMacLastModifiedTimeMs >= NON_PERSISTENT_MAC_REFRESH_MS_MAX;
|
|
if (!shouldUpdateMac) {
|
|
return config.getRandomizedMacAddress();
|
|
}
|
|
WifiConfiguration internalConfig = getInternalConfiguredNetwork(config.networkId);
|
|
setRandomizedMacAddress(internalConfig, MacAddressUtils.createRandomUnicastAddress());
|
|
return internalConfig.getRandomizedMacAddress();
|
|
}
|
|
|
|
/**
|
|
* Returns the randomized MAC address that should be used for this WifiConfiguration.
|
|
* This API may return a randomized MAC different from the persistent randomized MAC if
|
|
* the WifiConfiguration is configured for non-persistent MAC randomization.
|
|
* @param config
|
|
* @return MacAddress
|
|
*/
|
|
public MacAddress getRandomizedMacAndUpdateIfNeeded(WifiConfiguration config) {
|
|
MacAddress mac = shouldUseNonPersistentRandomization(config)
|
|
? updateRandomizedMacIfNeeded(config)
|
|
: setRandomizedMacToPersistentMac(config);
|
|
return mac;
|
|
}
|
|
|
|
/**
|
|
* Enable/disable verbose logging in WifiConfigManager & its helper classes.
|
|
*/
|
|
public void enableVerboseLogging(boolean verbose) {
|
|
mVerboseLoggingEnabled = verbose;
|
|
mWifiConfigStore.enableVerboseLogging(mVerboseLoggingEnabled);
|
|
mWifiKeyStore.enableVerboseLogging(mVerboseLoggingEnabled);
|
|
mWifiBlocklistMonitor.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 (configuration.enterpriseConfig != null && !TextUtils.isEmpty(
|
|
configuration.enterpriseConfig.getPassword())) {
|
|
configuration.enterpriseConfig.setPassword(PASSWORD_MASK);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper method to mask randomized MAC address from the provided WifiConfiguration Object.
|
|
* This is needed when the network configurations are being requested via the public
|
|
* WifiManager API's. This method puts "02:00:00:00:00:00" as the MAC address.
|
|
* @param configuration WifiConfiguration to hide the MAC address
|
|
*/
|
|
private void maskRandomizedMacAddressInWifiConfiguration(WifiConfiguration configuration) {
|
|
setRandomizedMacAddress(configuration, DEFAULT_MAC_ADDRESS);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* @param targetUid Target UID for MAC address reading: -1 = mask all, 0 = mask none, >0 =
|
|
* mask all but the targetUid (carrier app).
|
|
* @return Copy of the WifiConfiguration object, or a default WifiConfiguration if the input
|
|
* is null.
|
|
*/
|
|
private @NonNull WifiConfiguration createExternalWifiConfiguration(
|
|
@NonNull WifiConfiguration configuration, boolean maskPasswords, int targetUid) {
|
|
if (configuration == null) {
|
|
Log.wtf(TAG, "Unexpected null configuration in createExternalWifiConfiguration");
|
|
return new WifiConfiguration();
|
|
}
|
|
WifiConfiguration network = new WifiConfiguration(configuration);
|
|
if (maskPasswords) {
|
|
maskPasswordsInWifiConfiguration(network);
|
|
}
|
|
if (targetUid != Process.WIFI_UID && targetUid != Process.SYSTEM_UID
|
|
&& targetUid != configuration.creatorUid) {
|
|
maskRandomizedMacAddressInWifiConfiguration(network);
|
|
}
|
|
if (!isMacRandomizationSupported()) {
|
|
network.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE;
|
|
}
|
|
return network;
|
|
}
|
|
|
|
/**
|
|
* Returns whether MAC randomization is supported on this device.
|
|
* @param config
|
|
* @return
|
|
*/
|
|
private boolean isMacRandomizationSupported() {
|
|
return mContext.getResources().getBoolean(
|
|
R.bool.config_wifi_connected_mac_randomization_supported);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* @param targetUid Target UID for MAC address reading: -1 (Invalid UID) = mask all,
|
|
* WIFI||SYSTEM = mask none, <other> = mask all but the targetUid (carrier
|
|
* app).
|
|
* @return List of WifiConfiguration objects representing the networks.
|
|
*/
|
|
private List<WifiConfiguration> getConfiguredNetworks(
|
|
boolean savedOnly, boolean maskPasswords, int targetUid) {
|
|
List<WifiConfiguration> networks = new ArrayList<>();
|
|
for (WifiConfiguration config : getInternalConfiguredNetworks()) {
|
|
if (savedOnly && (config.ephemeral || config.isPasspoint())) {
|
|
continue;
|
|
}
|
|
networks.add(createExternalWifiConfiguration(config, maskPasswords, targetUid));
|
|
}
|
|
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, Process.WIFI_UID);
|
|
}
|
|
|
|
/**
|
|
* 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, Process.WIFI_UID);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the list of all configured networks with the passwords masked.
|
|
*
|
|
* @return List of WifiConfiguration objects representing the networks.
|
|
*/
|
|
public List<WifiConfiguration> getSavedNetworks(int targetUid) {
|
|
return getConfiguredNetworks(true, true, targetUid);
|
|
}
|
|
|
|
/**
|
|
* 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 @Nullable 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, Process.WIFI_UID);
|
|
}
|
|
|
|
/**
|
|
* 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 @Nullable 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, Process.WIFI_UID);
|
|
}
|
|
|
|
/**
|
|
* 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 @Nullable 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, Process.WIFI_UID);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the configured network corresponding to the provided networkId
|
|
* without any masking.
|
|
*
|
|
* WARNING: Don't use this to pass network configurations except in the wifi stack, when
|
|
* there is a need for passwords and randomized MAC address.
|
|
*
|
|
* @param networkId networkId of the requested network.
|
|
* @return Copy of WifiConfiguration object if found, null otherwise.
|
|
*/
|
|
public @Nullable WifiConfiguration getConfiguredNetworkWithoutMasking(int networkId) {
|
|
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
|
|
if (config == null) {
|
|
return null;
|
|
}
|
|
return new WifiConfiguration(config);
|
|
}
|
|
|
|
/**
|
|
* Helper method to retrieve all the internal WifiConfiguration objects corresponding to all
|
|
* the networks in our database.
|
|
*/
|
|
private Collection<WifiConfiguration> getInternalConfiguredNetworks() {
|
|
return mConfiguredNetworks.valuesForCurrentUser();
|
|
}
|
|
|
|
private @Nullable WifiConfiguration getInternalConfiguredNetworkByUpgradableType(
|
|
@NonNull WifiConfiguration config) {
|
|
WifiConfiguration internalConfig = null;
|
|
int securityType = config.getDefaultSecurityParams().getSecurityType();
|
|
WifiConfiguration possibleExistingConfig = new WifiConfiguration(config);
|
|
switch (securityType) {
|
|
case WifiConfiguration.SECURITY_TYPE_PSK:
|
|
possibleExistingConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
|
|
break;
|
|
case WifiConfiguration.SECURITY_TYPE_SAE:
|
|
possibleExistingConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
|
|
break;
|
|
case WifiConfiguration.SECURITY_TYPE_EAP:
|
|
possibleExistingConfig.setSecurityParams(
|
|
WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
|
|
break;
|
|
case WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE:
|
|
possibleExistingConfig.setSecurityParams(
|
|
WifiConfiguration.SECURITY_TYPE_EAP);
|
|
break;
|
|
case WifiConfiguration.SECURITY_TYPE_OPEN:
|
|
possibleExistingConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE);
|
|
break;
|
|
case WifiConfiguration.SECURITY_TYPE_OWE:
|
|
possibleExistingConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
|
|
break;
|
|
default:
|
|
return null;
|
|
}
|
|
internalConfig = mConfiguredNetworks.getByConfigKeyForCurrentUser(
|
|
possibleExistingConfig.getProfileKey());
|
|
return internalConfig;
|
|
}
|
|
|
|
/**
|
|
* 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 @Nullable WifiConfiguration getInternalConfiguredNetwork(
|
|
@NonNull WifiConfiguration config) {
|
|
WifiConfiguration internalConfig = mConfiguredNetworks.getForCurrentUser(config.networkId);
|
|
if (internalConfig != null) {
|
|
return internalConfig;
|
|
}
|
|
internalConfig = mConfiguredNetworks.getByConfigKeyForCurrentUser(
|
|
config.getProfileKey());
|
|
if (internalConfig != null) {
|
|
return internalConfig;
|
|
}
|
|
internalConfig = getInternalConfiguredNetworkByUpgradableType(config);
|
|
if (internalConfig == null) {
|
|
Log.e(TAG, "Cannot find network with networkId " + config.networkId
|
|
+ " or configKey " + config.getProfileKey()
|
|
+ " or upgradable security type check");
|
|
}
|
|
return internalConfig;
|
|
}
|
|
|
|
/**
|
|
* Helper method to retrieve the internal WifiConfiguration object corresponding to the
|
|
* provided network ID in our database.
|
|
*/
|
|
private @Nullable WifiConfiguration getInternalConfiguredNetwork(int networkId) {
|
|
if (networkId == WifiConfiguration.INVALID_NETWORK_ID) {
|
|
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 @Nullable 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 network configurations
|
|
* changed.
|
|
*
|
|
* In Android R we stopped sending out WifiConfiguration due to user privacy concerns.
|
|
* Thus, no matter how many networks changed,
|
|
* {@link WifiManager#EXTRA_MULTIPLE_NETWORKS_CHANGED} is always set to true, and
|
|
* {@link WifiManager#EXTRA_WIFI_CONFIGURATION} is always null.
|
|
*
|
|
* @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(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, true);
|
|
intent.putExtra(WifiManager.EXTRA_CHANGE_REASON, reason);
|
|
mContext.sendBroadcastAsUser(intent, UserHandle.ALL, Manifest.permission.ACCESS_WIFI_STATE);
|
|
}
|
|
|
|
/**
|
|
* 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 packageName Package name of the app requesting the modification.
|
|
*/
|
|
private boolean canModifyNetwork(WifiConfiguration config, int uid,
|
|
@Nullable String packageName) {
|
|
if (uid == Process.WIFI_UID) {
|
|
return true;
|
|
}
|
|
// Passpoint configurations are generated and managed by PasspointManager. They can be
|
|
// added by either PasspointNetworkNominator (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
|
|
&& config.enterpriseConfig.isAuthenticationSimBased()) {
|
|
return true;
|
|
}
|
|
|
|
// TODO: ideally package should not be null here (and hence we wouldn't need the
|
|
// isDeviceOwner(uid) method), but it would require changing many methods to pass the
|
|
// package name around (for example, all methods called by
|
|
// WifiServiceImpl.triggerConnectAndReturnStatus(netId, callingUid)
|
|
final boolean isOrganizationOwnedDeviceAdmin =
|
|
mWifiPermissionsUtil.isOrganizationOwnedDeviceAdmin(uid, packageName);
|
|
|
|
// If |uid| corresponds to the device owner or the profile owner of an organization owned
|
|
// device, allow all modifications.
|
|
if (isOrganizationOwnedDeviceAdmin) {
|
|
return true;
|
|
}
|
|
|
|
final boolean isCreator = (config.creatorUid == uid);
|
|
|
|
// WiFi config lockdown related logic. At this point we know uid is NOT a Device Owner
|
|
// or a Profile Owner of an organization owned device.
|
|
final boolean isConfigEligibleForLockdown =
|
|
mWifiPermissionsUtil.isOrganizationOwnedDeviceAdmin(config.creatorUid,
|
|
config.creatorName);
|
|
if (!isConfigEligibleForLockdown) {
|
|
// App that created the network or settings app (i.e user) has permission to
|
|
// modify the network.
|
|
return isCreator
|
|
|| mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)
|
|
|| mWifiPermissionsUtil.checkNetworkSetupWizardPermission(uid);
|
|
}
|
|
|
|
final ContentResolver resolver = mContext.getContentResolver();
|
|
final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver,
|
|
Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0;
|
|
return !isLockdownFeatureEnabled
|
|
// If not locked down, settings app (i.e user) has permission to modify the network.
|
|
&& (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)
|
|
|| mWifiPermissionsUtil.checkNetworkSetupWizardPermission(uid));
|
|
}
|
|
|
|
private void mergeSecurityParamsListWithInternalWifiConfiguration(
|
|
WifiConfiguration internalConfig, WifiConfiguration externalConfig) {
|
|
// If not set, just copy over all list.
|
|
if (internalConfig.getSecurityParamsList().isEmpty()) {
|
|
internalConfig.setSecurityParams(externalConfig.getSecurityParamsList());
|
|
return;
|
|
}
|
|
|
|
WifiConfigurationUtil.addUpgradableSecurityTypeIfNecessary(externalConfig);
|
|
|
|
// An external caller is only allowed to set one type manually.
|
|
// As a result, only default type matters.
|
|
// There might be 3 cases:
|
|
// 1. Existing config with new upgradable type config,
|
|
// ex. PSK/SAE config with SAE config.
|
|
// 2. Existing configuration with downgradable type config,
|
|
// ex. SAE config with PSK config.
|
|
// 3. The new type is not a compatible type of existing config.
|
|
// ex. Open config with PSK config.
|
|
// This might happen when updating a config via network ID directly.
|
|
int oldType = internalConfig.getDefaultSecurityParams().getSecurityType();
|
|
int newType = externalConfig.getDefaultSecurityParams().getSecurityType();
|
|
if (oldType != newType) {
|
|
if (internalConfig.isSecurityType(newType)) {
|
|
internalConfig.setSecurityParamsIsAddedByAutoUpgrade(newType, false);
|
|
} else if (externalConfig.isSecurityType(oldType)) {
|
|
internalConfig.setSecurityParams(newType);
|
|
internalConfig.addSecurityParams(oldType);
|
|
} else {
|
|
internalConfig.setSecurityParams(externalConfig.getSecurityParamsList());
|
|
}
|
|
}
|
|
}
|
|
|
|
private void mergeDppSecurityParamsWithInternalWifiConfiguration(
|
|
WifiConfiguration internalConfig, WifiConfiguration externalConfig) {
|
|
// Do not update for non-DPP network
|
|
if (!externalConfig.isSecurityType(WifiConfiguration.SECURITY_TYPE_DPP)) {
|
|
return;
|
|
}
|
|
|
|
if (externalConfig.getDppConnector().length != 0
|
|
&& externalConfig.getDppCSignKey().length != 0
|
|
&& externalConfig.getDppNetAccessKey().length != 0) {
|
|
internalConfig.setDppConnectionKeys(externalConfig.getDppConnector(),
|
|
externalConfig.getDppCSignKey(), externalConfig.getDppNetAccessKey());
|
|
}
|
|
|
|
if (externalConfig.getDppPrivateEcKey().length != 0) {
|
|
internalConfig.setDppConfigurator(externalConfig.getDppPrivateEcKey());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
}
|
|
|
|
mergeSecurityParamsListWithInternalWifiConfiguration(internalConfig, externalConfig);
|
|
mergeDppSecurityParamsWithInternalWifiConfiguration(internalConfig, externalConfig);
|
|
|
|
// 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()));
|
|
}
|
|
}
|
|
}
|
|
|
|
internalConfig.allowAutojoin = externalConfig.allowAutojoin;
|
|
// Copy over the |WifiEnterpriseConfig| parameters if set.
|
|
if (externalConfig.enterpriseConfig != null) {
|
|
internalConfig.enterpriseConfig.copyFromExternal(
|
|
externalConfig.enterpriseConfig, PASSWORD_MASK);
|
|
}
|
|
|
|
// Copy over any metered information.
|
|
internalConfig.meteredHint = externalConfig.meteredHint;
|
|
internalConfig.meteredOverride = externalConfig.meteredOverride;
|
|
|
|
internalConfig.trusted = externalConfig.trusted;
|
|
internalConfig.oemPaid = externalConfig.oemPaid;
|
|
internalConfig.oemPrivate = externalConfig.oemPrivate;
|
|
internalConfig.dbsSecondaryInternet = externalConfig.dbsSecondaryInternet;
|
|
internalConfig.carrierMerged = externalConfig.carrierMerged;
|
|
internalConfig.restricted = externalConfig.restricted;
|
|
|
|
// Copy over macRandomizationSetting
|
|
internalConfig.macRandomizationSetting = externalConfig.macRandomizationSetting;
|
|
internalConfig.carrierId = externalConfig.carrierId;
|
|
internalConfig.isHomeProviderNetwork = externalConfig.isHomeProviderNetwork;
|
|
internalConfig.subscriptionId = externalConfig.subscriptionId;
|
|
internalConfig.setSubscriptionGroup(externalConfig.getSubscriptionGroup());
|
|
internalConfig.getNetworkSelectionStatus()
|
|
.setConnectChoice(externalConfig.getNetworkSelectionStatus().getConnectChoice());
|
|
internalConfig.getNetworkSelectionStatus().setConnectChoiceRssi(
|
|
externalConfig.getNetworkSelectionStatus().getConnectChoiceRssi());
|
|
internalConfig.setBssidAllowlist(externalConfig.getBssidAllowlistInternal());
|
|
internalConfig.setRepeaterEnabled(externalConfig.isRepeaterEnabled());
|
|
}
|
|
|
|
/**
|
|
* 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.setIpAssignment(IpConfiguration.IpAssignment.DHCP);
|
|
configuration.setProxySettings(IpConfiguration.ProxySettings.NONE);
|
|
|
|
configuration.status = WifiConfiguration.Status.DISABLED;
|
|
configuration.getNetworkSelectionStatus().setNetworkSelectionStatus(
|
|
NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED);
|
|
configuration.getNetworkSelectionStatus().setNetworkSelectionDisableReason(
|
|
NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER);
|
|
}
|
|
|
|
/**
|
|
* 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, @Nullable String packageName) {
|
|
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);
|
|
|
|
// Convert legacy fields to new security params
|
|
externalConfig.convertLegacyFieldsToSecurityParamsIfNeeded();
|
|
|
|
// 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.noInternetAccessExpected = externalConfig.noInternetAccessExpected;
|
|
newInternalConfig.ephemeral = externalConfig.ephemeral;
|
|
newInternalConfig.osu = externalConfig.osu;
|
|
newInternalConfig.fromWifiNetworkSuggestion = externalConfig.fromWifiNetworkSuggestion;
|
|
newInternalConfig.fromWifiNetworkSpecifier = externalConfig.fromWifiNetworkSpecifier;
|
|
newInternalConfig.useExternalScores = externalConfig.useExternalScores;
|
|
newInternalConfig.shared = externalConfig.shared;
|
|
newInternalConfig.updateIdentifier = externalConfig.updateIdentifier;
|
|
newInternalConfig.setPasspointUniqueId(externalConfig.getPasspointUniqueId());
|
|
|
|
// Add debug information for network addition.
|
|
newInternalConfig.creatorUid = newInternalConfig.lastUpdateUid = uid;
|
|
newInternalConfig.creatorName = newInternalConfig.lastUpdateName =
|
|
packageName != null ? packageName : mContext.getPackageManager().getNameForUid(uid);
|
|
newInternalConfig.lastUpdated = mClock.getWallClockMillis();
|
|
newInternalConfig.numRebootsSinceLastUse = 0;
|
|
initRandomizedMacForInternalConfig(newInternalConfig);
|
|
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.
|
|
* @param overrideCreator when this set to true, will overrider the creator to the current
|
|
* modifier.
|
|
* @return Copy of existing WifiConfiguration object with parameters merged from the provided
|
|
* configuration.
|
|
*/
|
|
private @NonNull WifiConfiguration updateExistingInternalWifiConfigurationFromExternal(
|
|
@NonNull WifiConfiguration internalConfig, @NonNull WifiConfiguration externalConfig,
|
|
int uid, @Nullable String packageName, boolean overrideCreator) {
|
|
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 =
|
|
packageName != null ? packageName : mContext.getPackageManager().getNameForUid(uid);
|
|
newInternalConfig.lastUpdated = mClock.getWallClockMillis();
|
|
newInternalConfig.numRebootsSinceLastUse = 0;
|
|
if (overrideCreator) {
|
|
newInternalConfig.creatorName = newInternalConfig.lastUpdateName;
|
|
newInternalConfig.creatorUid = uid;
|
|
}
|
|
return newInternalConfig;
|
|
}
|
|
|
|
private void logUserActionEvents(WifiConfiguration before, WifiConfiguration after) {
|
|
// Logs changes in meteredOverride.
|
|
if (before.meteredOverride != after.meteredOverride) {
|
|
mWifiMetrics.logUserActionEvent(
|
|
WifiMetrics.convertMeteredOverrideEnumToUserActionEventType(
|
|
after.meteredOverride),
|
|
after.networkId);
|
|
}
|
|
|
|
// Logs changes in macRandomizationSetting.
|
|
if (before.macRandomizationSetting != after.macRandomizationSetting) {
|
|
mWifiMetrics.logUserActionEvent(
|
|
after.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_NONE
|
|
? UserActionEvent.EVENT_CONFIGURE_MAC_RANDOMIZATION_OFF
|
|
: UserActionEvent.EVENT_CONFIGURE_MAC_RANDOMIZATION_ON,
|
|
after.networkId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* @param packageName Package name of the app requesting the network addition/modification.
|
|
* @param overrideCreator when this set to true, will overrider the creator to the current
|
|
* modifier.
|
|
* @return NetworkUpdateResult object representing status of the update.
|
|
* WifiConfiguration object representing the existing configuration matching
|
|
* the new config, or null if none matches.
|
|
*/
|
|
private @NonNull Pair<NetworkUpdateResult, WifiConfiguration> addOrUpdateNetworkInternal(
|
|
@NonNull WifiConfiguration config, int uid, @Nullable String packageName,
|
|
boolean overrideCreator) {
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.v(TAG, "Adding/Updating network " + config.getPrintableSsid());
|
|
}
|
|
WifiConfiguration newInternalConfig = null;
|
|
|
|
long supportedFeatures = mWifiInjector.getActiveModeWarden()
|
|
.getPrimaryClientModeManager().getSupportedFeatures();
|
|
|
|
// 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, supportedFeatures,
|
|
WifiConfigurationUtil.VALIDATE_FOR_ADD)) {
|
|
Log.e(TAG, "Cannot add network with invalid config");
|
|
return new Pair<>(
|
|
new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID),
|
|
existingInternalConfig);
|
|
}
|
|
newInternalConfig =
|
|
createNewInternalWifiConfigurationFromExternal(config, uid, packageName);
|
|
// 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.getProfileKey());
|
|
}
|
|
// Existing network found. So, a network update.
|
|
if (existingInternalConfig != null) {
|
|
if (!WifiConfigurationUtil.validate(
|
|
config, supportedFeatures, WifiConfigurationUtil.VALIDATE_FOR_UPDATE)) {
|
|
Log.e(TAG, "Cannot update network with invalid config");
|
|
return new Pair<>(
|
|
new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID),
|
|
existingInternalConfig);
|
|
}
|
|
// Check for the app's permission before we let it update this network.
|
|
if (!canModifyNetwork(existingInternalConfig, uid, packageName)) {
|
|
Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
|
|
+ config.getProfileKey());
|
|
return new Pair<>(
|
|
new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID),
|
|
existingInternalConfig);
|
|
}
|
|
if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)
|
|
&& !config.isPasspoint()) {
|
|
logUserActionEvents(existingInternalConfig, config);
|
|
}
|
|
newInternalConfig =
|
|
updateExistingInternalWifiConfigurationFromExternal(
|
|
existingInternalConfig, config, uid, packageName, overrideCreator);
|
|
}
|
|
|
|
if (!WifiConfigurationUtil.addUpgradableSecurityTypeIfNecessary(newInternalConfig)) {
|
|
return new Pair<>(
|
|
new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID),
|
|
existingInternalConfig);
|
|
}
|
|
|
|
// Only add networks with proxy settings if the user has permission to
|
|
if (WifiConfigurationUtil.hasProxyChanged(existingInternalConfig, newInternalConfig)
|
|
&& !canModifyProxySettings(uid, packageName)) {
|
|
Log.e(TAG, "UID " + uid + " does not have permission to modify proxy Settings "
|
|
+ config.getProfileKey() + ". Must have NETWORK_SETTINGS,"
|
|
+ " or be device or profile owner.");
|
|
return new Pair<>(
|
|
new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID),
|
|
existingInternalConfig);
|
|
}
|
|
|
|
// Only allow changes in Repeater Enabled flag if the user has permission to
|
|
if (WifiConfigurationUtil.hasRepeaterEnabledChanged(
|
|
existingInternalConfig, newInternalConfig)
|
|
&& !mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) {
|
|
Log.e(TAG, "UID " + uid
|
|
+ " does not have permission to modify Repeater Enabled Settings "
|
|
+ " , or add a network with Repeater Enabled set to true "
|
|
+ config.getProfileKey() + ". Must have NETWORK_SETTINGS.");
|
|
return new Pair<>(
|
|
new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID),
|
|
existingInternalConfig);
|
|
}
|
|
|
|
if (WifiConfigurationUtil.hasMacRandomizationSettingsChanged(existingInternalConfig,
|
|
newInternalConfig) && !mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)
|
|
&& !mWifiPermissionsUtil.checkNetworkSetupWizardPermission(uid)
|
|
&& !(newInternalConfig.isPasspoint() && uid == newInternalConfig.creatorUid)
|
|
&& !config.fromWifiNetworkSuggestion
|
|
&& !mWifiPermissionsUtil.isDeviceInDemoMode(mContext)
|
|
&& !(mWifiPermissionsUtil.isAdmin(uid, packageName)
|
|
&& uid == newInternalConfig.creatorUid)) {
|
|
Log.e(TAG, "UID " + uid + " does not have permission to modify MAC randomization "
|
|
+ "Settings " + config.getProfileKey() + ". Must have "
|
|
+ "NETWORK_SETTINGS or NETWORK_SETUP_WIZARD or be in Demo Mode "
|
|
+ "or be the creator adding or updating a passpoint network "
|
|
+ "or be an admin updating their own network.");
|
|
return new Pair<>(
|
|
new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID),
|
|
existingInternalConfig);
|
|
}
|
|
|
|
if (config.isEnterprise()
|
|
&& config.enterpriseConfig.isEapMethodServerCertUsed()
|
|
&& !config.enterpriseConfig.isMandatoryParameterSetForServerCertValidation()
|
|
&& !config.enterpriseConfig.isTrustOnFirstUseEnabled()) {
|
|
boolean isSettingsOrSuw = mContext.checkPermission(Manifest.permission.NETWORK_SETTINGS,
|
|
-1 /* pid */, uid) == PERMISSION_GRANTED
|
|
|| mContext.checkPermission(Manifest.permission.NETWORK_SETUP_WIZARD,
|
|
-1 /* pid */, uid) == PERMISSION_GRANTED;
|
|
if (!(mWifiInjector.getWifiGlobals().isInsecureEnterpriseConfigurationAllowed()
|
|
&& isSettingsOrSuw)) {
|
|
Log.e(TAG, "Enterprise network configuration is missing either a Root CA "
|
|
+ "or a domain name");
|
|
return new Pair<>(
|
|
new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID),
|
|
existingInternalConfig);
|
|
}
|
|
Log.w(TAG, "Insecure Enterprise network " + config.SSID
|
|
+ " configured by Settings/SUW");
|
|
|
|
// Implicit user approval, when creating an insecure connection which is allowed
|
|
// in the configuration of the device
|
|
newInternalConfig.enterpriseConfig.setUserApproveNoCaCert(true);
|
|
}
|
|
|
|
// Update the keys for saved enterprise networks. For Passpoint, the certificates
|
|
// and keys are installed at the time the provider is installed. For suggestion enterprise
|
|
// network the certificates and keys are installed at the time the suggestion is added
|
|
if (!config.isPasspoint() && !config.fromWifiNetworkSuggestion && config.isEnterprise()) {
|
|
if (!(mWifiKeyStore.updateNetworkKeys(newInternalConfig, existingInternalConfig))) {
|
|
return new Pair<>(
|
|
new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID),
|
|
existingInternalConfig);
|
|
}
|
|
}
|
|
|
|
// Validate an Enterprise network with Trust On First Use.
|
|
if (config.isEnterprise() && config.enterpriseConfig.isTrustOnFirstUseEnabled()) {
|
|
if ((supportedFeatures & WIFI_FEATURE_TRUST_ON_FIRST_USE) == 0) {
|
|
Log.e(TAG, "Trust On First Use could not be set "
|
|
+ "when Trust On First Use is not supported.");
|
|
return new Pair<>(
|
|
new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID),
|
|
existingInternalConfig);
|
|
}
|
|
if (!config.enterpriseConfig.isEapMethodServerCertUsed()) {
|
|
Log.e(TAG, "Trust On First Use could not be set "
|
|
+ "when the server certificate is not used.");
|
|
return new Pair<>(
|
|
new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID),
|
|
existingInternalConfig);
|
|
} else if (config.enterpriseConfig.hasCaCertificate()) {
|
|
Log.e(TAG, "Trust On First Use could not be set "
|
|
+ "when Root CA certificate is set.");
|
|
return new Pair<>(
|
|
new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID),
|
|
existingInternalConfig);
|
|
}
|
|
}
|
|
|
|
boolean newNetwork = (existingInternalConfig == null);
|
|
// This is needed to inform IpClient 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 {
|
|
if (null != existingInternalConfig) {
|
|
mConfiguredNetworks.remove(existingInternalConfig.networkId);
|
|
}
|
|
mConfiguredNetworks.put(newInternalConfig);
|
|
} catch (IllegalArgumentException e) {
|
|
Log.e(TAG, "Failed to add network to config map", e);
|
|
return new Pair<>(
|
|
new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID),
|
|
existingInternalConfig);
|
|
}
|
|
if (removeExcessNetworks(uid, packageName)) {
|
|
if (mConfiguredNetworks.getForAllUsers(newInternalConfig.networkId) == null) {
|
|
Log.e(TAG, "Cannot add network because number of configured networks is maxed.");
|
|
return new Pair<>(
|
|
new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID),
|
|
existingInternalConfig);
|
|
}
|
|
}
|
|
|
|
// Only re-enable network: 1. add or update user saved network; 2. add or update a user
|
|
// saved passpoint network framework consider it is a new network.
|
|
if (!newInternalConfig.fromWifiNetworkSuggestion
|
|
&& (!newInternalConfig.isPasspoint() || newNetwork)) {
|
|
userEnabledNetwork(newInternalConfig.networkId);
|
|
}
|
|
|
|
// Stage the backup of the SettingsProvider package which backs this up.
|
|
mBackupManagerProxy.notifyDataChanged();
|
|
|
|
NetworkUpdateResult result = new NetworkUpdateResult(
|
|
newInternalConfig.networkId,
|
|
hasIpChanged,
|
|
hasProxyChanged,
|
|
hasCredentialChanged,
|
|
newNetwork);
|
|
|
|
localLog("addOrUpdateNetworkInternal: added/updated config."
|
|
+ " netId=" + newInternalConfig.networkId
|
|
+ " configKey=" + newInternalConfig.getProfileKey()
|
|
+ " uid=" + Integer.toString(newInternalConfig.creatorUid)
|
|
+ " name=" + newInternalConfig.creatorName);
|
|
return new Pair<>(result, existingInternalConfig);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* @param packageName Package name of the app requesting the network addition/modification.
|
|
* @param overrideCreator when this set to true, will overrider the creator to the current
|
|
* modifier.
|
|
* @return NetworkUpdateResult object representing status of the update.
|
|
*/
|
|
public NetworkUpdateResult addOrUpdateNetwork(WifiConfiguration config, int uid,
|
|
@Nullable String packageName, boolean overrideCreator) {
|
|
if (!mWifiPermissionsUtil.doesUidBelongToCurrentUserOrDeviceOwner(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);
|
|
}
|
|
config.convertLegacyFieldsToSecurityParamsIfNeeded();
|
|
WifiConfiguration existingConfig = getInternalConfiguredNetwork(config);
|
|
if (!config.isEphemeral()) {
|
|
// Removes the existing ephemeral network if it exists to add this configuration.
|
|
if (existingConfig != null && existingConfig.isEphemeral()) {
|
|
// In this case, new connection for this config won't happen because same
|
|
// network is already registered as an ephemeral network.
|
|
// Clear the Ephemeral Network to address the situation.
|
|
removeNetwork(
|
|
existingConfig.networkId, existingConfig.creatorUid, config.creatorName);
|
|
}
|
|
}
|
|
|
|
Pair<NetworkUpdateResult, WifiConfiguration> resultPair = addOrUpdateNetworkInternal(
|
|
config, uid, packageName, overrideCreator);
|
|
NetworkUpdateResult result = resultPair.first;
|
|
existingConfig = resultPair.second;
|
|
if (!result.isSuccess()) {
|
|
Log.e(TAG, "Failed to add/update network " + config.getPrintableSsid());
|
|
return result;
|
|
}
|
|
WifiConfiguration newConfig = getInternalConfiguredNetwork(result.getNetworkId());
|
|
sendConfiguredNetworkChangedBroadcast(
|
|
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);
|
|
}
|
|
|
|
for (OnNetworkUpdateListener listener : mListeners) {
|
|
if (result.isNewNetwork()) {
|
|
listener.onNetworkAdded(
|
|
createExternalWifiConfiguration(newConfig, true, Process.WIFI_UID));
|
|
} else {
|
|
listener.onNetworkUpdated(
|
|
createExternalWifiConfiguration(newConfig, true, Process.WIFI_UID),
|
|
createExternalWifiConfiguration(existingConfig, true, Process.WIFI_UID));
|
|
}
|
|
}
|
|
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) {
|
|
return addOrUpdateNetwork(config, uid, null, false);
|
|
}
|
|
|
|
/**
|
|
* Increments the number of reboots since last use for each configuration.
|
|
*
|
|
* @see {@link WifiConfiguration#numRebootsSinceLastUse}
|
|
*/
|
|
public void incrementNumRebootsSinceLastUse() {
|
|
getInternalConfiguredNetworks().forEach(config -> config.numRebootsSinceLastUse++);
|
|
saveToStore(false);
|
|
}
|
|
|
|
private boolean isDeviceOwnerProfileOwnerOrSystem(int uid, String packageName) {
|
|
return mWifiPermissionsUtil.isDeviceOwner(uid, packageName)
|
|
|| mWifiPermissionsUtil.isProfileOwner(uid, packageName)
|
|
|| mWifiPermissionsUtil.isSystem(packageName, uid);
|
|
}
|
|
|
|
/**
|
|
* Removes excess networks in case the number of saved networks exceeds the max limit
|
|
* specified in config_wifiMaxNumWifiConfigurations.
|
|
*
|
|
* If called by a non DO/PO/system app, and a limit on app-added networks is specified in
|
|
* config_wifiMaxNumWifiConfigurationsForAppAddedNetworks, only removes excess
|
|
* app-added networks.
|
|
*
|
|
* Configs are removed in ascending order of
|
|
* 1. Non-carrier networks before carrier networks
|
|
* 2. Non-connected networks before connected networks.
|
|
* 3. Deletion priority {@see WifiConfiguration#getDeletionPriority()}
|
|
* 4. Last use/creation/update time (lastUpdated/lastConnected or numRebootsSinceLastUse)
|
|
* 5. Open and OWE networks before networks with other security types.
|
|
* 6. Number of associations
|
|
*
|
|
* @param uid UID of the app requesting the network addition/modification.
|
|
* @param packageName Package name of the app requesting the network addition/modification.
|
|
* @return {@code true} if networks were removed, {@code false} otherwise.
|
|
*/
|
|
private boolean removeExcessNetworks(int uid, String packageName) {
|
|
final int maxNumTotalConfigs = mContext.getResources().getInteger(
|
|
R.integer.config_wifiMaxNumWifiConfigurations);
|
|
final int maxNumAppAddedConfigs = mContext.getResources().getInteger(
|
|
R.integer.config_wifiMaxNumWifiConfigurationsAddedByAllApps);
|
|
|
|
boolean callerIsApp = !isDeviceOwnerProfileOwnerOrSystem(uid, packageName);
|
|
if (maxNumTotalConfigs < 0 && (!callerIsApp || maxNumAppAddedConfigs < 0)) {
|
|
// Max number of saved networks not specified or does not need to be checked.
|
|
return false;
|
|
}
|
|
|
|
int numExcessNetworks = -1;
|
|
List<WifiConfiguration> networkList = getSavedNetworks(Process.WIFI_UID);
|
|
if (maxNumTotalConfigs >= 0) {
|
|
numExcessNetworks = networkList.size() - maxNumTotalConfigs;
|
|
}
|
|
|
|
if (callerIsApp && maxNumAppAddedConfigs >= 0) {
|
|
List<WifiConfiguration> appAddedNetworks = networkList
|
|
.stream()
|
|
.filter(n -> !isDeviceOwnerProfileOwnerOrSystem(n.creatorUid, n.creatorName))
|
|
.collect(Collectors.toList());
|
|
int numExcessAppAddedNetworks = appAddedNetworks.size() - maxNumAppAddedConfigs;
|
|
if (numExcessAppAddedNetworks > 0) {
|
|
// Only enforce the limit on app-added networks if it has been exceeded.
|
|
// Otherwise, default to checking the limit on the total number of networks.
|
|
numExcessNetworks = numExcessAppAddedNetworks;
|
|
networkList = appAddedNetworks;
|
|
}
|
|
}
|
|
|
|
if (numExcessNetworks <= 0) {
|
|
return false;
|
|
}
|
|
|
|
List<WifiConfiguration> configsToDelete = networkList
|
|
.stream()
|
|
.sorted(Comparator.comparing((WifiConfiguration config) -> config.carrierId
|
|
!= TelephonyManager.UNKNOWN_CARRIER_ID)
|
|
.thenComparing((WifiConfiguration config) -> config.status
|
|
== WifiConfiguration.Status.CURRENT)
|
|
.thenComparing((WifiConfiguration config) -> config.getDeletionPriority())
|
|
.thenComparing((WifiConfiguration config) -> -config.numRebootsSinceLastUse)
|
|
.thenComparing((WifiConfiguration config) ->
|
|
Math.max(config.lastConnected, config.lastUpdated))
|
|
.thenComparing((WifiConfiguration config) -> {
|
|
try {
|
|
int authType = config.getAuthType();
|
|
return !(authType == WifiConfiguration.KeyMgmt.NONE
|
|
|| authType == WifiConfiguration.KeyMgmt.OWE);
|
|
} catch (IllegalStateException e) {
|
|
// An invalid keymgmt configuration should be pruned first.
|
|
return false;
|
|
}
|
|
})
|
|
.thenComparing((WifiConfiguration config) -> config.numAssociation))
|
|
.limit(numExcessNetworks)
|
|
.collect(Collectors.toList());
|
|
for (WifiConfiguration config : configsToDelete) {
|
|
mConfiguredNetworks.remove(config.networkId);
|
|
localLog("removeExcessNetworks: removed config."
|
|
+ " netId=" + config.networkId
|
|
+ " configKey=" + config.getProfileKey());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Removes the specified network configuration from our database.
|
|
*
|
|
* @param config provided WifiConfiguration object.
|
|
* @param uid UID of the app requesting the network deletion.
|
|
* @return true if successful, false otherwise.
|
|
*/
|
|
private boolean removeNetworkInternal(WifiConfiguration config, int uid) {
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.v(TAG, "Removing network " + config.getPrintableSsid());
|
|
}
|
|
// Remove any associated enterprise keys for saved enterprise networks. Passpoint network
|
|
// will remove the enterprise keys when provider is uninstalled. Suggestion enterprise
|
|
// networks will remove the enterprise keys when suggestion is removed.
|
|
if (!config.fromWifiNetworkSuggestion && !config.isPasspoint() && config.isEnterprise()) {
|
|
mWifiKeyStore.removeKeys(config.enterpriseConfig, false);
|
|
}
|
|
|
|
// Do not remove the user choice when passpoint or suggestion networks are removed from
|
|
// WifiConfigManager. Will remove that when profile is deleted from PassointManager or
|
|
// WifiNetworkSuggestionsManager.
|
|
if (!config.isPasspoint() && !config.fromWifiNetworkSuggestion) {
|
|
removeConnectChoiceFromAllNetworks(config.getProfileKey());
|
|
}
|
|
mConfiguredNetworks.remove(config.networkId);
|
|
mScanDetailCaches.remove(config.networkId);
|
|
// Stage the backup of the SettingsProvider package which backs this up.
|
|
mBackupManagerProxy.notifyDataChanged();
|
|
mWifiBlocklistMonitor.handleNetworkRemoved(config.SSID);
|
|
|
|
localLog("removeNetworkInternal: removed config."
|
|
+ " netId=" + config.networkId
|
|
+ " configKey=" + config.getProfileKey()
|
|
+ " uid=" + Integer.toString(uid)
|
|
+ " name=" + mContext.getPackageManager().getNameForUid(uid));
|
|
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, String packageName) {
|
|
if (!mWifiPermissionsUtil.doesUidBelongToCurrentUserOrDeviceOwner(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, packageName)) {
|
|
Log.e(TAG, "UID " + uid + " does not have permission to delete configuration "
|
|
+ config.getProfileKey());
|
|
return false;
|
|
}
|
|
if (!removeNetworkInternal(config, uid)) {
|
|
Log.e(TAG, "Failed to remove network " + config.getPrintableSsid());
|
|
return false;
|
|
}
|
|
if (networkId == mLastSelectedNetworkId) {
|
|
clearLastSelectedNetwork();
|
|
}
|
|
if (!config.ephemeral && !config.isPasspoint()) {
|
|
mLruConnectionTracker.removeNetwork(config);
|
|
}
|
|
sendConfiguredNetworkChangedBroadcast(WifiManager.CHANGE_REASON_REMOVED);
|
|
// Unless the removed network is ephemeral or Passpoint, persist the network removal.
|
|
if (!config.ephemeral && !config.isPasspoint()) {
|
|
saveToStore(true);
|
|
}
|
|
for (OnNetworkUpdateListener listener : mListeners) {
|
|
listener.onNetworkRemoved(
|
|
createExternalWifiConfiguration(config, true, Process.WIFI_UID));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private String getCreatorPackageName(WifiConfiguration config) {
|
|
String creatorName = config.creatorName;
|
|
// getNameForUid (Stored in WifiConfiguration.creatorName) returns a concatenation of name
|
|
// and uid for shared UIDs ("name:uid").
|
|
if (!creatorName.contains(":")) {
|
|
return creatorName; // regular app not using shared UID.
|
|
}
|
|
// Separate the package name from the string for app using shared UID.
|
|
return creatorName.substring(0, creatorName.indexOf(":"));
|
|
}
|
|
|
|
/**
|
|
* 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(getCreatorPackageName(config))) {
|
|
continue;
|
|
}
|
|
localLog("Removing network " + config.SSID
|
|
+ ", application \"" + app.packageName + "\" uninstalled"
|
|
+ " from user " + UserHandle.getUserHandleForUid(app.uid));
|
|
if (removeNetwork(config.networkId, config.creatorUid, config.creatorName)) {
|
|
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.getUserHandleForUid(config.creatorUid).getIdentifier()) {
|
|
continue;
|
|
}
|
|
localLog("Removing network " + config.SSID + ", user " + userId + " removed");
|
|
if (removeNetwork(config.networkId, config.creatorUid, config.creatorName)) {
|
|
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.getProfileKey());
|
|
removeNetwork(config.networkId, config.creatorUid, config.creatorName);
|
|
didRemove = true;
|
|
} else if (config.ephemeral) {
|
|
Log.d(TAG, "Removing ephemeral network config " + config.getProfileKey());
|
|
removeNetwork(config.networkId, config.creatorUid, config.creatorName);
|
|
didRemove = true;
|
|
}
|
|
}
|
|
return didRemove;
|
|
}
|
|
|
|
/**
|
|
* Removes the suggestion network configuration matched with WifiConfiguration provided.
|
|
* @param suggestion WifiConfiguration for suggestion which needs to remove
|
|
* @return true if a network was removed, false otherwise.
|
|
*/
|
|
public boolean removeSuggestionConfiguredNetwork(@NonNull WifiConfiguration suggestion) {
|
|
WifiConfiguration config = getInternalConfiguredNetwork(
|
|
suggestion.getProfileKey());
|
|
if (config != null && config.ephemeral && config.fromWifiNetworkSuggestion) {
|
|
Log.d(TAG, "Removing suggestion network config " + config.getProfileKey());
|
|
return removeNetwork(config.networkId, suggestion.creatorUid, suggestion.creatorName);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Removes the passpoint network configuration matched with {@code configKey} provided.
|
|
*
|
|
* @param configKey Config Key for the corresponding passpoint.
|
|
* @return true if a network was removed, false otherwise.
|
|
*/
|
|
public boolean removePasspointConfiguredNetwork(@NonNull String configKey) {
|
|
WifiConfiguration config = getInternalConfiguredNetwork(configKey);
|
|
if (config != null && config.isPasspoint()) {
|
|
Log.d(TAG, "Removing passpoint network config " + config.getProfileKey());
|
|
return removeNetwork(config.networkId, config.creatorUid, config.creatorName);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Removes all save networks configurations not created by the caller.
|
|
*
|
|
* @param callerUid the uid of the caller
|
|
* @return {@code true} if at least one network is removed.
|
|
*/
|
|
public boolean removeNonCallerConfiguredNetwork(int callerUid) {
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.v(TAG, "removeNonCallerConfiguredNetwork caller = " + callerUid);
|
|
}
|
|
boolean didRemove = false;
|
|
WifiConfiguration[] copiedConfigs =
|
|
mConfiguredNetworks.valuesForAllUsers().toArray(new WifiConfiguration[0]);
|
|
for (WifiConfiguration config : copiedConfigs) {
|
|
if (config.creatorUid != callerUid) {
|
|
Log.d(TAG, "Removing non-caller network config " + config.getProfileKey());
|
|
removeNetwork(config.networkId, config.creatorUid, config.creatorName);
|
|
didRemove = true;
|
|
}
|
|
}
|
|
return didRemove;
|
|
}
|
|
|
|
/**
|
|
* Check whether a network belong to a known list of networks that may not support randomized
|
|
* MAC.
|
|
* @param networkId
|
|
* @return true if the network is in the hotlist and MAC randomization is enabled.
|
|
*/
|
|
public boolean isInFlakyRandomizationSsidHotlist(int networkId) {
|
|
WifiConfiguration config = getConfiguredNetwork(networkId);
|
|
return config != null
|
|
&& config.macRandomizationSetting != WifiConfiguration.RANDOMIZATION_NONE
|
|
&& mDeviceConfigFacade.getRandomizationFlakySsidHotlist().contains(config.SSID);
|
|
}
|
|
|
|
/**
|
|
* 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(WifiManager.CHANGE_REASON_CONFIG_CHANGE);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
private boolean updateNetworkSelectionStatus(@NonNull WifiConfiguration config, int reason) {
|
|
int prevNetworkSelectionStatus = config.getNetworkSelectionStatus()
|
|
.getNetworkSelectionStatus();
|
|
if (!mWifiBlocklistMonitor.updateNetworkSelectionStatus(config, reason)) {
|
|
return false;
|
|
}
|
|
int newNetworkSelectionStatus = config.getNetworkSelectionStatus()
|
|
.getNetworkSelectionStatus();
|
|
if (prevNetworkSelectionStatus != newNetworkSelectionStatus) {
|
|
sendNetworkSelectionStatusChangedUpdate(config, newNetworkSelectionStatus, reason);
|
|
sendConfiguredNetworkChangedBroadcast(WifiManager.CHANGE_REASON_CONFIG_CHANGE);
|
|
}
|
|
saveToStore(false);
|
|
return true;
|
|
}
|
|
|
|
private void sendNetworkSelectionStatusChangedUpdate(@NonNull WifiConfiguration config,
|
|
int newNetworkSelectionStatus, int disableReason) {
|
|
switch (newNetworkSelectionStatus) {
|
|
case NetworkSelectionStatus.NETWORK_SELECTION_ENABLED:
|
|
for (OnNetworkUpdateListener listener : mListeners) {
|
|
listener.onNetworkEnabled(
|
|
createExternalWifiConfiguration(config, true, Process.WIFI_UID));
|
|
}
|
|
break;
|
|
case NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED:
|
|
for (OnNetworkUpdateListener listener : mListeners) {
|
|
listener.onNetworkTemporarilyDisabled(
|
|
createExternalWifiConfiguration(config, true, Process.WIFI_UID),
|
|
disableReason);
|
|
}
|
|
break;
|
|
case NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED:
|
|
for (OnNetworkUpdateListener listener : mListeners) {
|
|
WifiConfiguration configForListener = new WifiConfiguration(config);
|
|
listener.onNetworkPermanentlyDisabled(
|
|
createExternalWifiConfiguration(config, true, Process.WIFI_UID),
|
|
disableReason);
|
|
}
|
|
break;
|
|
default:
|
|
// all cases covered
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Re-enable all temporary disabled configured networks.
|
|
*/
|
|
public void enableTemporaryDisabledNetworks() {
|
|
mWifiBlocklistMonitor.clearBssidBlocklist();
|
|
for (WifiConfiguration config : getInternalConfiguredNetworks()) {
|
|
if (config.getNetworkSelectionStatus().isNetworkTemporaryDisabled()) {
|
|
updateNetworkSelectionStatus(config,
|
|
NetworkSelectionStatus.DISABLED_NONE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
if (mWifiBlocklistMonitor.shouldEnableNetwork(config)) {
|
|
return updateNetworkSelectionStatus(config, NetworkSelectionStatus.DISABLED_NONE);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* @param packageName Package name of calling apps
|
|
* @return true if it succeeds, false otherwise
|
|
*/
|
|
public boolean enableNetwork(int networkId, boolean disableOthers, int uid,
|
|
@NonNull String packageName) {
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.v(TAG, "Enabling network " + networkId + " (disableOthers " + disableOthers + ")");
|
|
}
|
|
if (!mWifiPermissionsUtil.doesUidBelongToCurrentUserOrDeviceOwner(uid)) {
|
|
Log.e(TAG, "UID " + uid + " not visible to the current user");
|
|
return false;
|
|
}
|
|
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
|
|
if (config == null) {
|
|
return false;
|
|
}
|
|
// Set the "last selected" flag even if the app does not have permissions to modify this
|
|
// network config. Apps are allowed to connect to networks even if they don't have
|
|
// permission to modify it.
|
|
if (disableOthers) {
|
|
setLastSelectedNetwork(networkId);
|
|
}
|
|
if (!canModifyNetwork(config, uid, packageName)) {
|
|
Log.e(TAG, "UID " + uid + " package " + packageName
|
|
+ " does not have permission to update configuration "
|
|
+ config.getProfileKey());
|
|
return false;
|
|
}
|
|
if (!updateNetworkSelectionStatus(
|
|
networkId, WifiConfiguration.NetworkSelectionStatus.DISABLED_NONE)) {
|
|
return false;
|
|
}
|
|
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, @NonNull String packageName) {
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.v(TAG, "Disabling network " + networkId);
|
|
}
|
|
if (!mWifiPermissionsUtil.doesUidBelongToCurrentUserOrDeviceOwner(uid)) {
|
|
Log.e(TAG, "UID " + uid + " package " + packageName
|
|
+ " not visible to the current user");
|
|
return false;
|
|
}
|
|
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
|
|
if (config == null) {
|
|
return false;
|
|
}
|
|
// Reset the "last selected" flag even if the app does not have permissions to modify this
|
|
// network config.
|
|
if (networkId == mLastSelectedNetworkId) {
|
|
clearLastSelectedNetwork();
|
|
}
|
|
if (!canModifyNetwork(config, uid, packageName)) {
|
|
Log.e(TAG, "UID " + uid + " package " + packageName
|
|
+ " does not have permission to update configuration "
|
|
+ config.getProfileKey());
|
|
return false;
|
|
}
|
|
if (!updateNetworkSelectionStatus(
|
|
networkId, NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER)) {
|
|
return false;
|
|
}
|
|
saveToStore(true);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Changes the user's choice to allow auto-join using the
|
|
* {@link WifiManager#allowAutojoin(int, boolean)} API.
|
|
*
|
|
* @param networkId network ID of the network that needs the update.
|
|
* @param choice the choice to allow auto-join or not
|
|
* @return true if it succeeds, false otherwise
|
|
*/
|
|
public boolean allowAutojoin(int networkId, boolean choice) {
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.v(TAG, "Setting allowAutojoin to " + choice + " for netId " + networkId);
|
|
}
|
|
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
|
|
if (config == null) {
|
|
Log.e(TAG, "allowAutojoin: Supplied networkId " + networkId
|
|
+ " has no matching config");
|
|
return false;
|
|
}
|
|
|
|
config.allowAutojoin = choice;
|
|
if (!choice) {
|
|
removeConnectChoiceFromAllNetworks(config.getProfileKey());
|
|
clearConnectChoiceInternal(config);
|
|
}
|
|
sendConfiguredNetworkChangedBroadcast(WifiManager.CHANGE_REASON_CONFIG_CHANGE);
|
|
if (!config.ephemeral) {
|
|
saveToStore(true);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 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 the network was found, false otherwise.
|
|
*/
|
|
private boolean updateLastConnectUid(int networkId, int uid) {
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.v(TAG, "Update network last connect UID for " + networkId);
|
|
}
|
|
if (!mWifiPermissionsUtil.doesUidBelongToCurrentUserOrDeviceOwner(uid)) {
|
|
Log.e(TAG, "UID " + uid + " not visible to the current user");
|
|
return false;
|
|
}
|
|
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
|
|
if (config == null) {
|
|
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.
|
|
* @param shouldSetUserConnectChoice setup user connect choice on this network.
|
|
* @param rssi signal strength of the connected network.
|
|
* @return true if the network was found, false otherwise.
|
|
*/
|
|
public boolean updateNetworkAfterConnect(int networkId, boolean shouldSetUserConnectChoice,
|
|
int rssi) {
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.v(TAG, "Update network after connect for " + networkId);
|
|
}
|
|
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
|
|
if (config == null) {
|
|
return false;
|
|
}
|
|
|
|
// Only record connection order for non-passpoint from user saved or suggestion.
|
|
if (!config.isPasspoint() && (config.fromWifiNetworkSuggestion || !config.ephemeral)) {
|
|
mLruConnectionTracker.addNetwork(config);
|
|
}
|
|
if (shouldSetUserConnectChoice) {
|
|
setUserConnectChoice(config.networkId, rssi);
|
|
}
|
|
config.lastConnected = mClock.getWallClockMillis();
|
|
config.numRebootsSinceLastUse = 0;
|
|
config.numAssociation++;
|
|
config.getNetworkSelectionStatus().clearDisableReasonCounter();
|
|
config.getNetworkSelectionStatus().setHasEverConnected(true);
|
|
setNetworkStatus(config, WifiConfiguration.Status.CURRENT);
|
|
saveToStore(false);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Set captive portal to be detected for this network.
|
|
* @param networkId
|
|
*/
|
|
public void noteCaptivePortalDetected(int networkId) {
|
|
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
|
|
if (config != null) {
|
|
config.getNetworkSelectionStatus().setHasNeverDetectedCaptivePortal(false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
config.randomizedMacExpirationTimeMs = Math.max(config.randomizedMacExpirationTimeMs,
|
|
config.lastDisconnected + NON_PERSISTENT_MAC_WAIT_AFTER_DISCONNECT_MS);
|
|
// 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);
|
|
config.getNetworkSelectionStatus().setCandidateSecurityParams(null);
|
|
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.
|
|
* @param params Security params for this candidate.
|
|
* @return true if the network was found, false otherwise.
|
|
*/
|
|
public boolean setNetworkCandidateScanResult(int networkId, ScanResult scanResult, int score,
|
|
SecurityParams params) {
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.v(TAG, "Set network candidate scan result " + scanResult + " for " + networkId
|
|
+ " with security params " + params);
|
|
}
|
|
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
|
|
if (config == null) {
|
|
Log.e(TAG, "Cannot find network for " + networkId);
|
|
return false;
|
|
}
|
|
config.getNetworkSelectionStatus().setCandidate(scanResult);
|
|
config.getNetworkSelectionStatus().setCandidateScore(score);
|
|
config.getNetworkSelectionStatus().setSeenInLastQualifiedNetworkSelection(true);
|
|
config.getNetworkSelectionStatus().setCandidateSecurityParams(params);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Set the {@link NetworkSelectionStatus#mLastUsedSecurityParams}.
|
|
*
|
|
* @param networkId network ID corresponding to the network.
|
|
* @param params Security params for this candidate.
|
|
* @return true if the network was found, false otherwise.
|
|
*/
|
|
public boolean setNetworkLastUsedSecurityParams(int networkId, SecurityParams params) {
|
|
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
|
|
if (config == null) {
|
|
Log.e(TAG, "Cannot find network for " + networkId);
|
|
return false;
|
|
}
|
|
config.getNetworkSelectionStatus().setLastUsedSecurityParams(params);
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.v(TAG, "Update last used security param for " + config.getProfileKey()
|
|
+ " with security type " + params.getSecurityType());
|
|
}
|
|
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.
|
|
*/
|
|
public void removeConnectChoiceFromAllNetworks(String connectChoiceConfigKey) {
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.v(TAG, "Removing connect choice from all networks " + connectChoiceConfigKey);
|
|
}
|
|
if (connectChoiceConfigKey == null) {
|
|
return;
|
|
}
|
|
for (WifiConfiguration config : getInternalConfiguredNetworks()) {
|
|
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);
|
|
clearConnectChoiceInternal(config);
|
|
}
|
|
}
|
|
for (OnNetworkUpdateListener listener : mListeners) {
|
|
listener.onConnectChoiceRemoved(connectChoiceConfigKey);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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++;
|
|
config.validatedInternetAccess = false;
|
|
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.
|
|
*/
|
|
public 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.getProfileKey();
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
|
|
WifiScoreCard.PerNetwork network = mWifiScoreCard.lookupNetwork(config.SSID);
|
|
network.addFrequency(scanResult.frequency);
|
|
ScanDetailCache scanDetailCache = getOrCreateScanDetailCacheForNetwork(config);
|
|
if (scanDetailCache == null) {
|
|
Log.e(TAG, "Could not allocate scan cache for " + config.getPrintableSsid());
|
|
return;
|
|
}
|
|
|
|
// Adding a new BSSID
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* 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 getSavedNetworkForScanDetail(ScanDetail scanDetail) {
|
|
ScanResult scanResult = scanDetail.getScanResult();
|
|
if (scanResult == null) {
|
|
Log.e(TAG, "No scan result found in scan detail");
|
|
return null;
|
|
}
|
|
return getSavedNetworkForScanResult(scanResult);
|
|
}
|
|
|
|
/**
|
|
* Retrieves a configured network corresponding to the provided scan result if one exists.
|
|
*
|
|
* @param scanResult ScanResult instance to use for looking up the network.
|
|
* @return WifiConfiguration object representing the network corresponding to the scanResult,
|
|
* null if none exists.
|
|
*/
|
|
public WifiConfiguration getSavedNetworkForScanResult(@NonNull ScanResult scanResult) {
|
|
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, "getSavedNetworkFromScanResult Found " + config.getProfileKey()
|
|
+ " for " + scanResult.SSID + "[" + scanResult.capabilities + "]");
|
|
}
|
|
}
|
|
return config;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
public void updateScanDetailCacheFromScanDetailForSavedNetwork(ScanDetail scanDetail) {
|
|
WifiConfiguration network = getSavedNetworkForScanDetail(scanDetail);
|
|
if (network == null) {
|
|
return;
|
|
}
|
|
saveToScanDetailCacheForNetwork(network, scanDetail);
|
|
}
|
|
/**
|
|
* 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 getSavedNetworkForScanDetailAndCache(ScanDetail scanDetail) {
|
|
WifiConfiguration network = getSavedNetworkForScanDetail(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, Process.WIFI_UID);
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
long maxAge = SCAN_RESULT_MAXIMUM_AGE_MS;
|
|
long age = result.seen - previousSeen;
|
|
if (previousSeen > 0 && age > 0 && age < maxAge / 2) {
|
|
// Average the RSSI with previously seen instances of this scan result
|
|
double alpha = 0.5 - (double) age / (double) maxAge;
|
|
result.level = (int) ((double) result.level * (1 - alpha)
|
|
+ (double) previousRssi * alpha);
|
|
}
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.v(TAG, "Updating scan detail cache freq=" + result.frequency
|
|
+ " BSSID=" + result.BSSID
|
|
+ " RSSI=" + result.level
|
|
+ " for " + config.getProfileKey());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save the ScanDetail to the ScanDetailCache of the given network. This is used
|
|
* by {@link PasspointNetworkNominator} 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) {
|
|
// Check if networks should not be linked due to credential mismatch
|
|
if (mContext.getResources().getBoolean(
|
|
R.bool.config_wifi_only_link_same_credential_configurations)) {
|
|
if (!TextUtils.equals(network1.preSharedKey, network2.preSharedKey)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Skip VRRP MAC addresses since they are likely to correspond to different networks even if
|
|
// they match.
|
|
if ((network1.defaultGwMacAddress != null && network1.defaultGwMacAddress
|
|
.regionMatches(true, 0, VRRP_MAC_ADDRESS_PREFIX, 0,
|
|
VRRP_MAC_ADDRESS_PREFIX.length()))
|
|
|| (network2.defaultGwMacAddress != null && network2.defaultGwMacAddress
|
|
.regionMatches(true, 0, VRRP_MAC_ADDRESS_PREFIX, 0,
|
|
VRRP_MAC_ADDRESS_PREFIX.length()))) {
|
|
return false;
|
|
}
|
|
|
|
// Check if networks should be linked due to default gateway match
|
|
if (network1.defaultGwMacAddress != null && network2.defaultGwMacAddress != null) {
|
|
// If both default GW are known, link only if they are equal
|
|
if (network1.defaultGwMacAddress.equalsIgnoreCase(network2.defaultGwMacAddress)) {
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.v(TAG, "shouldNetworksBeLinked link due to same gw " + network2.SSID
|
|
+ " and " + network1.SSID + " GW " + network1.defaultGwMacAddress);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// We do not know BOTH default gateways yet, but if the first 16 ASCII characters of BSSID
|
|
// match then we can assume this is a DBDC with the same gateway. Once both gateways become
|
|
// known, we will unlink the networks if it turns out the gateways are actually different.
|
|
if (!mContext.getResources().getBoolean(
|
|
R.bool.config_wifiAllowLinkingUnknownDefaultGatewayConfigurations)) {
|
|
return false;
|
|
}
|
|
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 (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.getProfileKey()
|
|
+ " and " + network1.getProfileKey());
|
|
}
|
|
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.getProfileKey(), 1);
|
|
network1.linkedConfigurations.put(network2.getProfileKey(), 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.getProfileKey()) != null)) {
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.v(TAG, "unlinkNetworks un-link " + network1.getProfileKey()
|
|
+ " from " + network2.getProfileKey());
|
|
}
|
|
network2.linkedConfigurations.remove(network1.getProfileKey());
|
|
}
|
|
if (network1.linkedConfigurations != null
|
|
&& (network1.linkedConfigurations.get(network2.getProfileKey()) != null)) {
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.v(TAG, "unlinkNetworks un-link " + network2.getProfileKey()
|
|
+ " from " + network1.getProfileKey());
|
|
}
|
|
network1.linkedConfigurations.remove(network2.getProfileKey());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.isSecurityType(WifiConfiguration.SECURITY_TYPE_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.getProfileKey().equals(config.getProfileKey())) {
|
|
continue;
|
|
}
|
|
if (linkConfig.ephemeral) {
|
|
continue;
|
|
}
|
|
if (!linkConfig.getNetworkSelectionStatus().isNetworkEnabled()) {
|
|
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.isSecurityType(WifiConfiguration.SECURITY_TYPE_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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @param autoJoinOnly retrieve hidden network autojoin enabled only.
|
|
* @return list of hidden networks in the order of priority.
|
|
*/
|
|
public List<WifiScanner.ScanSettings.HiddenNetwork> retrieveHiddenNetworkList(
|
|
boolean autoJoinOnly) {
|
|
List<WifiScanner.ScanSettings.HiddenNetwork> hiddenList = new ArrayList<>();
|
|
List<WifiConfiguration> networks = getConfiguredNetworks();
|
|
// Remove any non hidden networks.
|
|
networks.removeIf(config -> !config.hiddenSSID);
|
|
networks.sort(mScanListComparator);
|
|
// The most frequently connected network has the highest priority now.
|
|
for (WifiConfiguration config : networks) {
|
|
if (!autoJoinOnly || config.allowAutojoin) {
|
|
hiddenList.add(new WifiScanner.ScanSettings.HiddenNetwork(config.SSID));
|
|
}
|
|
}
|
|
return hiddenList;
|
|
}
|
|
|
|
/**
|
|
* Check if the provided network was temporarily disabled by the user and still blocked.
|
|
*
|
|
* @param network Input can be SSID or FQDN. And caller must ensure that the SSID passed thru
|
|
* this API matched the WifiConfiguration.SSID rules, and thus be surrounded by
|
|
* quotes.
|
|
* @return true if network is blocking, otherwise false.
|
|
*/
|
|
public boolean isNetworkTemporarilyDisabledByUser(String network) {
|
|
if (mUserTemporarilyDisabledList.isLocked(network)) {
|
|
return true;
|
|
}
|
|
mUserTemporarilyDisabledList.remove(network);
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check if the provided network should be disabled because it's a non-carrier-merged network.
|
|
* @param config WifiConfiguration
|
|
* @return true if the network is a non-carrier-merged network and it should be disabled,
|
|
* otherwise false.
|
|
*/
|
|
public boolean isNonCarrierMergedNetworkTemporarilyDisabled(
|
|
@NonNull WifiConfiguration config) {
|
|
return mNonCarrierMergedNetworksStatusTracker.isNetworkDisabled(config);
|
|
}
|
|
|
|
/**
|
|
* User temporarily disable a network and will be block to auto-join when network is still
|
|
* nearby.
|
|
*
|
|
* The network will be re-enabled when:
|
|
* a) User select to connect the network.
|
|
* b) The network is not in range for {@link #USER_DISCONNECT_NETWORK_BLOCK_EXPIRY_MS}
|
|
* c) The maximum disable duration configured by
|
|
* config_wifiAllNonCarrierMergedWifiMaxDisableDurationMinutes has passed.
|
|
* d) Toggle wifi off, reset network settings or device reboot.
|
|
*
|
|
* @param network Input can be SSID or FQDN. And caller must ensure that the SSID passed thru
|
|
* this API matched the WifiConfiguration.SSID rules, and thus be surrounded by
|
|
* quotes.
|
|
* uid UID of the calling process.
|
|
*/
|
|
public void userTemporarilyDisabledNetwork(String network, int uid) {
|
|
int maxDisableDurationMinutes = mContext.getResources().getInteger(R.integer
|
|
.config_wifiAllNonCarrierMergedWifiMaxDisableDurationMinutes);
|
|
mUserTemporarilyDisabledList.add(network, USER_DISCONNECT_NETWORK_BLOCK_EXPIRY_MS,
|
|
maxDisableDurationMinutes * 60 * 1000);
|
|
Log.d(TAG, "Temporarily disable network: " + network + " uid=" + uid + " num="
|
|
+ mUserTemporarilyDisabledList.size() + ", maxDisableDurationMinutes:"
|
|
+ maxDisableDurationMinutes);
|
|
removeUserChoiceFromDisabledNetwork(network, uid);
|
|
saveToStore(false);
|
|
}
|
|
|
|
/**
|
|
* Temporarily disable visible and configured networks except for carrier merged networks for
|
|
* the given subscriptionId.
|
|
* @param subscriptionId
|
|
*/
|
|
public void startRestrictingAutoJoinToSubscriptionId(int subscriptionId) {
|
|
int minDisableDurationMinutes = mContext.getResources().getInteger(R.integer
|
|
.config_wifiAllNonCarrierMergedWifiMinDisableDurationMinutes);
|
|
int maxDisableDurationMinutes = mContext.getResources().getInteger(R.integer
|
|
.config_wifiAllNonCarrierMergedWifiMaxDisableDurationMinutes);
|
|
localLog("startRestrictingAutoJoinToSubscriptionId: " + subscriptionId
|
|
+ " minDisableDurationMinutes:" + minDisableDurationMinutes
|
|
+ " maxDisableDurationMinutes:" + maxDisableDurationMinutes);
|
|
long maxDisableDurationMs = maxDisableDurationMinutes * 60 * 1000;
|
|
// do a clear to make sure we start at a clean state.
|
|
mNonCarrierMergedNetworksStatusTracker.clear();
|
|
mNonCarrierMergedNetworksStatusTracker.disableAllNonCarrierMergedNetworks(subscriptionId,
|
|
minDisableDurationMinutes * 60 * 1000,
|
|
maxDisableDurationMs);
|
|
for (WifiConfiguration config : getInternalConfiguredNetworks()) {
|
|
ScanDetailCache scanDetailCache = getScanDetailCacheForNetwork(config.networkId);
|
|
if (scanDetailCache == null) {
|
|
continue;
|
|
}
|
|
ScanResult scanResult = scanDetailCache.getMostRecentScanResult();
|
|
if (scanResult == null) {
|
|
continue;
|
|
}
|
|
if (mClock.getWallClockMillis() - scanResult.seen
|
|
< NON_CARRIER_MERGED_NETWORKS_SCAN_CACHE_QUERY_DURATION_MS) {
|
|
// do not disable if this is a carrier-merged-network with the given subscriptionId
|
|
if (config.carrierMerged && config.subscriptionId == subscriptionId) {
|
|
continue;
|
|
}
|
|
mNonCarrierMergedNetworksStatusTracker.temporarilyDisableNetwork(config,
|
|
USER_DISCONNECT_NETWORK_BLOCK_EXPIRY_MS, maxDisableDurationMs);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resets the effects of startTemporarilyDisablngAllNonCarrierMergedWifi.
|
|
*/
|
|
public void stopRestrictingAutoJoinToSubscriptionId() {
|
|
mNonCarrierMergedNetworksStatusTracker.clear();
|
|
}
|
|
|
|
/**
|
|
* Update the user temporarily disabled network list with networks in range.
|
|
* @param networks networks in range in String format, FQDN or SSID. And caller must ensure
|
|
* that the SSID passed thru this API matched the WifiConfiguration.SSID rules,
|
|
* and thus be surrounded by quotes.
|
|
*/
|
|
public void updateUserDisabledList(List<String> networks) {
|
|
mUserTemporarilyDisabledList.update(new HashSet<>(networks));
|
|
mNonCarrierMergedNetworksStatusTracker.update(new HashSet<>(networks));
|
|
}
|
|
|
|
private void removeUserChoiceFromDisabledNetwork(
|
|
@NonNull String network, int uid) {
|
|
for (WifiConfiguration config : getInternalConfiguredNetworks()) {
|
|
if (TextUtils.equals(config.SSID, network) || TextUtils.equals(config.FQDN, network)) {
|
|
if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) {
|
|
mWifiMetrics.logUserActionEvent(
|
|
UserActionEvent.EVENT_DISCONNECT_WIFI, config.networkId);
|
|
}
|
|
removeConnectChoiceFromAllNetworks(config.getProfileKey());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* User enabled network manually, maybe trigger by user select to connect network.
|
|
* @param networkId enabled network id.
|
|
* @return true if the operation succeeded, false otherwise.
|
|
*/
|
|
public boolean userEnabledNetwork(int networkId) {
|
|
WifiConfiguration configuration = getInternalConfiguredNetwork(networkId);
|
|
if (configuration == null) {
|
|
return false;
|
|
}
|
|
final String network;
|
|
if (configuration.isPasspoint()) {
|
|
network = configuration.FQDN;
|
|
} else {
|
|
network = configuration.SSID;
|
|
}
|
|
mUserTemporarilyDisabledList.remove(network);
|
|
mWifiBlocklistMonitor.clearBssidBlocklistForSsid(configuration.SSID);
|
|
Log.d(TAG, "Enable disabled network: " + network + " num="
|
|
+ mUserTemporarilyDisabledList.size());
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Clear all user temporarily disabled networks.
|
|
*/
|
|
public void clearUserTemporarilyDisabledList() {
|
|
mUserTemporarilyDisabledList.clear();
|
|
}
|
|
|
|
/**
|
|
* Resets all sim networks state.
|
|
*/
|
|
public void resetSimNetworks() {
|
|
if (mVerboseLoggingEnabled) localLog("resetSimNetworks");
|
|
for (WifiConfiguration config : getInternalConfiguredNetworks()) {
|
|
if (config.enterpriseConfig == null
|
|
|| !config.enterpriseConfig.isAuthenticationSimBased()) {
|
|
continue;
|
|
}
|
|
if (config.enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.PEAP) {
|
|
Pair<String, String> currentIdentity =
|
|
mWifiCarrierInfoManager.getSimIdentity(config);
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.d(TAG, "New identity for config " + config + ": " + currentIdentity);
|
|
}
|
|
// Update the loaded config
|
|
if (currentIdentity == null) {
|
|
Log.d(TAG, "Identity is null");
|
|
} else {
|
|
config.enterpriseConfig.setIdentity(currentIdentity.first);
|
|
}
|
|
// do not reset anonymous identity since it may be dependent on user-entry
|
|
// (i.e. cannot re-request on every reboot/SIM re-entry)
|
|
} else {
|
|
// reset identity as well: supplicant will ask us for it
|
|
config.enterpriseConfig.setIdentity("");
|
|
if (!WifiCarrierInfoManager.isAnonymousAtRealmIdentity(
|
|
config.enterpriseConfig.getAnonymousIdentity())) {
|
|
config.enterpriseConfig.setAnonymousIdentity("");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear all ephemeral carrier networks from the app without carrier privilege, which leads to
|
|
* a disconnection.
|
|
* Disconnection and removing networks installed by privileged apps is handled by will be
|
|
* cleaned when privilege revokes.
|
|
*/
|
|
public void removeEphemeralCarrierNetworks(Set<String> carrierPrivilegedPackages) {
|
|
if (mVerboseLoggingEnabled) localLog("removeEphemeralCarrierNetwork");
|
|
WifiConfiguration[] copiedConfigs =
|
|
mConfiguredNetworks.valuesForAllUsers().toArray(new WifiConfiguration[0]);
|
|
for (WifiConfiguration config : copiedConfigs) {
|
|
if (!config.ephemeral
|
|
|| config.subscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
|
|
continue;
|
|
}
|
|
if (carrierPrivilegedPackages.contains(config.creatorName)) {
|
|
continue;
|
|
}
|
|
removeNetwork(config.networkId, config.creatorUid, config.creatorName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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#onUserSwitching} 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.w(TAG, "User switch before store is read!");
|
|
mConfiguredNetworks.setNewUser(userId);
|
|
mCurrentUserId = userId;
|
|
// Reset any state from previous user unlock.
|
|
mDeferredUserUnlockRead = false;
|
|
// Cannot read data from new user's CE store file before they log-in.
|
|
mPendingUnlockStoreRead = true;
|
|
return new HashSet<>();
|
|
}
|
|
if (mUserManager.isUserUnlockingOrUnlocked(UserHandle.of(mCurrentUserId))) {
|
|
saveToStore(true);
|
|
}
|
|
// Remove any private networks of the old user before switching the userId.
|
|
Set<Integer> removedNetworkIds = clearInternalDataForUser(mCurrentUserId);
|
|
mConfiguredNetworks.setNewUser(userId);
|
|
mCurrentUserId = userId;
|
|
|
|
if (mUserManager.isUserUnlockingOrUnlocked(UserHandle.of(mCurrentUserId))) {
|
|
handleUserUnlockOrSwitch(mCurrentUserId);
|
|
// only handle the switching of unlocked users in {@link WifiCarrierInfoManager}.
|
|
mWifiCarrierInfoManager.onUnlockedUserSwitching(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#onUserUnlocking} 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 (userId != mCurrentUserId) {
|
|
Log.e(TAG, "Ignore user unlock for non current user " + userId);
|
|
return;
|
|
}
|
|
if (mPendingStoreRead) {
|
|
Log.w(TAG, "Ignore user unlock until store is read!");
|
|
mDeferredUserUnlockRead = true;
|
|
return;
|
|
}
|
|
if (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#onUserStopping} is invoked.
|
|
*
|
|
* @param userId The identifier of the user that stopped.
|
|
*/
|
|
public void handleUserStop(int userId) {
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.v(TAG, "Handling user stop for " + userId);
|
|
}
|
|
if (userId == mCurrentUserId
|
|
&& mUserManager.isUserUnlockingOrUnlocked(UserHandle.of(mCurrentUserId))) {
|
|
saveToStore(true);
|
|
clearInternalDataForUser(mCurrentUserId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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() {
|
|
localLog("clearInternalData: Clearing all internal data");
|
|
mConfiguredNetworks.clear();
|
|
mUserTemporarilyDisabledList.clear();
|
|
mNonCarrierMergedNetworksStatusTracker.clear();
|
|
mRandomizedMacAddressMapping.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.
|
|
*
|
|
* @return List of network ID's of all the private networks of the old user which will be
|
|
* removed from memory.
|
|
*/
|
|
private Set<Integer> clearInternalDataForUser(int user) {
|
|
localLog("clearInternalUserData: Clearing user internal data for " + user);
|
|
Set<Integer> removedNetworkIds = new HashSet<>();
|
|
// Remove any private networks of the old user before switching the userId.
|
|
for (WifiConfiguration config : getConfiguredNetworks()) {
|
|
if ((!config.shared
|
|
&& mWifiPermissionsUtil.doesUidBelongToUser(config.creatorUid, user))
|
|
|| config.ephemeral) {
|
|
removedNetworkIds.add(config.networkId);
|
|
localLog("clearInternalUserData: removed config."
|
|
+ " netId=" + config.networkId
|
|
+ " configKey=" + config.getProfileKey());
|
|
mConfiguredNetworks.remove(config.networkId);
|
|
for (OnNetworkUpdateListener listener : mListeners) {
|
|
listener.onNetworkRemoved(
|
|
createExternalWifiConfiguration(config, true, Process.WIFI_UID));
|
|
}
|
|
}
|
|
}
|
|
if (!removedNetworkIds.isEmpty()) {
|
|
sendConfiguredNetworkChangedBroadcast(WifiManager.CHANGE_REASON_REMOVED);
|
|
}
|
|
mUserTemporarilyDisabledList.clear();
|
|
mNonCarrierMergedNetworksStatusTracker.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,
|
|
Map<String, String> macAddressMapping) {
|
|
|
|
long supportedFeatures = mWifiInjector.getActiveModeWarden()
|
|
.getPrimaryClientModeManager().getSupportedFeatures();
|
|
|
|
for (WifiConfiguration configuration : configurations) {
|
|
if (!WifiConfigurationUtil.validate(
|
|
configuration, supportedFeatures, WifiConfigurationUtil.VALIDATE_FOR_ADD)) {
|
|
Log.e(TAG, "Skipping malformed network from shared store: " + configuration);
|
|
continue;
|
|
}
|
|
|
|
WifiConfiguration existingConfiguration = getInternalConfiguredNetwork(configuration);
|
|
if (null != existingConfiguration) {
|
|
Log.d(TAG, "Merging network from shared store "
|
|
+ configuration.getProfileKey());
|
|
mergeWithInternalWifiConfiguration(existingConfiguration, configuration);
|
|
continue;
|
|
}
|
|
|
|
configuration.networkId = mNextNetworkId++;
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.v(TAG, "Adding network from shared store "
|
|
+ configuration.getProfileKey());
|
|
}
|
|
try {
|
|
mConfiguredNetworks.put(configuration);
|
|
} catch (IllegalArgumentException e) {
|
|
Log.e(TAG, "Failed to add network to config map", e);
|
|
}
|
|
}
|
|
mRandomizedMacAddressMapping.putAll(macAddressMapping);
|
|
}
|
|
|
|
/**
|
|
* Helper function to populate the internal (in-memory) data from the retrieved user store
|
|
* (file) data.
|
|
*
|
|
* @param configurations list of configurations retrieved from store.
|
|
*/
|
|
private void loadInternalDataFromUserStore(List<WifiConfiguration> configurations) {
|
|
long supportedFeatures = mWifiInjector.getActiveModeWarden()
|
|
.getPrimaryClientModeManager().getSupportedFeatures();
|
|
|
|
for (WifiConfiguration configuration : configurations) {
|
|
if (!WifiConfigurationUtil.validate(
|
|
configuration, supportedFeatures, WifiConfigurationUtil.VALIDATE_FOR_ADD)) {
|
|
Log.e(TAG, "Skipping malformed network from user store: " + configuration);
|
|
continue;
|
|
}
|
|
|
|
WifiConfiguration existingConfiguration = getInternalConfiguredNetwork(configuration);
|
|
if (null != existingConfiguration) {
|
|
Log.d(TAG, "Merging network from user store "
|
|
+ configuration.getProfileKey());
|
|
mergeWithInternalWifiConfiguration(existingConfiguration, configuration);
|
|
continue;
|
|
}
|
|
|
|
configuration.networkId = mNextNetworkId++;
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.v(TAG, "Adding network from user store "
|
|
+ configuration.getProfileKey());
|
|
}
|
|
try {
|
|
mConfiguredNetworks.put(configuration);
|
|
} catch (IllegalArgumentException e) {
|
|
Log.e(TAG, "Failed to add network to config map", e);
|
|
}
|
|
|
|
if (configuration.isMostRecentlyConnected) {
|
|
mLruConnectionTracker.addNetwork(configuration);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initializes the randomized MAC address for an internal WifiConfiguration depending on
|
|
* whether it should use non-persistent randomization.
|
|
* @param config
|
|
*/
|
|
private void initRandomizedMacForInternalConfig(WifiConfiguration internalConfig) {
|
|
MacAddress randomizedMac = shouldUseNonPersistentRandomization(internalConfig)
|
|
? MacAddressUtils.createRandomUnicastAddress()
|
|
: getPersistentMacAddress(internalConfig);
|
|
if (randomizedMac != null) {
|
|
setRandomizedMacAddress(internalConfig, randomizedMac);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Assign randomized MAC addresses for configured networks.
|
|
* This is needed to generate persistent randomized MAC address for existing networks when
|
|
* a device updates to Q+ for the first time since we are not calling addOrUpdateNetwork when
|
|
* we load configuration at boot.
|
|
*/
|
|
private void generateRandomizedMacAddresses() {
|
|
for (WifiConfiguration config : getInternalConfiguredNetworks()) {
|
|
if (DEFAULT_MAC_ADDRESS.equals(config.getRandomizedMacAddress())) {
|
|
initRandomizedMacForInternalConfig(config);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 macAddressMapping
|
|
*/
|
|
private void loadInternalData(
|
|
List<WifiConfiguration> sharedConfigurations,
|
|
List<WifiConfiguration> userConfigurations,
|
|
Map<String, String> macAddressMapping) {
|
|
// Clear out all the existing in-memory lists and load the lists from what was retrieved
|
|
// from the config store.
|
|
clearInternalData();
|
|
loadInternalDataFromSharedStore(sharedConfigurations, macAddressMapping);
|
|
loadInternalDataFromUserStore(userConfigurations);
|
|
generateRandomizedMacAddresses();
|
|
if (mConfiguredNetworks.sizeForAllUsers() == 0) {
|
|
Log.w(TAG, "No stored networks found.");
|
|
}
|
|
// reset identity & anonymous identity for networks using SIM-based authentication
|
|
// on load (i.e. boot) so that if the user changed SIMs while the device was powered off,
|
|
// we do not reuse stale credentials that would lead to authentication failure.
|
|
resetSimNetworks();
|
|
sendConfiguredNetworkChangedBroadcast(WifiManager.CHANGE_REASON_ADDED);
|
|
mPendingStoreRead = false;
|
|
}
|
|
|
|
/**
|
|
* Helper method to handle any config store errors on user builds vs other debuggable builds.
|
|
*/
|
|
private boolean handleConfigStoreFailure(boolean onlyUserStore) {
|
|
// On eng/userdebug builds, return failure to leave the device in a debuggable state.
|
|
if (!mBuildProperties.isUserBuild()) return false;
|
|
|
|
// On user builds, ignore the failure and let the user create new networks.
|
|
Log.w(TAG, "Ignoring config store errors on user build");
|
|
if (!onlyUserStore) {
|
|
loadInternalData(Collections.emptyList(), Collections.emptyList(),
|
|
Collections.emptyMap());
|
|
} else {
|
|
loadInternalDataFromUserStore(Collections.emptyList());
|
|
}
|
|
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), false otherwise.
|
|
*/
|
|
public boolean loadFromStore() {
|
|
// 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.");
|
|
List<WifiConfigStore.StoreFile> userStoreFiles =
|
|
WifiConfigStore.createUserFiles(
|
|
mCurrentUserId, mFrameworkFacade.isNiapModeOn(mContext));
|
|
if (userStoreFiles == null) {
|
|
Log.wtf(TAG, "Failed to create user store files");
|
|
return false;
|
|
}
|
|
mWifiConfigStore.setUserStores(userStoreFiles);
|
|
mDeferredUserUnlockRead = false;
|
|
}
|
|
try {
|
|
mWifiConfigStore.read();
|
|
} catch (IOException | IllegalStateException e) {
|
|
Log.wtf(TAG, "Reading from new store failed. All saved networks are lost!", e);
|
|
return handleConfigStoreFailure(false);
|
|
} catch (XmlPullParserException e) {
|
|
Log.wtf(TAG, "XML deserialization of store failed. All saved networks are lost!", e);
|
|
return handleConfigStoreFailure(false);
|
|
}
|
|
loadInternalData(mNetworkListSharedStoreData.getConfigurations(),
|
|
mNetworkListUserStoreData.getConfigurations(),
|
|
mRandomizedMacStoreData.getMacMapping());
|
|
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.
|
|
*/
|
|
private boolean loadFromUserStoreAfterUnlockOrSwitch(int userId) {
|
|
try {
|
|
List<WifiConfigStore.StoreFile> userStoreFiles =
|
|
WifiConfigStore.createUserFiles(
|
|
userId, mFrameworkFacade.isNiapModeOn(mContext));
|
|
if (userStoreFiles == null) {
|
|
Log.e(TAG, "Failed to create user store files");
|
|
return false;
|
|
}
|
|
mWifiConfigStore.switchUserStoresAndRead(userStoreFiles);
|
|
} catch (IOException | IllegalStateException e) {
|
|
Log.wtf(TAG, "Reading from new store failed. All saved private networks are lost!", e);
|
|
return handleConfigStoreFailure(true);
|
|
} catch (XmlPullParserException e) {
|
|
Log.wtf(TAG, "XML deserialization of store failed. All saved private networks are "
|
|
+ "lost!", e);
|
|
return handleConfigStoreFailure(true);
|
|
}
|
|
loadInternalDataFromUserStore(mNetworkListUserStoreData.getConfigurations());
|
|
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) {
|
|
if (mPendingStoreRead) {
|
|
Log.e(TAG, "Cannot save to store before store is read!");
|
|
return false;
|
|
}
|
|
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 && mWifiPermissionsUtil
|
|
.doesUidBelongToUser(config.creatorUid, 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;
|
|
}
|
|
|
|
config.isMostRecentlyConnected =
|
|
mLruConnectionTracker.isMostRecentlyConnected(config);
|
|
|
|
// 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 || !mWifiPermissionsUtil
|
|
.doesUidBelongToUser(config.creatorUid, 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.
|
|
mNetworkListSharedStoreData.setConfigurations(sharedConfigurations);
|
|
mNetworkListUserStoreData.setConfigurations(userConfigurations);
|
|
mRandomizedMacStoreData.setMacMapping(mRandomizedMacAddressMapping);
|
|
|
|
try {
|
|
mWifiConfigStore.write(forceWrite);
|
|
} catch (IOException | IllegalStateException 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 - ConfigurationMap Begin ----");
|
|
mConfiguredNetworks.dump(fd, pw, args);
|
|
pw.println("WifiConfigManager - ConfigurationMap End ----");
|
|
pw.println("WifiConfigManager - Next network ID to be allocated " + mNextNetworkId);
|
|
pw.println("WifiConfigManager - Last selected network ID " + mLastSelectedNetworkId);
|
|
pw.println("WifiConfigManager - PNO scan frequency culling enabled = "
|
|
+ mContext.getResources().getBoolean(R.bool.config_wifiPnoFrequencyCullingEnabled));
|
|
pw.println("WifiConfigManager - PNO scan recency sorting enabled = "
|
|
+ mContext.getResources().getBoolean(R.bool.config_wifiPnoRecencySortingEnabled));
|
|
mWifiConfigStore.dump(fd, pw, args);
|
|
mWifiCarrierInfoManager.dump(fd, pw, args);
|
|
mNonCarrierMergedNetworksStatusTracker.dump(fd, pw, args);
|
|
}
|
|
|
|
/**
|
|
* Returns true if the given uid has permission to add, update or remove proxy settings
|
|
*/
|
|
private boolean canModifyProxySettings(int uid, String packageName) {
|
|
final boolean isAdmin = mWifiPermissionsUtil.isAdmin(uid, packageName);
|
|
final boolean hasNetworkSettingsPermission =
|
|
mWifiPermissionsUtil.checkNetworkSettingsPermission(uid);
|
|
final boolean hasNetworkSetupWizardPermission =
|
|
mWifiPermissionsUtil.checkNetworkSetupWizardPermission(uid);
|
|
final boolean hasNetworkManagedProvisioningPermission =
|
|
mWifiPermissionsUtil.checkNetworkManagedProvisioningPermission(uid);
|
|
// If |uid| corresponds to the admin, allow all modifications.
|
|
if (isAdmin || hasNetworkSettingsPermission
|
|
|| hasNetworkSetupWizardPermission || hasNetworkManagedProvisioningPermission) {
|
|
return true;
|
|
}
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.v(TAG, "UID: " + uid + " cannot modify WifiConfiguration proxy settings."
|
|
+ " hasNetworkSettings=" + hasNetworkSettingsPermission
|
|
+ " hasNetworkSetupWizard=" + hasNetworkSetupWizardPermission
|
|
+ " Admin=" + isAdmin);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Add the network update event listener
|
|
*/
|
|
public void addOnNetworkUpdateListener(@NonNull OnNetworkUpdateListener listener) {
|
|
if (listener == null) {
|
|
Log.wtf(TAG, "addOnNetworkUpdateListener: listener must not be null");
|
|
return;
|
|
}
|
|
mListeners.add(listener);
|
|
}
|
|
|
|
/**
|
|
* Remove the network update event listener
|
|
*/
|
|
public void removeOnNetworkUpdateListener(@NonNull OnNetworkUpdateListener listener) {
|
|
if (listener == null) {
|
|
Log.wtf(TAG, "removeOnNetworkUpdateListener: listener must not be null");
|
|
return;
|
|
}
|
|
mListeners.remove(listener);
|
|
}
|
|
|
|
/**
|
|
* Set extra failure reason for given config. Used to surface extra failure details to the UI
|
|
* @param netId The network ID of the config to set the extra failure reason for
|
|
* @param reason the WifiConfiguration.ExtraFailureReason failure code representing the most
|
|
* recent failure reason
|
|
*/
|
|
public void setRecentFailureAssociationStatus(int netId, int reason) {
|
|
WifiConfiguration config = getInternalConfiguredNetwork(netId);
|
|
if (config == null) {
|
|
return;
|
|
}
|
|
mWifiMetrics.incrementRecentFailureAssociationStatusCount(reason);
|
|
int previousReason = config.recentFailure.getAssociationStatus();
|
|
config.recentFailure.setAssociationStatus(reason, mClock.getElapsedSinceBootMillis());
|
|
if (previousReason != reason) {
|
|
sendConfiguredNetworkChangedBroadcast(WifiManager.CHANGE_REASON_CONFIG_CHANGE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param netId The network ID of the config to clear the extra failure reason from
|
|
*/
|
|
public void clearRecentFailureReason(int netId) {
|
|
WifiConfiguration config = getInternalConfiguredNetwork(netId);
|
|
if (config == null) {
|
|
return;
|
|
}
|
|
config.recentFailure.clear();
|
|
}
|
|
|
|
/**
|
|
* Clear all recent failure reasons that have timed out.
|
|
*/
|
|
public void cleanupExpiredRecentFailureReasons() {
|
|
long timeoutDuration = mContext.getResources().getInteger(
|
|
R.integer.config_wifiRecentFailureReasonExpirationMinutes) * 60 * 1000;
|
|
for (WifiConfiguration config : getInternalConfiguredNetworks()) {
|
|
if (config.recentFailure.getAssociationStatus()
|
|
!= WifiConfiguration.RECENT_FAILURE_NONE
|
|
&& mClock.getElapsedSinceBootMillis()
|
|
>= config.recentFailure.getLastUpdateTimeSinceBootMillis() + timeoutDuration) {
|
|
config.recentFailure.clear();
|
|
sendConfiguredNetworkChangedBroadcast(WifiManager.CHANGE_REASON_CONFIG_CHANGE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find the highest RSSI among all valid scanDetails in current network's scanDetail cache.
|
|
* If scanDetail is too old, it is not considered to be valid.
|
|
* @param netId The network ID of the config to find scan RSSI
|
|
* @params scanRssiValidTimeMs The valid time for scan RSSI
|
|
* @return The highest RSSI in dBm found with current network's scanDetail cache.
|
|
*/
|
|
public int findScanRssi(int netId, int scanRssiValidTimeMs) {
|
|
int scanMaxRssi = WifiInfo.INVALID_RSSI;
|
|
ScanDetailCache scanDetailCache = getScanDetailCacheForNetwork(netId);
|
|
if (scanDetailCache == null || scanDetailCache.size() == 0) return scanMaxRssi;
|
|
long nowInMillis = mClock.getWallClockMillis();
|
|
for (ScanDetail scanDetail : scanDetailCache.values()) {
|
|
ScanResult result = scanDetail.getScanResult();
|
|
if (result == null) continue;
|
|
boolean valid = (nowInMillis - result.seen) < scanRssiValidTimeMs;
|
|
|
|
if (valid) {
|
|
scanMaxRssi = Math.max(scanMaxRssi, result.level);
|
|
}
|
|
}
|
|
return scanMaxRssi;
|
|
}
|
|
|
|
public Comparator<WifiConfiguration> getScanListComparator() {
|
|
return mScanListComparator;
|
|
}
|
|
|
|
/**
|
|
* This API is called when a connection successfully completes on an existing network
|
|
* selected by the user. It is not called after the first connection of a newly added network.
|
|
* Following actions will be triggered:
|
|
* 1. If this network is disabled, we need re-enable it again.
|
|
* 2. This network is favored over all the other networks visible in latest network
|
|
* selection procedure.
|
|
*
|
|
* @param netId ID for the network chosen by the user
|
|
* @param rssi the signal strength of the user selected network
|
|
* @return true -- There is change made to connection choice of any saved network.
|
|
* false -- There is no change made to connection choice of any saved network.
|
|
*/
|
|
private boolean setUserConnectChoice(int netId, int rssi) {
|
|
localLog("userSelectNetwork: network ID=" + netId);
|
|
WifiConfiguration selected = getInternalConfiguredNetwork(netId);
|
|
|
|
if (selected == null || selected.getProfileKey() == null) {
|
|
localLog("userSelectNetwork: Invalid configuration with nid=" + netId);
|
|
return false;
|
|
}
|
|
|
|
// Enable the network if it is disabled.
|
|
if (!selected.getNetworkSelectionStatus().isNetworkEnabled()) {
|
|
updateNetworkSelectionStatus(selected,
|
|
WifiConfiguration.NetworkSelectionStatus.DISABLED_NONE);
|
|
}
|
|
boolean changed = setLegacyUserConnectChoice(selected, rssi);
|
|
return changed;
|
|
}
|
|
|
|
/**
|
|
* This maintains the legacy user connect choice state in the config store
|
|
*/
|
|
public boolean setLegacyUserConnectChoice(@NonNull final WifiConfiguration selected,
|
|
int rssi) {
|
|
boolean change = false;
|
|
Collection<WifiConfiguration> configuredNetworks = getInternalConfiguredNetworks();
|
|
ArrayList<WifiConfiguration> networksInRange = new ArrayList<>();
|
|
String key = selected.getProfileKey();
|
|
for (WifiConfiguration network : configuredNetworks) {
|
|
WifiConfiguration.NetworkSelectionStatus status = network.getNetworkSelectionStatus();
|
|
if (network.networkId == selected.networkId) {
|
|
if (status.getConnectChoice() != null) {
|
|
localLog("Remove user selection preference of " + status.getConnectChoice()
|
|
+ " from " + network.SSID + " : " + network.networkId);
|
|
clearConnectChoiceInternal(network);
|
|
change = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (status.getSeenInLastQualifiedNetworkSelection()) {
|
|
setConnectChoiceInternal(network, key, rssi);
|
|
change = true;
|
|
networksInRange.add(network);
|
|
}
|
|
}
|
|
|
|
for (OnNetworkUpdateListener listener : mListeners) {
|
|
listener.onConnectChoiceSet(networksInRange, key, rssi);
|
|
}
|
|
return change;
|
|
}
|
|
|
|
private void clearConnectChoiceInternal(WifiConfiguration config) {
|
|
config.getNetworkSelectionStatus().setConnectChoice(null);
|
|
config.getNetworkSelectionStatus().setConnectChoiceRssi(0);
|
|
}
|
|
|
|
private void setConnectChoiceInternal(WifiConfiguration config, String key, int rssi) {
|
|
config.getNetworkSelectionStatus().setConnectChoice(key);
|
|
config.getNetworkSelectionStatus().setConnectChoiceRssi(rssi);
|
|
localLog("Add connect choice key: " + key + " rssi: " + rssi + " to "
|
|
+ WifiNetworkSelector.toNetworkString(config));
|
|
}
|
|
|
|
/** Update WifiConfigManager before connecting to a network. */
|
|
public void updateBeforeConnect(int networkId, int callingUid, @NonNull String packageName) {
|
|
userEnabledNetwork(networkId);
|
|
if (!enableNetwork(networkId, true, callingUid, null)
|
|
|| !updateLastConnectUid(networkId, callingUid)) {
|
|
Log.i(TAG, "connect Allowing uid " + callingUid + " packageName " + packageName
|
|
+ " with insufficient permissions to connect=" + networkId);
|
|
}
|
|
}
|
|
|
|
/** See {@link WifiManager#save(WifiConfiguration, WifiManager.ActionListener)} */
|
|
public NetworkUpdateResult updateBeforeSaveNetwork(WifiConfiguration config, int callingUid,
|
|
@NonNull String packageName) {
|
|
NetworkUpdateResult result = addOrUpdateNetwork(config, callingUid);
|
|
if (!result.isSuccess()) {
|
|
Log.e(TAG, "saveNetwork adding/updating config=" + config + " failed");
|
|
return result;
|
|
}
|
|
if (!enableNetwork(result.getNetworkId(), false, callingUid, null)) {
|
|
Log.e(TAG, "saveNetwork enabling config=" + config + " failed");
|
|
return NetworkUpdateResult.makeFailed();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Gets the most recent scan result that is newer than maxAgeMillis for each configured network.
|
|
* @param maxAgeMillis scan results older than this parameter will get filtered out.
|
|
*/
|
|
public @NonNull List<ScanResult> getMostRecentScanResultsForConfiguredNetworks(
|
|
int maxAgeMillis) {
|
|
List<ScanResult> results = new ArrayList<>();
|
|
long timeNowMs = mClock.getWallClockMillis();
|
|
for (WifiConfiguration config : getInternalConfiguredNetworks()) {
|
|
ScanDetailCache scanDetailCache = getScanDetailCacheForNetwork(config.networkId);
|
|
if (scanDetailCache == null) {
|
|
continue;
|
|
}
|
|
ScanResult scanResult = scanDetailCache.getMostRecentScanResult();
|
|
if (scanResult == null) {
|
|
continue;
|
|
}
|
|
if (timeNowMs - scanResult.seen < maxAgeMillis) {
|
|
results.add(scanResult);
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* Update the configuration according to transition disable indications.
|
|
*
|
|
* @param networkId network ID corresponding to the network.
|
|
* @param indicationBit transition disable indication bits.
|
|
* @return true if the network was found, false otherwise.
|
|
*/
|
|
public boolean updateNetworkTransitionDisable(int networkId,
|
|
@WifiMonitor.TransitionDisableIndication int indicationBit) {
|
|
localLog("updateNetworkTransitionDisable: network ID=" + networkId
|
|
+ " indication: " + indicationBit);
|
|
WifiConfiguration config = getInternalConfiguredNetwork(networkId);
|
|
if (config == null) {
|
|
Log.e(TAG, "Cannot find network for " + networkId);
|
|
return false;
|
|
}
|
|
WifiConfiguration copy = new WifiConfiguration(config);
|
|
boolean changed = false;
|
|
if (0 != (indicationBit & WifiMonitor.TDI_USE_WPA3_PERSONAL)
|
|
&& config.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE)) {
|
|
config.setSecurityParamsEnabled(WifiConfiguration.SECURITY_TYPE_PSK, false);
|
|
changed = true;
|
|
}
|
|
if (0 != (indicationBit & WifiMonitor.TDI_USE_SAE_PK)) {
|
|
config.enableSaePkOnlyMode(true);
|
|
changed = true;
|
|
}
|
|
if (0 != (indicationBit & WifiMonitor.TDI_USE_WPA3_ENTERPRISE)
|
|
&& config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE)) {
|
|
config.setSecurityParamsEnabled(WifiConfiguration.SECURITY_TYPE_EAP, false);
|
|
changed = true;
|
|
}
|
|
if (0 != (indicationBit & WifiMonitor.TDI_USE_ENHANCED_OPEN)
|
|
&& config.isSecurityType(WifiConfiguration.SECURITY_TYPE_OWE)) {
|
|
config.setSecurityParamsEnabled(WifiConfiguration.SECURITY_TYPE_OPEN, false);
|
|
changed = true;
|
|
}
|
|
if (changed) {
|
|
for (OnNetworkUpdateListener listener : mListeners) {
|
|
listener.onSecurityParamsUpdate(copy, config.getSecurityParamsList());
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the configured network corresponding to the provided configKey
|
|
* without any masking.
|
|
*
|
|
* WARNING: Don't use this to pass network configurations except in the wifi stack, when
|
|
* there is a need for passwords and randomized MAC address.
|
|
*
|
|
* @param configKey configKey of the requested network.
|
|
* @return Copy of WifiConfiguration object if found, null otherwise.
|
|
*/
|
|
private WifiConfiguration getConfiguredNetworkWithoutMasking(String configKey) {
|
|
WifiConfiguration config = getInternalConfiguredNetwork(configKey);
|
|
if (config == null) {
|
|
return null;
|
|
}
|
|
return new WifiConfiguration(config);
|
|
}
|
|
|
|
/**
|
|
* This method links the config of the provided network id to every linkable saved network.
|
|
*
|
|
* @param networkId networkId corresponding to the network to be potentially linked.
|
|
*/
|
|
public void updateLinkedNetworks(int networkId) {
|
|
WifiConfiguration internalConfig = getInternalConfiguredNetwork(networkId);
|
|
if (internalConfig == null) {
|
|
return;
|
|
}
|
|
internalConfig.linkedConfigurations = new HashMap<>();
|
|
attemptNetworkLinking(internalConfig);
|
|
}
|
|
|
|
/**
|
|
* This method returns a map containing each config key and unmasked WifiConfiguration of every
|
|
* network linked to the provided network id.
|
|
* @param networkId networkId to get the linked configs of.
|
|
* @return HashMap of config key to unmasked WifiConfiguration
|
|
*/
|
|
public Map<String, WifiConfiguration> getLinkedNetworksWithoutMasking(int networkId) {
|
|
WifiConfiguration internalConfig = getInternalConfiguredNetwork(networkId);
|
|
if (internalConfig == null) {
|
|
return null;
|
|
}
|
|
|
|
Map<String, WifiConfiguration> linkedNetworks = new HashMap<>();
|
|
Map<String, Integer> linkedConfigurations = internalConfig.linkedConfigurations;
|
|
if (linkedConfigurations == null) {
|
|
return null;
|
|
}
|
|
for (String configKey : linkedConfigurations.keySet()) {
|
|
WifiConfiguration linkConfig = getConfiguredNetworkWithoutMasking(configKey);
|
|
if (linkConfig == null
|
|
|| !linkConfig.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK)) {
|
|
continue;
|
|
}
|
|
|
|
linkConfig.getNetworkSelectionStatus().setCandidateSecurityParams(
|
|
SecurityParams.createSecurityParamsBySecurityType(
|
|
WifiConfiguration.SECURITY_TYPE_PSK));
|
|
linkedNetworks.put(configKey, linkConfig);
|
|
}
|
|
return linkedNetworks;
|
|
}
|
|
|
|
/**
|
|
* This method updates FILS AKMs to the internal network.
|
|
*
|
|
* @param networkId networkId corresponding to the network to be updated.
|
|
*/
|
|
public void updateFilsAkms(int networkId,
|
|
boolean isFilsSha256Supported, boolean isFilsSha384Supported) {
|
|
WifiConfiguration internalConfig = getInternalConfiguredNetwork(networkId);
|
|
if (internalConfig == null) {
|
|
return;
|
|
}
|
|
internalConfig.enableFils(isFilsSha256Supported, isFilsSha384Supported);
|
|
}
|
|
|
|
/**
|
|
* This method updates auto-upgrade flag to the internal network.
|
|
*
|
|
* @param networkId networkId corresponding to the network to be updated.
|
|
* @param securityType the target security type
|
|
* @param isAddedByAutoUpgrade indicate whether the target security type is added
|
|
* by auto-upgrade or not.
|
|
*/
|
|
public void updateIsAddedByAutoUpgradeFlag(int networkId,
|
|
int securityType, boolean isAddedByAutoUpgrade) {
|
|
WifiConfiguration internalConfig = getInternalConfiguredNetwork(networkId);
|
|
if (internalConfig == null) {
|
|
return;
|
|
}
|
|
internalConfig.setSecurityParamsIsAddedByAutoUpgrade(securityType, isAddedByAutoUpgrade);
|
|
saveToStore(true);
|
|
}
|
|
|
|
private static final int SUBJECT_ALTERNATIVE_NAMES_EMAIL = 1;
|
|
private static final int SUBJECT_ALTERNATIVE_NAMES_DNS = 2;
|
|
private static final int SUBJECT_ALTERNATIVE_NAMES_URI = 6;
|
|
/** altSubjectMatch only matches EMAIL, DNS, and URI. */
|
|
private static String getAltSubjectMatchFromAltSubjectName(X509Certificate cert) {
|
|
Collection<List<?>> col = null;
|
|
try {
|
|
col = cert.getSubjectAlternativeNames();
|
|
} catch (CertificateParsingException ex) {
|
|
col = null;
|
|
}
|
|
|
|
if (null == col) return null;
|
|
if (0 == col.size()) return null;
|
|
|
|
List<String> altSubjectNameList = new ArrayList<>();
|
|
for (List<?> item: col) {
|
|
if (2 != item.size()) continue;
|
|
if (!(item.get(0) instanceof Integer)) continue;
|
|
if (!(item.get(1) instanceof String)) continue;
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
int type = (Integer) item.get(0);
|
|
if (SUBJECT_ALTERNATIVE_NAMES_EMAIL == type) {
|
|
sb.append("EMAIL:");
|
|
} else if (SUBJECT_ALTERNATIVE_NAMES_DNS == type) {
|
|
sb.append("DNS:");
|
|
} else if (SUBJECT_ALTERNATIVE_NAMES_URI == type) {
|
|
sb.append("URI:");
|
|
} else {
|
|
Log.d(TAG, "Ignore type " + type + " for altSubjectMatch");
|
|
continue;
|
|
}
|
|
sb.append((String) item.get(1));
|
|
altSubjectNameList.add(sb.toString());
|
|
}
|
|
if (altSubjectNameList.size() > 0) {
|
|
// wpa_supplicant uses ';' as the separator.
|
|
return String.join(";", altSubjectNameList);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* This method updates the Root CA certificate and the domain name of the
|
|
* server in the internal network.
|
|
*
|
|
* @param networkId networkId corresponding to the network to be updated.
|
|
* @param caCert Root CA certificate to be updated.
|
|
* @param serverCert Server certificate to be updated.
|
|
* @param certHash Server certificate hash (for TOFU case with no Root CA)
|
|
* @return true if updating Root CA certificate successfully; otherwise, false.
|
|
*/
|
|
public boolean updateCaCertificate(int networkId, @NonNull X509Certificate caCert,
|
|
@NonNull X509Certificate serverCert, String certHash) {
|
|
WifiConfiguration internalConfig = getInternalConfiguredNetwork(networkId);
|
|
if (internalConfig == null) {
|
|
Log.e(TAG, "No network for network ID " + networkId);
|
|
return false;
|
|
}
|
|
if (!internalConfig.isEnterprise()) {
|
|
Log.e(TAG, "Network " + networkId + " is not an Enterprise network");
|
|
return false;
|
|
}
|
|
if (!internalConfig.enterpriseConfig.isEapMethodServerCertUsed()) {
|
|
Log.e(TAG, "Network " + networkId + " does not need verifying server cert");
|
|
return false;
|
|
}
|
|
if (null == caCert) {
|
|
Log.e(TAG, "Root CA cert is null");
|
|
return false;
|
|
}
|
|
if (null == serverCert) {
|
|
Log.e(TAG, "Server cert is null");
|
|
return false;
|
|
}
|
|
CertificateSubjectInfo serverCertInfo = CertificateSubjectInfo.parse(
|
|
serverCert.getSubjectDN().getName());
|
|
if (null == serverCertInfo) {
|
|
Log.e(TAG, "Invalid Server CA cert subject");
|
|
return false;
|
|
}
|
|
|
|
WifiConfiguration newConfig = new WifiConfiguration(internalConfig);
|
|
try {
|
|
if (newConfig.enterpriseConfig.isTrustOnFirstUseEnabled()) {
|
|
if (TextUtils.isEmpty(certHash)) {
|
|
newConfig.enterpriseConfig.setCaCertificateForTrustOnFirstUse(caCert);
|
|
} else {
|
|
newConfig.enterpriseConfig.setServerCertificateHash(certHash);
|
|
}
|
|
newConfig.enterpriseConfig.enableTrustOnFirstUse(false);
|
|
} else {
|
|
// setCaCertificate will mark that this CA certificate should be removed on
|
|
// removing this configuration.
|
|
newConfig.enterpriseConfig.setCaCertificate(caCert);
|
|
}
|
|
} catch (IllegalArgumentException ex) {
|
|
Log.e(TAG, "Failed to set CA cert: " + caCert);
|
|
return false;
|
|
}
|
|
|
|
// If there is a subject alternative name, it should be matched first.
|
|
String altSubjectNames = getAltSubjectMatchFromAltSubjectName(serverCert);
|
|
if (!TextUtils.isEmpty(altSubjectNames)) {
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.d(TAG, "Set altSubjectMatch to " + altSubjectNames);
|
|
}
|
|
newConfig.enterpriseConfig.setAltSubjectMatch(altSubjectNames);
|
|
} else {
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.d(TAG, "Set domainSuffixMatch to " + serverCertInfo.commonName);
|
|
}
|
|
newConfig.enterpriseConfig.setDomainSuffixMatch(serverCertInfo.commonName);
|
|
}
|
|
newConfig.enterpriseConfig.setUserApproveNoCaCert(false);
|
|
// Trigger an update to install CA certificate and the corresponding configuration.
|
|
NetworkUpdateResult result = addOrUpdateNetwork(newConfig, internalConfig.creatorUid);
|
|
if (!result.isSuccess()) {
|
|
Log.e(TAG, "Failed to install CA cert for network " + internalConfig.SSID);
|
|
mFrameworkFacade.showToast(mContext, mContext.getResources().getString(
|
|
R.string.wifi_ca_cert_failed_to_install_ca_cert));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* This method updates Trust On First Use flag according to
|
|
* Trust On First Use support and No-Ca-Cert Approval.
|
|
*/
|
|
public void updateTrustOnFirstUseFlag(boolean enableTrustOnFirstUse) {
|
|
getInternalConfiguredNetworks().stream()
|
|
.filter(config -> config.isEnterprise())
|
|
.filter(config -> config.enterpriseConfig.isEapMethodServerCertUsed())
|
|
.filter(config -> !config.enterpriseConfig.hasCaCertificate())
|
|
.forEach(config ->
|
|
config.enterpriseConfig.enableTrustOnFirstUse(enableTrustOnFirstUse));
|
|
}
|
|
|
|
/**
|
|
* This method updates that a network could has no CA cert as a user approves it.
|
|
*
|
|
* @param networkId networkId corresponding to the network to be updated.
|
|
* @param approved true for the approval; otherwise, false.
|
|
*/
|
|
public void setUserApproveNoCaCert(int networkId, boolean approved) {
|
|
WifiConfiguration internalConfig = getInternalConfiguredNetwork(networkId);
|
|
if (internalConfig == null) return;
|
|
if (!internalConfig.isEnterprise()) return;
|
|
if (!internalConfig.enterpriseConfig.isEapMethodServerCertUsed()) return;
|
|
internalConfig.enterpriseConfig.setUserApproveNoCaCert(approved);
|
|
}
|
|
|
|
/**
|
|
* This method updates that a network uses Trust On First Use.
|
|
*
|
|
* @param networkId networkId corresponding to the network to be updated.
|
|
* @param enable true to enable Trust On First Use; otherwise, disable Trust On First Use.
|
|
*/
|
|
public void enableTrustOnFirstUse(int networkId, boolean enable) {
|
|
WifiConfiguration internalConfig = getInternalConfiguredNetwork(networkId);
|
|
if (internalConfig == null) return;
|
|
if (!internalConfig.isEnterprise()) return;
|
|
if (!internalConfig.enterpriseConfig.isEapMethodServerCertUsed()) return;
|
|
internalConfig.enterpriseConfig.enableTrustOnFirstUse(enable);
|
|
}
|
|
|
|
/**
|
|
* Add custom DHCP options.
|
|
*
|
|
* @param ssid the network SSID.
|
|
* @param oui the 3-byte OUI.
|
|
* @param options the list of DHCP options.
|
|
*/
|
|
public void addCustomDhcpOptions(@NonNull WifiSsid ssid, @NonNull byte[] oui,
|
|
@NonNull List<DhcpOption> options) {
|
|
mCustomDhcpOptions.put(new NetworkIdentifier(ssid, oui), options);
|
|
}
|
|
|
|
/**
|
|
* Remove custom DHCP options.
|
|
*
|
|
* @param ssid the network SSID.
|
|
* @param oui the 3-byte OUI.
|
|
*/
|
|
public void removeCustomDhcpOptions(@NonNull WifiSsid ssid, @NonNull byte[] oui) {
|
|
mCustomDhcpOptions.remove(new NetworkIdentifier(ssid, oui));
|
|
}
|
|
|
|
/**
|
|
* Get custom DHCP options.
|
|
*
|
|
* @param ssid the network SSID.
|
|
* @param ouiList the list of OUIs.
|
|
*
|
|
* @return null if no entry in the map is keyed by the SSID and any OUI in the list.
|
|
* all the DHCP options keyed by the SSID and the OUIs in the list.
|
|
*/
|
|
public List<DhcpOption> getCustomDhcpOptions(@NonNull WifiSsid ssid,
|
|
@NonNull List<byte[]> ouiList) {
|
|
Set<DhcpOption> results = new HashSet<>();
|
|
for (byte[] oui : ouiList) {
|
|
List<DhcpOption> options = mCustomDhcpOptions.get(new NetworkIdentifier(ssid, oui));
|
|
if (options != null) {
|
|
results.addAll(options);
|
|
}
|
|
}
|
|
return new ArrayList<>(results);
|
|
}
|
|
}
|