1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.wifi; 18 19 import android.content.Context; 20 import android.net.wifi.ScanResult; 21 import android.net.wifi.WifiConfiguration; 22 import android.util.LocalLog; 23 import android.util.Pair; 24 25 import com.android.internal.R; 26 27 import java.util.ArrayList; 28 import java.util.Arrays; 29 import java.util.List; 30 31 /** 32 * This class is the WifiNetworkSelector.NetworkEvaluator implementation for 33 * saved networks. 34 */ 35 public class SavedNetworkEvaluator implements WifiNetworkSelector.NetworkEvaluator { 36 private static final String NAME = "SavedNetworkEvaluator"; 37 private final WifiConfigManager mWifiConfigManager; 38 private final Clock mClock; 39 private final LocalLog mLocalLog; 40 private final WifiConnectivityHelper mConnectivityHelper; 41 private final int mRssiScoreSlope; 42 private final int mRssiScoreOffset; 43 private final int mSameBssidAward; 44 private final int mSameNetworkAward; 45 private final int mBand5GHzAward; 46 private final int mLastSelectionAward; 47 private final int mSecurityAward; 48 private final int mThresholdSaturatedRssi24; 49 private final int mThresholdSaturatedRssi5; 50 51 SavedNetworkEvaluator(final Context context, WifiConfigManager configManager, Clock clock, 52 LocalLog localLog, WifiConnectivityHelper connectivityHelper) { 53 mWifiConfigManager = configManager; 54 mClock = clock; 55 mLocalLog = localLog; 56 mConnectivityHelper = connectivityHelper; 57 58 mRssiScoreSlope = context.getResources().getInteger( 59 R.integer.config_wifi_framework_RSSI_SCORE_SLOPE); 60 mRssiScoreOffset = context.getResources().getInteger( 61 R.integer.config_wifi_framework_RSSI_SCORE_OFFSET); 62 mSameBssidAward = context.getResources().getInteger( 63 R.integer.config_wifi_framework_SAME_BSSID_AWARD); 64 mSameNetworkAward = context.getResources().getInteger( 65 R.integer.config_wifi_framework_current_network_boost); 66 mLastSelectionAward = context.getResources().getInteger( 67 R.integer.config_wifi_framework_LAST_SELECTION_AWARD); 68 mSecurityAward = context.getResources().getInteger( 69 R.integer.config_wifi_framework_SECURITY_AWARD); 70 mBand5GHzAward = context.getResources().getInteger( 71 R.integer.config_wifi_framework_5GHz_preference_boost_factor); 72 mThresholdSaturatedRssi24 = context.getResources().getInteger( 73 R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz); 74 mThresholdSaturatedRssi5 = context.getResources().getInteger( 75 R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz); 76 } 77 78 private void localLog(String log) { 79 mLocalLog.log(log); 80 } 81 82 /** 83 * Get the evaluator name. 84 */ 85 public String getName() { 86 return NAME; 87 } 88 89 /** 90 * Update all the saved networks' selection status 91 */ 92 private void updateSavedNetworkSelectionStatus() { 93 List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks(); 94 if (savedNetworks.size() == 0) { 95 localLog("No saved networks."); 96 return; 97 } 98 99 StringBuffer sbuf = new StringBuffer(); 100 for (WifiConfiguration network : savedNetworks) { 101 /** 102 * Ignore Passpoint networks. Passpoint networks are also considered as "saved" 103 * network, but without being persisted to the storage. They are managed 104 * by {@link PasspointNetworkEvaluator}. 105 */ 106 if (network.isPasspoint()) { 107 continue; 108 } 109 110 // If a configuration is temporarily disabled, re-enable it before trying 111 // to connect to it. 112 mWifiConfigManager.tryEnableNetwork(network.networkId); 113 114 //TODO(b/30928589): Enable "permanently" disabled networks if we are in DISCONNECTED 115 // state. 116 117 // Clear the cached candidate, score and seen. 118 mWifiConfigManager.clearNetworkCandidateScanResult(network.networkId); 119 120 // Log disabled network. 121 WifiConfiguration.NetworkSelectionStatus status = network.getNetworkSelectionStatus(); 122 if (!status.isNetworkEnabled()) { 123 sbuf.append(" ").append(WifiNetworkSelector.toNetworkString(network)).append(" "); 124 for (int index = WifiConfiguration.NetworkSelectionStatus 125 .NETWORK_SELECTION_DISABLED_STARTING_INDEX; 126 index < WifiConfiguration.NetworkSelectionStatus 127 .NETWORK_SELECTION_DISABLED_MAX; 128 index++) { 129 int count = status.getDisableReasonCounter(index); 130 // Here we log the reason as long as its count is greater than zero. The 131 // network may not be disabled because of this particular reason. Logging 132 // this information anyway to help understand what happened to the network. 133 if (count > 0) { 134 sbuf.append("reason=") 135 .append(WifiConfiguration.NetworkSelectionStatus 136 .getNetworkDisableReasonString(index)) 137 .append(", count=").append(count).append("; "); 138 } 139 } 140 sbuf.append("\n"); 141 } 142 } 143 144 if (sbuf.length() > 0) { 145 localLog("Disabled saved networks:"); 146 localLog(sbuf.toString()); 147 } 148 } 149 150 /** 151 * Update the evaluator. 152 */ 153 public void update(List<ScanDetail> scanDetails) { 154 updateSavedNetworkSelectionStatus(); 155 } 156 157 private int calculateBssidScore(ScanResult scanResult, WifiConfiguration network, 158 WifiConfiguration currentNetwork, String currentBssid, 159 StringBuffer sbuf) { 160 int score = 0; 161 boolean is5GHz = scanResult.is5GHz(); 162 163 sbuf.append("[ ").append(scanResult.SSID).append(" ").append(scanResult.BSSID) 164 .append(" RSSI:").append(scanResult.level).append(" ] "); 165 // Calculate the RSSI score. 166 int rssiSaturationThreshold = is5GHz ? mThresholdSaturatedRssi5 : mThresholdSaturatedRssi24; 167 int rssi = scanResult.level < rssiSaturationThreshold ? scanResult.level 168 : rssiSaturationThreshold; 169 score += (rssi + mRssiScoreOffset) * mRssiScoreSlope; 170 sbuf.append(" RSSI score: ").append(score).append(","); 171 172 // 5GHz band bonus. 173 if (is5GHz) { 174 score += mBand5GHzAward; 175 sbuf.append(" 5GHz bonus: ").append(mBand5GHzAward).append(","); 176 } 177 178 // Last user selection award. 179 int lastUserSelectedNetworkId = mWifiConfigManager.getLastSelectedNetwork(); 180 if (lastUserSelectedNetworkId != WifiConfiguration.INVALID_NETWORK_ID 181 && lastUserSelectedNetworkId == network.networkId) { 182 long timeDifference = mClock.getElapsedSinceBootMillis() 183 - mWifiConfigManager.getLastSelectedTimeStamp(); 184 if (timeDifference > 0) { 185 int bonus = mLastSelectionAward - (int) (timeDifference / 1000 / 60); 186 score += bonus > 0 ? bonus : 0; 187 sbuf.append(" User selection ").append(timeDifference / 1000 / 60) 188 .append(" minutes ago, bonus: ").append(bonus).append(","); 189 } 190 } 191 192 // Same network award. 193 if (currentNetwork != null 194 && (network.networkId == currentNetwork.networkId 195 //TODO(b/36788683): re-enable linked configuration check 196 /* || network.isLinked(currentNetwork) */)) { 197 score += mSameNetworkAward; 198 sbuf.append(" Same network bonus: ").append(mSameNetworkAward).append(","); 199 200 // When firmware roaming is supported, equivalent BSSIDs (the ones under the 201 // same network as the currently connected one) get the same BSSID award. 202 if (mConnectivityHelper.isFirmwareRoamingSupported() 203 && currentBssid != null && !currentBssid.equals(scanResult.BSSID)) { 204 score += mSameBssidAward; 205 sbuf.append(" Equivalent BSSID bonus: ").append(mSameBssidAward).append(","); 206 } 207 } 208 209 // Same BSSID award. 210 if (currentBssid != null && currentBssid.equals(scanResult.BSSID)) { 211 score += mSameBssidAward; 212 sbuf.append(" Same BSSID bonus: ").append(mSameBssidAward).append(","); 213 } 214 215 // Security award. 216 if (!WifiConfigurationUtil.isConfigForOpenNetwork(network)) { 217 score += mSecurityAward; 218 sbuf.append(" Secure network bonus: ").append(mSecurityAward).append(","); 219 } 220 221 sbuf.append(" ## Total score: ").append(score).append("\n"); 222 223 return score; 224 } 225 226 /** 227 * Evaluate all the networks from the scan results and return 228 * the WifiConfiguration of the network chosen for connection. 229 * 230 * @return configuration of the chosen network; 231 * null if no network in this category is available. 232 */ 233 public WifiConfiguration evaluateNetworks(List<ScanDetail> scanDetails, 234 WifiConfiguration currentNetwork, String currentBssid, boolean connected, 235 boolean untrustedNetworkAllowed, 236 List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks) { 237 int highestScore = Integer.MIN_VALUE; 238 ScanResult scanResultCandidate = null; 239 WifiConfiguration candidate = null; 240 StringBuffer scoreHistory = new StringBuffer(); 241 242 for (ScanDetail scanDetail : scanDetails) { 243 ScanResult scanResult = scanDetail.getScanResult(); 244 int highestScoreOfScanResult = Integer.MIN_VALUE; 245 int candidateIdOfScanResult = WifiConfiguration.INVALID_NETWORK_ID; 246 247 // One ScanResult can be associated with more than one networks, hence we calculate all 248 // the scores and use the highest one as the ScanResult's score. 249 List<WifiConfiguration> associatedConfigurations = null; 250 WifiConfiguration associatedConfiguration = 251 mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail); 252 253 if (associatedConfiguration == null) { 254 continue; 255 } else { 256 associatedConfigurations = 257 new ArrayList<>(Arrays.asList(associatedConfiguration)); 258 } 259 260 for (WifiConfiguration network : associatedConfigurations) { 261 /** 262 * Ignore Passpoint and Ephemeral networks. They are configured networks, 263 * but without being persisted to the storage. They are evaluated by 264 * {@link PasspointNetworkEvaluator} and {@link ScoredNetworkEvaluator} 265 * respectively. 266 */ 267 if (network.isPasspoint() || network.isEphemeral()) { 268 continue; 269 } 270 271 WifiConfiguration.NetworkSelectionStatus status = 272 network.getNetworkSelectionStatus(); 273 status.setSeenInLastQualifiedNetworkSelection(true); 274 275 if (!status.isNetworkEnabled()) { 276 continue; 277 } else if (network.BSSID != null && !network.BSSID.equals("any") 278 && !network.BSSID.equals(scanResult.BSSID)) { 279 // App has specified the only BSSID to connect for this 280 // configuration. So only the matching ScanResult can be a candidate. 281 localLog("Network " + WifiNetworkSelector.toNetworkString(network) 282 + " has specified BSSID " + network.BSSID + ". Skip " 283 + scanResult.BSSID); 284 continue; 285 } 286 287 int score = calculateBssidScore(scanResult, network, currentNetwork, currentBssid, 288 scoreHistory); 289 290 // Set candidate ScanResult for all saved networks to ensure that users can 291 // override network selection. See WifiNetworkSelector#setUserConnectChoice. 292 // TODO(b/36067705): consider alternative designs to push filtering/selecting of 293 // user connect choice networks to RecommendedNetworkEvaluator. 294 if (score > status.getCandidateScore() || (score == status.getCandidateScore() 295 && status.getCandidate() != null 296 && scanResult.level > status.getCandidate().level)) { 297 mWifiConfigManager.setNetworkCandidateScanResult( 298 network.networkId, scanResult, score); 299 } 300 301 // If the network is marked to use external scores, or is an open network with 302 // curate saved open networks enabled, do not consider it for network selection. 303 if (network.useExternalScores) { 304 localLog("Network " + WifiNetworkSelector.toNetworkString(network) 305 + " has external score."); 306 continue; 307 } 308 309 if (score > highestScoreOfScanResult) { 310 highestScoreOfScanResult = score; 311 candidateIdOfScanResult = network.networkId; 312 } 313 } 314 315 if (connectableNetworks != null) { 316 connectableNetworks.add(Pair.create(scanDetail, 317 mWifiConfigManager.getConfiguredNetwork(candidateIdOfScanResult))); 318 } 319 320 if (highestScoreOfScanResult > highestScore 321 || (highestScoreOfScanResult == highestScore 322 && scanResultCandidate != null 323 && scanResult.level > scanResultCandidate.level)) { 324 highestScore = highestScoreOfScanResult; 325 scanResultCandidate = scanResult; 326 mWifiConfigManager.setNetworkCandidateScanResult( 327 candidateIdOfScanResult, scanResultCandidate, highestScore); 328 // Reload the network config with the updated info. 329 candidate = mWifiConfigManager.getConfiguredNetwork(candidateIdOfScanResult); 330 } 331 } 332 333 if (scoreHistory.length() > 0) { 334 localLog("\n" + scoreHistory.toString()); 335 } 336 337 if (scanResultCandidate == null) { 338 localLog("did not see any good candidates."); 339 } 340 return candidate; 341 } 342 } 343