SavedNetworkEvaluator.java revision e4b4b229331da3964671606f18557b2e7f681b45
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 17package com.android.server.wifi; 18 19import android.content.Context; 20import android.database.ContentObserver; 21import android.net.wifi.ScanResult; 22import android.net.wifi.WifiConfiguration; 23import android.os.Handler; 24import android.os.Looper; 25import android.provider.Settings; 26import android.util.LocalLog; 27import android.util.Pair; 28 29import com.android.internal.R; 30import com.android.internal.annotations.VisibleForTesting; 31 32import java.util.ArrayList; 33import java.util.Arrays; 34import java.util.List; 35 36/** 37 * This class is the WifiNetworkSelector.NetworkEvaluator implementation for 38 * saved networks. 39 */ 40public class SavedNetworkEvaluator implements WifiNetworkSelector.NetworkEvaluator { 41 private static final String NAME = "WifiSavedNetworkEvaluator"; 42 private final WifiConfigManager mWifiConfigManager; 43 private final Clock mClock; 44 private final LocalLog mLocalLog; 45 private final int mRssiScoreSlope; 46 private final int mRssiScoreOffset; 47 private final int mSameBssidAward; 48 private final int mSameNetworkAward; 49 private final int mBand5GHzAward; 50 private final int mLastSelectionAward; 51 private final int mPasspointSecurityAward; 52 private final int mSecurityAward; 53 private final int mNoInternetPenalty; 54 private final int mThresholdSaturatedRssi24; 55 @VisibleForTesting final ContentObserver mContentObserver; 56 private boolean mCurateSavedOpenNetworks; 57 58 SavedNetworkEvaluator(final Context context, WifiConfigManager configManager, Clock clock, 59 LocalLog localLog, Looper looper, final FrameworkFacade frameworkFacade) { 60 mWifiConfigManager = configManager; 61 mClock = clock; 62 mLocalLog = localLog; 63 64 mRssiScoreSlope = context.getResources().getInteger( 65 R.integer.config_wifi_framework_RSSI_SCORE_SLOPE); 66 mRssiScoreOffset = context.getResources().getInteger( 67 R.integer.config_wifi_framework_RSSI_SCORE_OFFSET); 68 mSameBssidAward = context.getResources().getInteger( 69 R.integer.config_wifi_framework_SAME_BSSID_AWARD); 70 mSameNetworkAward = context.getResources().getInteger( 71 R.integer.config_wifi_framework_current_network_boost); 72 mLastSelectionAward = context.getResources().getInteger( 73 R.integer.config_wifi_framework_LAST_SELECTION_AWARD); 74 mPasspointSecurityAward = context.getResources().getInteger( 75 R.integer.config_wifi_framework_PASSPOINT_SECURITY_AWARD); 76 mSecurityAward = context.getResources().getInteger( 77 R.integer.config_wifi_framework_SECURITY_AWARD); 78 mBand5GHzAward = context.getResources().getInteger( 79 R.integer.config_wifi_framework_5GHz_preference_boost_factor); 80 mThresholdSaturatedRssi24 = context.getResources().getInteger( 81 R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz); 82 mNoInternetPenalty = (mThresholdSaturatedRssi24 + mRssiScoreOffset) 83 * mRssiScoreSlope + mBand5GHzAward + mSameNetworkAward 84 + mSameBssidAward + mSecurityAward; 85 mContentObserver = new ContentObserver(new Handler(looper)) { 86 @Override 87 public void onChange(boolean selfChange) { 88 boolean networkRecommendationsEnabled = frameworkFacade.getIntegerSetting(context, 89 Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0) == 1; 90 boolean curateSavedOpenNetworks = frameworkFacade.getIntegerSetting(context, 91 Settings.Global.CURATE_SAVED_OPEN_NETWORKS, 0) == 1; 92 mCurateSavedOpenNetworks = networkRecommendationsEnabled && curateSavedOpenNetworks; 93 } 94 }; 95 mContentObserver.onChange(false /* selfChange*/); 96 context.getContentResolver().registerContentObserver( 97 Settings.Global.getUriFor(Settings.Global.CURATE_SAVED_OPEN_NETWORKS), 98 false /* notifyForDescendents */, mContentObserver); 99 context.getContentResolver().registerContentObserver( 100 Settings.Global.getUriFor(Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED), 101 false /* notifyForDescendents */, mContentObserver); 102 } 103 104 private void localLog(String log) { 105 if (mLocalLog != null) { 106 mLocalLog.log(log); 107 } 108 } 109 110 /** 111 * Get the evaluator name. 112 */ 113 public String getName() { 114 return NAME; 115 } 116 117 /** 118 * Update all the saved networks' selection status 119 */ 120 private void updateSavedNetworkSelectionStatus() { 121 List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks(); 122 if (savedNetworks.size() == 0) { 123 localLog("No saved networks."); 124 return; 125 } 126 127 StringBuffer sbuf = new StringBuffer("Saved Networks List: \n"); 128 for (WifiConfiguration network : savedNetworks) { 129 WifiConfiguration.NetworkSelectionStatus status = 130 network.getNetworkSelectionStatus(); 131 132 // If a configuration is temporarily disabled, re-enable it before trying 133 // to connect to it. 134 mWifiConfigManager.tryEnableNetwork(network.networkId); 135 136 //TODO(b/30928589): Enable "permanently" disabled networks if we are in DISCONNECTED 137 // state. 138 139 // Clear the cached candidate, score and seen. 140 mWifiConfigManager.clearNetworkCandidateScanResult(network.networkId); 141 142 sbuf.append(" ").append(WifiNetworkSelector.toNetworkString(network)).append(" ") 143 .append(" User Preferred BSSID: ").append(network.BSSID) 144 .append(" FQDN: ").append(network.FQDN).append(" ") 145 .append(status.getNetworkStatusString()).append(" Disable account: "); 146 for (int index = WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE; 147 index < WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_DISABLED_MAX; 148 index++) { 149 sbuf.append(status.getDisableReasonCounter(index)).append(" "); 150 } 151 sbuf.append("Connect Choice: ").append(status.getConnectChoice()) 152 .append(" set time: ").append(status.getConnectChoiceTimestamp()) 153 .append("\n"); 154 } 155 localLog(sbuf.toString()); 156 } 157 158 /** 159 * Update the evaluator. 160 */ 161 public void update(List<ScanDetail> scanDetails) { 162 updateSavedNetworkSelectionStatus(); 163 } 164 165 private int calculateBssidScore(ScanResult scanResult, WifiConfiguration network, 166 WifiConfiguration currentNetwork, String currentBssid, 167 StringBuffer sbuf) { 168 int score = 0; 169 170 sbuf.append("[ ").append(scanResult).append("] "); 171 // Calculate the RSSI score. 172 int rssi = scanResult.level <= mThresholdSaturatedRssi24 173 ? scanResult.level : mThresholdSaturatedRssi24; 174 score += (rssi + mRssiScoreOffset) * mRssiScoreSlope; 175 sbuf.append(" RSSI score: ").append(score).append(","); 176 177 // 5GHz band bonus. 178 if (scanResult.is5GHz()) { 179 score += mBand5GHzAward; 180 sbuf.append(" 5GHz bonus: ").append(mBand5GHzAward) 181 .append(","); 182 } 183 184 // Last user selection award. 185 int lastUserSelectedNetworkId = mWifiConfigManager.getLastSelectedNetwork(); 186 if (lastUserSelectedNetworkId != WifiConfiguration.INVALID_NETWORK_ID 187 && lastUserSelectedNetworkId == network.networkId) { 188 long timeDifference = mClock.getElapsedSinceBootMillis() 189 - mWifiConfigManager.getLastSelectedTimeStamp(); 190 if (timeDifference > 0) { 191 int bonus = mLastSelectionAward - (int) (timeDifference / 1000 / 60); 192 score += bonus > 0 ? bonus : 0; 193 sbuf.append(" User selected it last time ").append(timeDifference / 1000 / 60) 194 .append(" minutes ago, bonus: ").append(bonus).append(","); 195 } 196 } 197 198 // Same network award. 199 if (currentNetwork != null 200 && (network.networkId == currentNetwork.networkId 201 || network.isLinked(currentNetwork))) { 202 score += mSameNetworkAward; 203 sbuf.append(" Same network the current one bonus: ") 204 .append(mSameNetworkAward).append(","); 205 } 206 207 // Same BSSID award. 208 if (currentBssid != null && currentBssid.equals(scanResult.BSSID)) { 209 score += mSameBssidAward; 210 sbuf.append(" Same BSSID as the current one bonus: ").append(mSameBssidAward) 211 .append(","); 212 } 213 214 // Security award. 215 if (network.isPasspoint()) { 216 score += mPasspointSecurityAward; 217 sbuf.append(" Passpoint bonus: ").append(mPasspointSecurityAward).append(","); 218 } else if (!WifiConfigurationUtil.isConfigForOpenNetwork(network)) { 219 score += mSecurityAward; 220 sbuf.append(" Secure network bonus: ").append(mSecurityAward).append(","); 221 } 222 223 // No internet penalty. 224 if (network.numNoInternetAccessReports > 0 && !network.validatedInternetAccess) { 225 score -= mNoInternetPenalty; 226 sbuf.append(" No internet penalty: -").append(mNoInternetPenalty).append(","); 227 } 228 229 sbuf.append(" ## Total score: ").append(score).append("\n"); 230 231 return score; 232 } 233 234 private WifiConfiguration adjustCandidateWithUserSelection(WifiConfiguration candidate, 235 ScanResult scanResultCandidate) { 236 WifiConfiguration tempConfig = candidate; 237 238 while (tempConfig.getNetworkSelectionStatus().getConnectChoice() != null) { 239 String key = tempConfig.getNetworkSelectionStatus().getConnectChoice(); 240 tempConfig = mWifiConfigManager.getConfiguredNetwork(key); 241 242 if (tempConfig != null) { 243 WifiConfiguration.NetworkSelectionStatus tempStatus = 244 tempConfig.getNetworkSelectionStatus(); 245 if (tempStatus.getCandidate() != null && tempStatus.isNetworkEnabled()) { 246 scanResultCandidate = tempStatus.getCandidate(); 247 candidate = tempConfig; 248 } 249 } else { 250 localLog("Connect choice: " + key + " has no corresponding saved config."); 251 break; 252 } 253 } 254 localLog("After user selection adjustment, the final candidate is:" 255 + WifiNetworkSelector.toNetworkString(candidate) + " : " 256 + scanResultCandidate.BSSID); 257 return candidate; 258 } 259 260 /** 261 * Evaluate all the networks from the scan results and return 262 * the WifiConfiguration of the network chosen for connection. 263 * 264 * @return configuration of the chosen network; 265 * null if no network in this category is available. 266 */ 267 public WifiConfiguration evaluateNetworks(List<ScanDetail> scanDetails, 268 WifiConfiguration currentNetwork, String currentBssid, boolean connected, 269 boolean untrustedNetworkAllowed, 270 List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks) { 271 int highestScore = Integer.MIN_VALUE; 272 ScanResult scanResultCandidate = null; 273 WifiConfiguration candidate = null; 274 StringBuffer scoreHistory = new StringBuffer(); 275 276 for (ScanDetail scanDetail : scanDetails) { 277 ScanResult scanResult = scanDetail.getScanResult(); 278 int highestScoreOfScanResult = Integer.MIN_VALUE; 279 int score; 280 int candidateIdOfScanResult = WifiConfiguration.INVALID_NETWORK_ID; 281 282 // One ScanResult can be associated with more than one networks, hence we calculate all 283 // the scores and use the highest one as the ScanResult's score. 284 // TODO(b/31065385): WifiConfigManager does not support passpoint networks currently. 285 // So this list has just one entry always. 286 List<WifiConfiguration> associatedConfigurations = null; 287 WifiConfiguration associatedConfiguration = 288 mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail); 289 290 if (associatedConfiguration == null) { 291 continue; 292 } else { 293 associatedConfigurations = 294 new ArrayList<>(Arrays.asList(associatedConfiguration)); 295 } 296 297 for (WifiConfiguration network : associatedConfigurations) { 298 WifiConfiguration.NetworkSelectionStatus status = 299 network.getNetworkSelectionStatus(); 300 status.setSeenInLastQualifiedNetworkSelection(true); 301 302 if (!status.isNetworkEnabled()) { 303 continue; 304 } else if (network.BSSID != null && !network.BSSID.equals("any") 305 && !network.BSSID.equals(scanResult.BSSID)) { 306 // App has specified the only BSSID to connect for this 307 // configuration. So only the matching ScanResult can be a candidate. 308 localLog("Network " + WifiNetworkSelector.toNetworkString(network) 309 + " has specified BSSID " + network.BSSID + ". Skip " 310 + scanResult.BSSID); 311 continue; 312 } 313 314 // If the network is marked to use external scores, leave it to the 315 // external score evaluator to handle it. 316 if (network.useExternalScores) { 317 localLog("Network " + WifiNetworkSelector.toNetworkString(network) 318 + " has external score."); 319 continue; 320 } 321 322 if (mCurateSavedOpenNetworks 323 && WifiConfigurationUtil.isConfigForOpenNetwork(network)) { 324 localLog("Network " + WifiNetworkSelector.toNetworkString(network) 325 + " is open and CURATE_SAVED_OPEN_NETWORKS is enabled."); 326 continue; 327 } 328 329 score = calculateBssidScore(scanResult, network, currentNetwork, currentBssid, 330 scoreHistory); 331 332 if (score > highestScoreOfScanResult) { 333 highestScoreOfScanResult = score; 334 candidateIdOfScanResult = network.networkId; 335 } 336 337 if (score > status.getCandidateScore() || (score == status.getCandidateScore() 338 && status.getCandidate() != null 339 && scanResult.level > status.getCandidate().level)) { 340 mWifiConfigManager.setNetworkCandidateScanResult( 341 candidateIdOfScanResult, scanResult, score); 342 } 343 } 344 345 if (connectableNetworks != null) { 346 connectableNetworks.add(Pair.create(scanDetail, 347 mWifiConfigManager.getConfiguredNetwork(candidateIdOfScanResult))); 348 } 349 350 if (highestScoreOfScanResult > highestScore 351 || (highestScoreOfScanResult == highestScore 352 && scanResultCandidate != null 353 && scanResult.level > scanResultCandidate.level)) { 354 highestScore = highestScoreOfScanResult; 355 scanResultCandidate = scanResult; 356 mWifiConfigManager.setNetworkCandidateScanResult( 357 candidateIdOfScanResult, scanResultCandidate, highestScore); 358 // Reload the network config with the updated info. 359 candidate = mWifiConfigManager.getConfiguredNetwork(candidateIdOfScanResult); 360 } 361 } 362 363 if (scoreHistory.length() > 0) { 364 localLog("\n" + scoreHistory.toString()); 365 } 366 367 if (scanResultCandidate != null) { 368 return adjustCandidateWithUserSelection(candidate, scanResultCandidate); 369 } else { 370 localLog("did not see any good candidates."); 371 return null; 372 } 373 } 374} 375