| /* |
| * 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); |
| } |
| } |
| } |
| } |
| |
| } |