blob: 8d28d7a4ae2270b563de4c2038a61ccb1fd1eca7 [file] [log] [blame]
/*
* Copyright (C) 2018 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 com.android.server.wifi.WifiNetworkSelector.toNetworkString;
import android.annotation.NonNull;
import android.net.wifi.WifiConfiguration;
import android.telephony.TelephonyManager;
import android.util.LocalLog;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import com.android.modules.utils.build.SdkLevel;
import com.android.server.wifi.WifiNetworkSuggestionsManager.ExtendedWifiNetworkSuggestion;
import com.android.server.wifi.hotspot2.PasspointNetworkNominateHelper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
/**
* Nominator nominate the highest available suggestion candidates.
* Note:
* <li> This class is not thread safe and meant to be used only from {@link WifiNetworkSelector}.
* </li>
*
*/
@NotThreadSafe
public class NetworkSuggestionNominator implements WifiNetworkSelector.NetworkNominator {
private static final String TAG = "NetworkSuggestionNominator";
private final WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
private final WifiConfigManager mWifiConfigManager;
private final PasspointNetworkNominateHelper mPasspointNetworkNominateHelper;
private final LocalLog mLocalLog;
private final WifiCarrierInfoManager mWifiCarrierInfoManager;
private final WifiMetrics mWifiMetrics;
NetworkSuggestionNominator(WifiNetworkSuggestionsManager networkSuggestionsManager,
WifiConfigManager wifiConfigManager, PasspointNetworkNominateHelper nominateHelper,
LocalLog localLog, WifiCarrierInfoManager wifiCarrierInfoManager,
WifiMetrics wifiMetrics) {
mWifiNetworkSuggestionsManager = networkSuggestionsManager;
mWifiConfigManager = wifiConfigManager;
mPasspointNetworkNominateHelper = nominateHelper;
mLocalLog = localLog;
mWifiCarrierInfoManager = wifiCarrierInfoManager;
mWifiMetrics = wifiMetrics;
}
@Override
public void update(List<ScanDetail> scanDetails) {
// Update the matching profiles into WifiConfigManager, help displaying Suggestion and
// Passpoint networks in Wifi Picker
addOrUpdateSuggestionsToWifiConfigManger(scanDetails);
mPasspointNetworkNominateHelper.getPasspointNetworkCandidates(scanDetails, true);
}
@Override
public void nominateNetworks(List<ScanDetail> scanDetails,
boolean untrustedNetworkAllowed, boolean oemPaidNetworkAllowed,
boolean oemPrivateNetworkAllowed,
@NonNull OnConnectableListener onConnectableListener) {
if (scanDetails.isEmpty()) {
return;
}
MatchMetaInfo matchMetaInfo = new MatchMetaInfo();
findMatchedPasspointSuggestionNetworks(
scanDetails, matchMetaInfo, untrustedNetworkAllowed, oemPaidNetworkAllowed,
oemPrivateNetworkAllowed);
findMatchedSuggestionNetworks(scanDetails, matchMetaInfo, untrustedNetworkAllowed,
oemPaidNetworkAllowed,
oemPrivateNetworkAllowed);
if (matchMetaInfo.isEmpty()) {
mLocalLog.log("did not see any matching auto-join enabled network suggestions.");
} else {
matchMetaInfo.findConnectableNetworksAndHighestPriority(onConnectableListener);
}
}
private void addOrUpdateSuggestionsToWifiConfigManger(List<ScanDetail> scanDetails) {
for (ScanDetail scanDetail : scanDetails) {
Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestions =
mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(scanDetail);
if (matchingExtNetworkSuggestions == null || matchingExtNetworkSuggestions.isEmpty()) {
continue;
}
for (ExtendedWifiNetworkSuggestion ewns : matchingExtNetworkSuggestions) {
addOrUpdateSuggestionToWifiConfigManger(ewns);
}
}
}
private void addOrUpdateSuggestionToWifiConfigManger(ExtendedWifiNetworkSuggestion ewns) {
WifiConfiguration config = ewns.createInternalWifiConfiguration(mWifiCarrierInfoManager);
WifiConfiguration wCmConfiguredNetwork =
mWifiConfigManager.getConfiguredNetwork(config.getProfileKey());
NetworkUpdateResult result = mWifiConfigManager.addOrUpdateNetwork(
config, ewns.perAppInfo.uid, ewns.perAppInfo.packageName);
if (!result.isSuccess()) {
mLocalLog.log("Failed to add network suggestion");
return;
}
mLocalLog.log(config.getProfileKey()
+ " is added/updated in the WifiConfigManager");
mWifiConfigManager.allowAutojoin(result.getNetworkId(), config.allowAutojoin);
WifiConfiguration currentWCmConfiguredNetwork =
mWifiConfigManager.getConfiguredNetwork(result.getNetworkId());
// Try to enable network selection
if (wCmConfiguredNetwork == null) {
if (!mWifiConfigManager.updateNetworkSelectionStatus(result.getNetworkId(),
WifiConfiguration.NetworkSelectionStatus.DISABLED_NONE)) {
mLocalLog.log("Failed to make network suggestion selectable");
}
} else {
if (!currentWCmConfiguredNetwork.getNetworkSelectionStatus().isNetworkEnabled()
&& !mWifiConfigManager.tryEnableNetwork(wCmConfiguredNetwork.networkId)) {
mLocalLog.log("Ignoring blocked network: "
+ toNetworkString(wCmConfiguredNetwork));
}
}
}
/** Helper method to avoid code duplication in regular & passpoint based suggestions filter. */
private boolean shouldIgnoreBasedOnChecksForTrustedOrOemPaidOrOemPrivate(
WifiConfiguration config, boolean untrustedNetworkAllowed,
boolean oemPaidNetworkAllowed, boolean oemPrivateNetworkAllowed) {
// If untrusted network is not allowed, ignore untrusted suggestion.
if (!untrustedNetworkAllowed && !config.trusted) {
return true;
}
// For suggestions with both oem paid & oem private set, ignore them If both oem paid
// & oem private network is not allowed. If oem paid network is allowed, then mark
// the suggestion oem paid for this connection attempt, else mark oem private for this
// connection attempt.
if (config.oemPaid && config.oemPrivate) {
if (!oemPaidNetworkAllowed && !oemPrivateNetworkAllowed) {
return true;
}
if (oemPaidNetworkAllowed) {
config.oemPrivate = false; // only oemPaid set.
} else if (oemPrivateNetworkAllowed) {
config.oemPaid = false; // only oemPrivate set.
}
} else {
// If oem paid network is not allowed, ignore oem paid suggestion.
if (!oemPaidNetworkAllowed && config.oemPaid) {
return true;
}
// If oem paid network is not allowed, ignore oem paid suggestion.
if (!oemPrivateNetworkAllowed && config.oemPrivate) {
return true;
}
}
return false;
}
private void findMatchedPasspointSuggestionNetworks(List<ScanDetail> scanDetails,
MatchMetaInfo matchMetaInfo, boolean untrustedNetworkAllowed,
boolean oemPaidNetworkAllowed, boolean oemPrivateNetworkAllowed) {
List<Pair<ScanDetail, WifiConfiguration>> candidates =
mPasspointNetworkNominateHelper.getPasspointNetworkCandidates(scanDetails, true);
for (Pair<ScanDetail, WifiConfiguration> candidate : candidates) {
WifiConfiguration config = candidate.second;
Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestions =
mWifiNetworkSuggestionsManager.getNetworkSuggestionsForFqdn(config.FQDN);
if (matchingExtNetworkSuggestions.isEmpty()) {
mLocalLog.log("No user approved suggestion for FQDN:" + config.FQDN);
continue;
}
Optional<ExtendedWifiNetworkSuggestion> matchingPasspointExtSuggestion =
matchingExtNetworkSuggestions.stream()
.filter(ewns -> Objects.equals(
ewns.wns.wifiConfiguration.getPasspointUniqueId(),
config.getPasspointUniqueId()))
.findFirst();
if (!matchingPasspointExtSuggestion.isPresent()) {
mLocalLog.log("Suggestion is missing for passpoint FQDN: " + config.FQDN
+ " profile key: " + config.getProfileKey());
continue;
}
if (!isNetworkAvailableToAutoConnect(config, untrustedNetworkAllowed,
oemPaidNetworkAllowed, oemPrivateNetworkAllowed)) {
continue;
}
matchMetaInfo.put(matchingPasspointExtSuggestion.get(), config, candidate.first);
}
}
private void findMatchedSuggestionNetworks(List<ScanDetail> scanDetails,
MatchMetaInfo matchMetaInfo, boolean untrustedNetworkAllowed,
boolean oemPaidNetworkAllowed,
boolean oemPrivateNetworkAllowed) {
for (ScanDetail scanDetail : scanDetails) {
Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestions =
mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(scanDetail);
if (matchingExtNetworkSuggestions.isEmpty()) {
continue;
}
if (matchingExtNetworkSuggestions.size() > 1) {
mWifiMetrics.incrementNetworkSuggestionMoreThanOneSuggestionForSingleScanResult();
}
for (ExtendedWifiNetworkSuggestion ewns : matchingExtNetworkSuggestions) {
WifiConfiguration config = ewns.createInternalWifiConfiguration(
mWifiCarrierInfoManager);
WifiConfiguration wCmConfiguredNetwork =
mWifiConfigManager.getConfiguredNetwork(config.getProfileKey());
if (wCmConfiguredNetwork == null) {
mLocalLog.log(config.getProfileKey()
+ "hasn't add to WifiConfigManager?");
continue;
}
if (SdkLevel.isAtLeastS()
&& mWifiConfigManager.getConfiguredNetwork(config.getKey()) != null) {
// If a saved profile is available for the same network
mWifiMetrics.addSuggestionExistsForSavedNetwork(
config.getKey());
}
if (!wCmConfiguredNetwork.getNetworkSelectionStatus().isNetworkEnabled()
&& !mWifiConfigManager.tryEnableNetwork(wCmConfiguredNetwork.networkId)) {
mLocalLog.log("Ignoring blocklisted network: "
+ toNetworkString(wCmConfiguredNetwork));
continue;
}
if (!isNetworkAvailableToAutoConnect(wCmConfiguredNetwork, untrustedNetworkAllowed,
oemPaidNetworkAllowed, oemPrivateNetworkAllowed)) {
continue;
}
matchMetaInfo.put(ewns, wCmConfiguredNetwork, scanDetail);
}
}
}
private boolean isNetworkAvailableToAutoConnect(WifiConfiguration config,
boolean untrustedNetworkAllowed, boolean oemPaidNetworkAllowed,
boolean oemPrivateNetworkAllowed) {
// Ignore insecure enterprise config.
if (config.isEnterprise() && config.enterpriseConfig.isEapMethodServerCertUsed()
&& !config.enterpriseConfig
.isMandatoryParameterSetForServerCertValidation()) {
mLocalLog.log("Ignoring insecure enterprise network: " + config);
return false;
}
// If oem paid network is not allowed, ignore oem paid suggestion.
if (shouldIgnoreBasedOnChecksForTrustedOrOemPaidOrOemPrivate(config,
untrustedNetworkAllowed, oemPaidNetworkAllowed, oemPrivateNetworkAllowed)) {
mLocalLog.log("Ignoring network since it needs corresponding NetworkRequest: "
+ toNetworkString(config));
return false;
}
if (!isCarrierNetworkAvailableToAutoConnect(config)) {
return false;
}
String network = config.isPasspoint() ? config.FQDN : config.SSID;
if (mWifiConfigManager.isNonCarrierMergedNetworkTemporarilyDisabled(config)) {
mLocalLog.log("Ignoring non-carrier-merged network: " + network);
return false;
}
if (mWifiConfigManager.isNetworkTemporarilyDisabledByUser(network)) {
mLocalLog.log("Ignoring user disabled network: " + network);
return false;
}
return config.allowAutojoin;
}
private boolean isCarrierNetworkAvailableToAutoConnect(WifiConfiguration config) {
if (config.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
return true;
}
if (!mWifiCarrierInfoManager.isSimReady(config.subscriptionId)) {
mLocalLog.log("SIM is not present for subId: " + config.subscriptionId);
return false;
}
if (WifiConfiguration.isMetered(config, null)
&& mWifiCarrierInfoManager.isCarrierNetworkFromNonDefaultDataSim(config)) {
mLocalLog.log("Ignoring carrier network from non default data SIM, network: "
+ toNetworkString(config));
return false;
}
if (!mWifiCarrierInfoManager
.isCarrierNetworkOffloadEnabled(config.subscriptionId, config.carrierMerged)) {
mLocalLog.log("Carrier offload is disabled for "
+ (config.carrierMerged ? "merged" : "unmerged")
+ " network from subId: " + config.subscriptionId);
return false;
}
return isSimBasedNetworkAvailableToAutoConnect(config);
}
private boolean isSimBasedNetworkAvailableToAutoConnect(WifiConfiguration config) {
if (config.enterpriseConfig == null
|| !config.enterpriseConfig.isAuthenticationSimBased()) {
return true;
}
int subId = config.subscriptionId;
if (mWifiCarrierInfoManager.requiresImsiEncryption(subId)
&& !mWifiCarrierInfoManager.isImsiEncryptionInfoAvailable(subId)) {
mLocalLog.log("Ignoring SIM based network IMSI encryption info not Available, subId: "
+ subId);
return false;
}
return true;
}
@Override
public @NominatorId int getId() {
return NOMINATOR_ID_SUGGESTION;
}
@Override
public String getName() {
return TAG;
}
// Container classes to handle book-keeping while we're iterating through the scan list.
private class PerNetworkSuggestionMatchMetaInfo {
public final ExtendedWifiNetworkSuggestion extWifiNetworkSuggestion;
public final ScanDetail matchingScanDetail;
public WifiConfiguration wCmConfiguredNetwork; // Added to WifiConfigManager.
PerNetworkSuggestionMatchMetaInfo(
@NonNull ExtendedWifiNetworkSuggestion extWifiNetworkSuggestion,
@Nullable WifiConfiguration wCmConfiguredNetwork,
@NonNull ScanDetail matchingScanDetail) {
this.extWifiNetworkSuggestion = extWifiNetworkSuggestion;
this.wCmConfiguredNetwork = wCmConfiguredNetwork;
this.matchingScanDetail = matchingScanDetail;
}
}
private class PerAppMatchMetaInfo {
public final List<PerNetworkSuggestionMatchMetaInfo> networkInfos = new ArrayList<>();
/**
* Add the network suggestion & associated info to this package meta info.
*/
public void put(ExtendedWifiNetworkSuggestion wifiNetworkSuggestion,
WifiConfiguration matchingWifiConfiguration,
ScanDetail matchingScanDetail) {
networkInfos.add(new PerNetworkSuggestionMatchMetaInfo(
wifiNetworkSuggestion, matchingWifiConfiguration, matchingScanDetail));
}
/**
* Pick the highest priority networks among the current match info candidates for this
* app.
*/
public List<PerNetworkSuggestionMatchMetaInfo> getHighestPriorityNetworks() {
// Partition the list to a map of network suggestions keyed in by the priorities.
// There can be multiple networks with the same priority, hence a list in the value.
Map<Integer, List<PerNetworkSuggestionMatchMetaInfo>> matchedNetworkInfosPerPriority =
networkInfos.stream()
.collect(Collectors.toMap(
e -> e.extWifiNetworkSuggestion.wns.wifiConfiguration.priority,
e -> Arrays.asList(e),
(v1, v2) -> { // concatenate networks with the same priority.
List<PerNetworkSuggestionMatchMetaInfo> concatList =
new ArrayList<>(v1);
concatList.addAll(v2);
return concatList;
}));
if (matchedNetworkInfosPerPriority.isEmpty()) { // should never happen.
Log.wtf(TAG, "Unexepectedly got empty");
return List.of();
}
// Return the list associated with the highest priority value.
return matchedNetworkInfosPerPriority.get(Collections.max(
matchedNetworkInfosPerPriority.keySet()));
}
}
private class MatchMetaInfo {
private SparseArray<PerAppMatchMetaInfo> mAppInfos = new SparseArray<>();
/**
* Add all the network suggestion & associated info.
*/
public void put(ExtendedWifiNetworkSuggestion wifiNetworkSuggestion,
WifiConfiguration wCmConfiguredNetwork,
ScanDetail matchingScanDetail) {
// Put the suggestion into buckets for each app to allow sorting based on
// priorities set by app.
int key = Objects.hash(wifiNetworkSuggestion.perAppInfo.packageName,
wifiNetworkSuggestion.wns.priorityGroup);
PerAppMatchMetaInfo appInfo = mAppInfos.get(key, new PerAppMatchMetaInfo());
appInfo.put(wifiNetworkSuggestion, wCmConfiguredNetwork, matchingScanDetail);
mAppInfos.put(key, appInfo);
}
/**
* Are there any matched candidates?
*/
public boolean isEmpty() {
return mAppInfos.size() == 0;
}
/**
* Run through all connectable suggestions and nominate highest priority networks from each
* app as candidates to {@link WifiNetworkSelector}.
*/
public void findConnectableNetworksAndHighestPriority(
@NonNull OnConnectableListener onConnectableListener) {
for (int i = 0; i < mAppInfos.size(); i++) {
List<PerNetworkSuggestionMatchMetaInfo> matchedNetworkInfos =
mAppInfos.valueAt(i).getHighestPriorityNetworks();
for (PerNetworkSuggestionMatchMetaInfo matchedNetworkInfo : matchedNetworkInfos) {
mLocalLog.log(String.format("network suggestion candidate %s nominated",
toNetworkString(matchedNetworkInfo.wCmConfiguredNetwork)));
onConnectableListener.onConnectable(
matchedNetworkInfo.matchingScanDetail,
matchedNetworkInfo.wCmConfiguredNetwork);
}
}
}
}
}