blob: 31dfc23d889e44777d0fc32bbce4f780ee05373c [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 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 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.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
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;
NetworkSuggestionNominator(WifiNetworkSuggestionsManager networkSuggestionsManager,
WifiConfigManager wifiConfigManager, PasspointNetworkNominateHelper nominateHelper,
LocalLog localLog, WifiCarrierInfoManager wifiCarrierInfoManager) {
mWifiNetworkSuggestionsManager = networkSuggestionsManager;
mWifiConfigManager = wifiConfigManager;
mPasspointNetworkNominateHelper = nominateHelper;
mLocalLog = localLog;
mWifiCarrierInfoManager = wifiCarrierInfoManager;
}
@Override
public void update(List<ScanDetail> scanDetails) {
// TODO(b/115504887): This could be used to re-evaluate any temporary blacklists.
}
@Override
public void nominateNetworks(List<ScanDetail> scanDetails,
WifiConfiguration currentNetwork, String currentBssid, boolean connected,
boolean untrustedNetworkAllowed,
@NonNull OnConnectableListener onConnectableListener) {
if (scanDetails.isEmpty()) {
return;
}
MatchMetaInfo matchMetaInfo = new MatchMetaInfo();
Set<ExtendedWifiNetworkSuggestion> autoJoinDisabledSuggestions = new HashSet<>();
findMatchedPasspointSuggestionNetworks(scanDetails, matchMetaInfo, untrustedNetworkAllowed);
findMatchedSuggestionNetworks(scanDetails, matchMetaInfo,
autoJoinDisabledSuggestions, untrustedNetworkAllowed);
if (matchMetaInfo.isEmpty()) {
mLocalLog.log("did not see any matching auto-join enabled network suggestions.");
} else {
matchMetaInfo.findConnectableNetworksAndHighestPriority(onConnectableListener);
}
addAutojoinDisabledSuggestionToWifiConfigManager(autoJoinDisabledSuggestions);
}
private void findMatchedPasspointSuggestionNetworks(List<ScanDetail> scanDetails,
MatchMetaInfo matchMetaInfo, boolean untrustedNetworkAllowed) {
List<Pair<ScanDetail, WifiConfiguration>> candidates =
mPasspointNetworkNominateHelper.getPasspointNetworkCandidates(scanDetails, true);
for (Pair<ScanDetail, WifiConfiguration> candidate : candidates) {
WifiConfiguration config = candidate.second;
Set<ExtendedWifiNetworkSuggestion> matchingPasspointExtSuggestions =
mWifiNetworkSuggestionsManager
.getNetworkSuggestionsForFqdn(config.FQDN);
if (matchingPasspointExtSuggestions == null
|| matchingPasspointExtSuggestions.isEmpty()) {
mLocalLog.log("Suggestion is missing for passpoint: " + config.FQDN);
continue;
}
if (WifiConfiguration.isMetered(config, null)
&& mWifiCarrierInfoManager.isCarrierNetworkFromNonDefaultDataSim(config)) {
continue;
}
if (!isCarrierNetworkAvailableToAutoConnect(config)) {
continue;
}
// If untrusted network is not allowed, ignore untrusted suggestion.
if (!untrustedNetworkAllowed && !config.trusted) {
continue;
}
Set<ExtendedWifiNetworkSuggestion> autoJoinEnabledExtSuggestions =
matchingPasspointExtSuggestions.stream()
.filter(ewns -> ewns.isAutojoinEnabled)
.collect(Collectors.toSet());
if (autoJoinEnabledExtSuggestions.isEmpty()) {
continue;
}
matchMetaInfo.putAll(autoJoinEnabledExtSuggestions,
config, candidate.first);
}
}
private void findMatchedSuggestionNetworks(List<ScanDetail> scanDetails,
MatchMetaInfo matchMetaInfo,
Set<ExtendedWifiNetworkSuggestion> autoJoinDisabledSuggestions,
boolean untrustedNetworkAllowed) {
for (ScanDetail scanDetail : scanDetails) {
Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestions =
mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(scanDetail);
if (matchingExtNetworkSuggestions == null || matchingExtNetworkSuggestions.isEmpty()) {
continue;
}
Set<ExtendedWifiNetworkSuggestion> autojoinEnableSuggestions = new HashSet<>();
for (ExtendedWifiNetworkSuggestion ewns : matchingExtNetworkSuggestions) {
// Ignore insecure enterprise config.
if (ewns.wns.wifiConfiguration.isEnterprise()
&& ewns.wns.wifiConfiguration.enterpriseConfig.isInsecure()) {
continue;
}
// If untrusted network is not allowed, ignore untrusted suggestion.
WifiConfiguration config = ewns.wns.wifiConfiguration;
if (!untrustedNetworkAllowed && !config.trusted) {
continue;
}
if (WifiConfiguration.isMetered(config, null)
&& mWifiCarrierInfoManager.isCarrierNetworkFromNonDefaultDataSim(config)) {
continue;
}
if (!ewns.isAutojoinEnabled
|| !isCarrierNetworkAvailableToAutoConnect(config)) {
autoJoinDisabledSuggestions.add(ewns);
continue;
}
if (mWifiConfigManager.isNetworkTemporarilyDisabledByUser(config.SSID)) {
mLocalLog.log("Ignoring user disabled SSID: "
+ config.SSID);
autoJoinDisabledSuggestions.add(ewns);
continue;
}
autojoinEnableSuggestions.add(ewns);
}
if (autojoinEnableSuggestions.isEmpty()) {
continue;
}
// All matching suggestions have the same network credentials type. So, use any one of
// them to lookup/add the credentials to WifiConfigManager.
// Note: Apps could provide different credentials (password, ceritificate) for the same
// network, need to handle that in the future.
String configKey = autojoinEnableSuggestions.stream().findAny().get()
.wns.wifiConfiguration.getKey();
// Check if we already have a network with the same credentials in WifiConfigManager
// database.
WifiConfiguration wCmConfiguredNetwork =
mWifiConfigManager.getConfiguredNetwork(configKey);
if (wCmConfiguredNetwork != null) {
// If existing network is not from suggestion, ignore.
if (!(wCmConfiguredNetwork.fromWifiNetworkSuggestion
&& wCmConfiguredNetwork.allowAutojoin)) {
continue;
}
int creatorUid = wCmConfiguredNetwork.creatorUid;
Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestionsFromSamePackage =
autojoinEnableSuggestions.stream()
.filter(ewns -> ewns.perAppInfo.uid == creatorUid)
.collect(Collectors.toSet());
if (matchingExtNetworkSuggestionsFromSamePackage.isEmpty()) {
continue;
}
// If the network is currently blacklisted, ignore.
if (!wCmConfiguredNetwork.getNetworkSelectionStatus().isNetworkEnabled()
&& !mWifiConfigManager.tryEnableNetwork(wCmConfiguredNetwork.networkId)) {
mLocalLog.log("Ignoring blacklisted network: "
+ WifiNetworkSelector.toNetworkString(wCmConfiguredNetwork));
continue;
}
matchingExtNetworkSuggestions = matchingExtNetworkSuggestionsFromSamePackage;
}
matchMetaInfo.putAll(matchingExtNetworkSuggestions, wCmConfiguredNetwork, scanDetail);
}
}
private boolean isCarrierNetworkAvailableToAutoConnect(WifiConfiguration config) {
if (config.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
return true;
}
int subId = mWifiCarrierInfoManager.getBestMatchSubscriptionId(config);
if (!mWifiCarrierInfoManager.isSimPresent(subId)) {
mLocalLog.log("SIM is not present for subId: " + subId);
return false;
}
return isSimBasedNetworkAvailableToAutoConnect(config);
}
private boolean isSimBasedNetworkAvailableToAutoConnect(WifiConfiguration config) {
if (config.enterpriseConfig == null
|| !config.enterpriseConfig.isAuthenticationSimBased()) {
return true;
}
int subId = mWifiCarrierInfoManager.getBestMatchSubscriptionId(config);
if (mWifiCarrierInfoManager.requiresImsiEncryption(subId)
&& !mWifiCarrierInfoManager.isImsiEncryptionInfoAvailable(subId)) {
mLocalLog.log("Ignoring SIM based network IMSI encryption info not Available, "
+ "subId: " + subId);
return false;
}
return true;
}
// Add auto-join disabled suggestions also to WifiConfigManager if the app allows credential
// sharing.This will surface these networks on the UI, to allow the user manually connect to it.
private void addAutojoinDisabledSuggestionToWifiConfigManager(
Set<ExtendedWifiNetworkSuggestion> autoJoinDisabledSuggestions) {
for (ExtendedWifiNetworkSuggestion ewns : autoJoinDisabledSuggestions) {
if (!ewns.wns.isUserAllowedToManuallyConnect) {
continue;
}
WifiConfiguration config = ewns.createInternalWifiConfiguration();
WifiConfiguration wCmConfiguredNetwork =
mWifiConfigManager.getConfiguredNetwork(config.getKey());
NetworkUpdateResult result = mWifiConfigManager.addOrUpdateNetwork(
config, ewns.perAppInfo.uid, ewns.perAppInfo.packageName);
if (!result.isSuccess()) {
mLocalLog.log("Failed to add network suggestion");
continue;
}
mWifiConfigManager.allowAutojoin(result.getNetworkId(), config.allowAutojoin);
WifiConfiguration currentWCmConfiguredNetwork =
mWifiConfigManager.getConfiguredNetwork(result.netId);
// 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 blacklisted network: "
+ WifiNetworkSelector.toNetworkString(wCmConfiguredNetwork));
}
}
}
}
// Add and enable this network to the central database (i.e WifiConfigManager).
// Returns the copy of WifiConfiguration with the allocated network ID filled in.
private WifiConfiguration addCandidateToWifiConfigManager(
@NonNull ExtendedWifiNetworkSuggestion ewns) {
WifiConfiguration wifiConfiguration = ewns.createInternalWifiConfiguration();
NetworkUpdateResult result =
mWifiConfigManager.addOrUpdateNetwork(wifiConfiguration, ewns.perAppInfo.uid,
ewns.perAppInfo.packageName);
if (!result.isSuccess()) {
mLocalLog.log("Failed to add network suggestion");
return null;
}
mWifiConfigManager.allowAutojoin(result.getNetworkId(), wifiConfiguration.allowAutojoin);
if (!mWifiConfigManager.updateNetworkSelectionStatus(result.getNetworkId(),
WifiConfiguration.NetworkSelectionStatus.DISABLED_NONE)) {
mLocalLog.log("Failed to make network suggestion selectable");
return null;
}
int candidateNetworkId = result.getNetworkId();
return mWifiConfigManager.getConfiguredNetwork(candidateNetworkId);
}
@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 Collections.EMPTY_LIST;
}
// Return the list associated with the highest priority value.
return matchedNetworkInfosPerPriority.get(Collections.max(
matchedNetworkInfosPerPriority.keySet()));
}
}
private class MatchMetaInfo {
private Map<String, PerAppMatchMetaInfo> mAppInfos = new HashMap<>();
/**
* Add all the network suggestion & associated info.
*/
public void putAll(Set<ExtendedWifiNetworkSuggestion> wifiNetworkSuggestions,
WifiConfiguration wCmConfiguredNetwork,
ScanDetail matchingScanDetail) {
// Separate the suggestions into buckets for each app to allow sorting based on
// priorities set by app.
for (ExtendedWifiNetworkSuggestion wifiNetworkSuggestion : wifiNetworkSuggestions) {
PerAppMatchMetaInfo appInfo = mAppInfos.computeIfAbsent(
wifiNetworkSuggestion.perAppInfo.packageName,
k -> new PerAppMatchMetaInfo());
appInfo.put(wifiNetworkSuggestion, wCmConfiguredNetwork, matchingScanDetail);
}
}
/**
* Are there any matched candidates?
*/
public boolean isEmpty() {
return mAppInfos.isEmpty();
}
/**
* 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 (PerAppMatchMetaInfo appInfo : mAppInfos.values()) {
List<PerNetworkSuggestionMatchMetaInfo> matchedNetworkInfos =
appInfo.getHighestPriorityNetworks();
for (PerNetworkSuggestionMatchMetaInfo matchedNetworkInfo : matchedNetworkInfos) {
// if the network does not already exist in WifiConfigManager, add now.
if (matchedNetworkInfo.wCmConfiguredNetwork == null) {
matchedNetworkInfo.wCmConfiguredNetwork = addCandidateToWifiConfigManager(
matchedNetworkInfo.extWifiNetworkSuggestion);
if (matchedNetworkInfo.wCmConfiguredNetwork == null) continue;
mLocalLog.log(String.format("network suggestion candidate %s (new)",
WifiNetworkSelector.toNetworkString(
matchedNetworkInfo.wCmConfiguredNetwork)));
} else {
mLocalLog.log(String.format("network suggestion candidate %s (existing)",
WifiNetworkSelector.toNetworkString(
matchedNetworkInfo.wCmConfiguredNetwork)));
}
onConnectableListener.onConnectable(
matchedNetworkInfo.matchingScanDetail,
matchedNetworkInfo.wCmConfiguredNetwork);
}
}
}
}
}