blob: d2808dee76c105859adc97dca1b3283e5f14aba6 [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.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 com.android.server.wifi.util.ScanResultUtil;
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;
NetworkSuggestionNominator(WifiNetworkSuggestionsManager networkSuggestionsManager,
WifiConfigManager wifiConfigManager, PasspointNetworkNominateHelper nominateHelper,
LocalLog localLog) {
mWifiNetworkSuggestionsManager = networkSuggestionsManager;
mWifiConfigManager = wifiConfigManager;
mPasspointNetworkNominateHelper = nominateHelper;
mLocalLog = localLog;
}
@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) {
MatchMetaInfo matchMetaInfo = new MatchMetaInfo();
Set<ExtendedWifiNetworkSuggestion> autoJoinDisabledSuggestions = new HashSet<>();
List<ScanDetail> filteredScanDetails = scanDetails.stream().filter(scanDetail ->
!mWifiConfigManager.wasEphemeralNetworkDeleted(
ScanResultUtil.createQuotedSSID(scanDetail.getScanResult().SSID)))
.collect(Collectors.toList());
if (filteredScanDetails.isEmpty()) {
return;
}
findMatchedPasspointSuggestionNetworks(filteredScanDetails, matchMetaInfo);
findMatchedSuggestionNetworks(filteredScanDetails, matchMetaInfo,
autoJoinDisabledSuggestions);
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) {
List<Pair<ScanDetail, WifiConfiguration>> candidates =
mPasspointNetworkNominateHelper.getPasspointNetworkCandidates(scanDetails, true);
for (Pair<ScanDetail, WifiConfiguration> candidate : candidates) {
Set<ExtendedWifiNetworkSuggestion> matchingPasspointExtSuggestions =
mWifiNetworkSuggestionsManager
.getNetworkSuggestionsForFqdn(candidate.second.FQDN)
.stream()
.filter(
ens -> ens.isAutoJoinEnabled)
.collect(Collectors.toSet());
if (matchingPasspointExtSuggestions == null
|| matchingPasspointExtSuggestions.isEmpty()) {
continue;
}
matchMetaInfo.putAll(matchingPasspointExtSuggestions,
candidate.second, candidate.first);
}
}
private void findMatchedSuggestionNetworks(List<ScanDetail> scanDetails,
MatchMetaInfo matchMetaInfo,
Set<ExtendedWifiNetworkSuggestion> autoJoinDisabledSuggestions) {
for (ScanDetail scanDetail : scanDetails) {
Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestions =
mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(scanDetail);
if (matchingExtNetworkSuggestions == null || matchingExtNetworkSuggestions.isEmpty()) {
continue;
}
Set<ExtendedWifiNetworkSuggestion> autojoinEnableSuggestions =
matchingExtNetworkSuggestions.stream()
.filter(ewns -> ewns.isAutoJoinEnabled)
.collect(Collectors.toSet());
autoJoinDisabledSuggestions.addAll(
matchingExtNetworkSuggestions.stream()
.filter(ewns -> !ewns.isAutoJoinEnabled)
.collect(Collectors.toSet()));
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.
ExtendedWifiNetworkSuggestion matchingExtNetworkSuggestion =
autojoinEnableSuggestions.stream().findAny().get();
// Check if we already have a network with the same credentials in WifiConfigManager
// database.
WifiConfiguration wCmConfiguredNetwork =
mWifiConfigManager.getConfiguredNetwork(
matchingExtNetworkSuggestion.wns.wifiConfiguration.getKey());
if (wCmConfiguredNetwork != null) {
// If existing network is not from suggestion, ignore.
if (!(wCmConfiguredNetwork.fromWifiNetworkSuggestion
&& wCmConfiguredNetwork.allowAutojoin)) {
continue;
}
// Update the WifiConfigManager with the latest WifiConfig
WifiConfiguration config = createConfigForAddingToWifiConfigManager(
matchingExtNetworkSuggestion.wns.wifiConfiguration, true);
NetworkUpdateResult result = mWifiConfigManager.addOrUpdateNetwork(
config,
matchingExtNetworkSuggestion.perAppInfo.uid,
matchingExtNetworkSuggestion.perAppInfo.packageName);
if (result.isSuccess()) {
wCmConfiguredNetwork = mWifiConfigManager.getConfiguredNetwork(
result.getNetworkId());
}
// If the network is currently blacklisted, ignore.
if (!wCmConfiguredNetwork.getNetworkSelectionStatus().isNetworkEnabled()
&& !mWifiConfigManager.tryEnableNetwork(wCmConfiguredNetwork.networkId)) {
mLocalLog.log("Ignoring blacklisted network: "
+ WifiNetworkSelector.toNetworkString(wCmConfiguredNetwork));
continue;
}
}
matchMetaInfo.putAll(matchingExtNetworkSuggestions, wCmConfiguredNetwork, scanDetail);
}
}
// 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 =
createConfigForAddingToWifiConfigManager(ewns.wns.wifiConfiguration, false);
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;
}
WifiConfiguration currentWCmConfiguredNetwork =
mWifiConfigManager.getConfiguredNetwork(result.netId);
// Try to enable network selection
if (wCmConfiguredNetwork == null) {
if (!mWifiConfigManager.updateNetworkSelectionStatus(result.getNetworkId(),
WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE)) {
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 WifiConfiguration config, int uid, @NonNull String packageName) {
WifiConfiguration wifiConfiguration =
createConfigForAddingToWifiConfigManager(config, true);
NetworkUpdateResult result =
mWifiConfigManager.addOrUpdateNetwork(wifiConfiguration, uid, packageName);
if (!result.isSuccess()) {
mLocalLog.log("Failed to add network suggestion");
return null;
}
if (!mWifiConfigManager.updateNetworkSelectionStatus(result.getNetworkId(),
WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE)) {
mLocalLog.log("Failed to make network suggestion selectable");
return null;
}
int candidateNetworkId = result.getNetworkId();
return mWifiConfigManager.getConfiguredNetwork(candidateNetworkId);
}
private WifiConfiguration createConfigForAddingToWifiConfigManager(WifiConfiguration config,
boolean allowAutojoin) {
WifiConfiguration wifiConfiguration = new WifiConfiguration(config);
// Mark the network ephemeral because we don't want these persisted by WifiConfigManager.
wifiConfiguration.ephemeral = true;
wifiConfiguration.fromWifiNetworkSuggestion = true;
wifiConfiguration.allowAutojoin = allowAutojoin;
return wifiConfiguration;
}
@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.wns.wifiConfiguration,
matchedNetworkInfo.extWifiNetworkSuggestion.perAppInfo.uid,
matchedNetworkInfo.extWifiNetworkSuggestion.perAppInfo.packageName);
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);
}
}
}
}
}