/* * 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 android.content.Context; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.util.LocalLog; import android.util.Pair; import com.android.internal.R; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * This class is the WifiNetworkSelector.NetworkEvaluator implementation for * saved networks. */ public class SavedNetworkEvaluator implements WifiNetworkSelector.NetworkEvaluator { private static final String NAME = "SavedNetworkEvaluator"; private final WifiConfigManager mWifiConfigManager; private final Clock mClock; private final LocalLog mLocalLog; private final WifiConnectivityHelper mConnectivityHelper; private final int mRssiScoreSlope; private final int mRssiScoreOffset; private final int mSameBssidAward; private final int mSameNetworkAward; private final int mBand5GHzAward; private final int mLastSelectionAward; private final int mSecurityAward; private final int mThresholdSaturatedRssi24; private final int mThresholdSaturatedRssi5; SavedNetworkEvaluator(final Context context, WifiConfigManager configManager, Clock clock, LocalLog localLog, WifiConnectivityHelper connectivityHelper) { mWifiConfigManager = configManager; mClock = clock; mLocalLog = localLog; mConnectivityHelper = connectivityHelper; mRssiScoreSlope = context.getResources().getInteger( R.integer.config_wifi_framework_RSSI_SCORE_SLOPE); mRssiScoreOffset = context.getResources().getInteger( R.integer.config_wifi_framework_RSSI_SCORE_OFFSET); mSameBssidAward = context.getResources().getInteger( R.integer.config_wifi_framework_SAME_BSSID_AWARD); mSameNetworkAward = context.getResources().getInteger( R.integer.config_wifi_framework_current_network_boost); mLastSelectionAward = context.getResources().getInteger( R.integer.config_wifi_framework_LAST_SELECTION_AWARD); mSecurityAward = context.getResources().getInteger( R.integer.config_wifi_framework_SECURITY_AWARD); mBand5GHzAward = context.getResources().getInteger( R.integer.config_wifi_framework_5GHz_preference_boost_factor); mThresholdSaturatedRssi24 = context.getResources().getInteger( R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz); mThresholdSaturatedRssi5 = context.getResources().getInteger( R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz); } private void localLog(String log) { mLocalLog.log(log); } /** * Get the evaluator name. */ public String getName() { return NAME; } /** * Update all the saved networks' selection status */ private void updateSavedNetworkSelectionStatus() { List savedNetworks = mWifiConfigManager.getSavedNetworks(); if (savedNetworks.size() == 0) { localLog("No saved networks."); return; } StringBuffer sbuf = new StringBuffer(); for (WifiConfiguration network : savedNetworks) { /** * Ignore Passpoint networks. Passpoint networks are also considered as "saved" * network, but without being persisted to the storage. They are managed * by {@link PasspointNetworkEvaluator}. */ if (network.isPasspoint()) { continue; } // If a configuration is temporarily disabled, re-enable it before trying // to connect to it. mWifiConfigManager.tryEnableNetwork(network.networkId); //TODO(b/30928589): Enable "permanently" disabled networks if we are in DISCONNECTED // state. // Clear the cached candidate, score and seen. mWifiConfigManager.clearNetworkCandidateScanResult(network.networkId); // Log disabled network. WifiConfiguration.NetworkSelectionStatus status = network.getNetworkSelectionStatus(); if (!status.isNetworkEnabled()) { sbuf.append(" ").append(WifiNetworkSelector.toNetworkString(network)).append(" "); for (int index = WifiConfiguration.NetworkSelectionStatus .NETWORK_SELECTION_DISABLED_STARTING_INDEX; index < WifiConfiguration.NetworkSelectionStatus .NETWORK_SELECTION_DISABLED_MAX; index++) { int count = status.getDisableReasonCounter(index); // Here we log the reason as long as its count is greater than zero. The // network may not be disabled because of this particular reason. Logging // this information anyway to help understand what happened to the network. if (count > 0) { sbuf.append("reason=") .append(WifiConfiguration.NetworkSelectionStatus .getNetworkDisableReasonString(index)) .append(", count=").append(count).append("; "); } } sbuf.append("\n"); } } if (sbuf.length() > 0) { localLog("Disabled saved networks:"); localLog(sbuf.toString()); } } /** * Update the evaluator. */ public void update(List scanDetails) { updateSavedNetworkSelectionStatus(); } private int calculateBssidScore(ScanResult scanResult, WifiConfiguration network, WifiConfiguration currentNetwork, String currentBssid, StringBuffer sbuf) { int score = 0; boolean is5GHz = scanResult.is5GHz(); sbuf.append("[ ").append(scanResult.SSID).append(" ").append(scanResult.BSSID) .append(" RSSI:").append(scanResult.level).append(" ] "); // Calculate the RSSI score. int rssiSaturationThreshold = is5GHz ? mThresholdSaturatedRssi5 : mThresholdSaturatedRssi24; int rssi = scanResult.level < rssiSaturationThreshold ? scanResult.level : rssiSaturationThreshold; score += (rssi + mRssiScoreOffset) * mRssiScoreSlope; sbuf.append(" RSSI score: ").append(score).append(","); // 5GHz band bonus. if (is5GHz) { score += mBand5GHzAward; sbuf.append(" 5GHz bonus: ").append(mBand5GHzAward).append(","); } // Last user selection award. int lastUserSelectedNetworkId = mWifiConfigManager.getLastSelectedNetwork(); if (lastUserSelectedNetworkId != WifiConfiguration.INVALID_NETWORK_ID && lastUserSelectedNetworkId == network.networkId) { long timeDifference = mClock.getElapsedSinceBootMillis() - mWifiConfigManager.getLastSelectedTimeStamp(); if (timeDifference > 0) { int bonus = mLastSelectionAward - (int) (timeDifference / 1000 / 60); score += bonus > 0 ? bonus : 0; sbuf.append(" User selection ").append(timeDifference / 1000 / 60) .append(" minutes ago, bonus: ").append(bonus).append(","); } } // Same network award. if (currentNetwork != null && (network.networkId == currentNetwork.networkId //TODO(b/36788683): re-enable linked configuration check /* || network.isLinked(currentNetwork) */)) { score += mSameNetworkAward; sbuf.append(" Same network bonus: ").append(mSameNetworkAward).append(","); // When firmware roaming is supported, equivalent BSSIDs (the ones under the // same network as the currently connected one) get the same BSSID award. if (mConnectivityHelper.isFirmwareRoamingSupported() && currentBssid != null && !currentBssid.equals(scanResult.BSSID)) { score += mSameBssidAward; sbuf.append(" Equivalent BSSID bonus: ").append(mSameBssidAward).append(","); } } // Same BSSID award. if (currentBssid != null && currentBssid.equals(scanResult.BSSID)) { score += mSameBssidAward; sbuf.append(" Same BSSID bonus: ").append(mSameBssidAward).append(","); } // Security award. if (!WifiConfigurationUtil.isConfigForOpenNetwork(network)) { score += mSecurityAward; sbuf.append(" Secure network bonus: ").append(mSecurityAward).append(","); } sbuf.append(" ## Total score: ").append(score).append("\n"); return score; } /** * Evaluate all the networks from the scan results and return * the WifiConfiguration of the network chosen for connection. * * @return configuration of the chosen network; * null if no network in this category is available. */ public WifiConfiguration evaluateNetworks(List scanDetails, WifiConfiguration currentNetwork, String currentBssid, boolean connected, boolean untrustedNetworkAllowed, List> connectableNetworks) { int highestScore = Integer.MIN_VALUE; ScanResult scanResultCandidate = null; WifiConfiguration candidate = null; StringBuffer scoreHistory = new StringBuffer(); for (ScanDetail scanDetail : scanDetails) { ScanResult scanResult = scanDetail.getScanResult(); int highestScoreOfScanResult = Integer.MIN_VALUE; int candidateIdOfScanResult = WifiConfiguration.INVALID_NETWORK_ID; // One ScanResult can be associated with more than one networks, hence we calculate all // the scores and use the highest one as the ScanResult's score. List associatedConfigurations = null; WifiConfiguration associatedConfiguration = mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail); if (associatedConfiguration == null) { continue; } else { associatedConfigurations = new ArrayList<>(Arrays.asList(associatedConfiguration)); } for (WifiConfiguration network : associatedConfigurations) { /** * Ignore Passpoint and Ephemeral networks. They are configured networks, * but without being persisted to the storage. They are evaluated by * {@link PasspointNetworkEvaluator} and {@link ScoredNetworkEvaluator} * respectively. */ if (network.isPasspoint() || network.isEphemeral()) { continue; } WifiConfiguration.NetworkSelectionStatus status = network.getNetworkSelectionStatus(); status.setSeenInLastQualifiedNetworkSelection(true); if (!status.isNetworkEnabled()) { continue; } else if (network.BSSID != null && !network.BSSID.equals("any") && !network.BSSID.equals(scanResult.BSSID)) { // App has specified the only BSSID to connect for this // configuration. So only the matching ScanResult can be a candidate. localLog("Network " + WifiNetworkSelector.toNetworkString(network) + " has specified BSSID " + network.BSSID + ". Skip " + scanResult.BSSID); continue; } int score = calculateBssidScore(scanResult, network, currentNetwork, currentBssid, scoreHistory); // Set candidate ScanResult for all saved networks to ensure that users can // override network selection. See WifiNetworkSelector#setUserConnectChoice. // TODO(b/36067705): consider alternative designs to push filtering/selecting of // user connect choice networks to RecommendedNetworkEvaluator. if (score > status.getCandidateScore() || (score == status.getCandidateScore() && status.getCandidate() != null && scanResult.level > status.getCandidate().level)) { mWifiConfigManager.setNetworkCandidateScanResult( network.networkId, scanResult, score); } // If the network is marked to use external scores, or is an open network with // curate saved open networks enabled, do not consider it for network selection. if (network.useExternalScores) { localLog("Network " + WifiNetworkSelector.toNetworkString(network) + " has external score."); continue; } if (score > highestScoreOfScanResult) { highestScoreOfScanResult = score; candidateIdOfScanResult = network.networkId; } } if (connectableNetworks != null) { connectableNetworks.add(Pair.create(scanDetail, mWifiConfigManager.getConfiguredNetwork(candidateIdOfScanResult))); } if (highestScoreOfScanResult > highestScore || (highestScoreOfScanResult == highestScore && scanResultCandidate != null && scanResult.level > scanResultCandidate.level)) { highestScore = highestScoreOfScanResult; scanResultCandidate = scanResult; mWifiConfigManager.setNetworkCandidateScanResult( candidateIdOfScanResult, scanResultCandidate, highestScore); // Reload the network config with the updated info. candidate = mWifiConfigManager.getConfiguredNetwork(candidateIdOfScanResult); } } if (scoreHistory.length() > 0) { localLog("\n" + scoreHistory.toString()); } if (scanResultCandidate == null) { localLog("did not see any good candidates."); } return candidate; } }