WifiQualifiedNetworkSelector.java revision 60969bf2c849011fb585cf6dc914dbd779dfb8cf
1/* 2 * Copyright (C) 2015 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.net.NetworkKey; 21import android.net.NetworkScoreManager; 22import android.net.WifiKey; 23import android.net.wifi.ScanResult; 24import android.net.wifi.WifiConfiguration; 25import android.net.wifi.WifiInfo; 26import android.net.wifi.WifiManager; 27import android.text.TextUtils; 28import android.util.LocalLog; 29import android.util.Log; 30 31import com.android.internal.R; 32import com.android.internal.annotations.VisibleForTesting; 33 34import java.io.FileDescriptor; 35import java.io.PrintWriter; 36import java.util.ArrayList; 37import java.util.HashMap; 38import java.util.Iterator; 39import java.util.List; 40import java.util.Map; 41 42/** 43 * This class looks at all the connectivity scan results then 44 * select an network for the phone to connect/roam to. 45 */ 46public class WifiQualifiedNetworkSelector { 47 private WifiConfigManager mWifiConfigManager; 48 private WifiInfo mWifiInfo; 49 private NetworkScoreManager mScoreManager; 50 private WifiNetworkScoreCache mNetworkScoreCache; 51 private Clock mClock; 52 private static final String TAG = "WifiQualifiedNetworkSelector:"; 53 private boolean mDbg = true; 54 private WifiConfiguration mCurrentConnectedNetwork = null; 55 private String mCurrentBssid = null; 56 //buffer most recent scan results 57 private List<ScanDetail> mScanDetails = null; 58 //buffer of filtered scan results (Scan results considered by network selection) 59 private volatile List<ScanDetail> mFilteredScanDetails = null; 60 61 //Minimum time gap between last successful Qualified Network Selection and new selection attempt 62 //usable only when current state is connected state default 10 s 63 private static final int MINIMUM_QUALIFIED_NETWORK_SELECTION_INTERVAL = 10 * 1000; 64 65 //if current network is on 2.4GHz band and has a RSSI over this, need not new network selection 66 public static final int QUALIFIED_RSSI_24G_BAND = -73; 67 //if current network is on 5GHz band and has a RSSI over this, need not new network selection 68 public static final int QUALIFIED_RSSI_5G_BAND = -70; 69 //any RSSI larger than this will benefit the traffic very limited 70 public static final int RSSI_SATURATION_2G_BAND = -60; 71 public static final int RSSI_SATURATION_5G_BAND = -57; 72 //Any value below this will be considered not usable 73 public static final int MINIMUM_2G_ACCEPT_RSSI = -85; 74 public static final int MINIMUM_5G_ACCEPT_RSSI = -82; 75 76 public static final int RSSI_SCORE_SLOPE = 4; 77 public static final int RSSI_SCORE_OFFSET = 85; 78 79 public static final int BAND_AWARD_5GHz = 40; 80 public static final int SAME_NETWORK_AWARD = 16; 81 82 public static final int SAME_BSSID_AWARD = 24; 83 public static final int LAST_SELECTION_AWARD = 480; 84 public static final int PASSPOINT_SECURITY_AWARD = 40; 85 public static final int SECURITY_AWARD = 80; 86 public static final int BSSID_BLACKLIST_THRESHOLD = 3; 87 public static final int BSSID_BLACKLIST_EXPIRE_TIME = 30 * 60 * 1000; 88 private final int mNoIntnetPenalty; 89 //TODO: check whether we still need this one when we update the scan manager 90 public static final int SCAN_RESULT_MAXIMUNM_AGE = 40000; 91 private static final int INVALID_TIME_STAMP = -1; 92 private long mLastQualifiedNetworkSelectionTimeStamp = INVALID_TIME_STAMP; 93 94 // Temporarily, for dog food 95 private final LocalLog mLocalLog = new LocalLog(16384); 96 private int mRssiScoreSlope = RSSI_SCORE_SLOPE; 97 private int mRssiScoreOffset = RSSI_SCORE_OFFSET; 98 private int mSameBssidAward = SAME_BSSID_AWARD; 99 private int mLastSelectionAward = LAST_SELECTION_AWARD; 100 private int mPasspointSecurityAward = PASSPOINT_SECURITY_AWARD; 101 private int mSecurityAward = SECURITY_AWARD; 102 private int mUserPreferedBand = WifiManager.WIFI_FREQUENCY_BAND_AUTO; 103 private Map<String, BssidBlacklistStatus> mBssidBlacklist = 104 new HashMap<String, BssidBlacklistStatus>(); 105 106 /** 107 * class save the blacklist status of a given BSSID 108 */ 109 private static class BssidBlacklistStatus { 110 //how many times it is requested to be blacklisted (association rejection trigger this) 111 int mCounter; 112 boolean mIsBlacklisted; 113 long mBlacklistedTimeStamp = INVALID_TIME_STAMP; 114 } 115 116 private void qnsLog(String log) { 117 if (mDbg) { 118 mLocalLog.log(log); 119 } 120 } 121 122 private void qnsLoge(String log) { 123 mLocalLog.log(log); 124 } 125 126 @VisibleForTesting 127 void setWifiNetworkScoreCache(WifiNetworkScoreCache cache) { 128 mNetworkScoreCache = cache; 129 } 130 131 /** 132 * @return current target connected network 133 */ 134 public WifiConfiguration getConnetionTargetNetwork() { 135 return mCurrentConnectedNetwork; 136 } 137 138 /** 139 * @return the list of ScanDetails scored as potential candidates by the last run of 140 * selectQualifiedNetwork, this will be empty if QNS determined no selection was needed on last 141 * run. This includes scan details of sufficient signal strength, and had an associated 142 * WifiConfiguration. 143 */ 144 public List<ScanDetail> getFilteredScanDetails() { 145 return mFilteredScanDetails; 146 } 147 148 /** 149 * set the user selected preferred band 150 * 151 * @param band preferred band user selected 152 */ 153 public void setUserPreferredBand(int band) { 154 mUserPreferedBand = band; 155 } 156 157 WifiQualifiedNetworkSelector(WifiConfigManager configureStore, Context context, 158 WifiInfo wifiInfo, Clock clock) { 159 mWifiConfigManager = configureStore; 160 mWifiInfo = wifiInfo; 161 mClock = clock; 162 mScoreManager = 163 (NetworkScoreManager) context.getSystemService(Context.NETWORK_SCORE_SERVICE); 164 if (mScoreManager != null) { 165 mNetworkScoreCache = new WifiNetworkScoreCache(context); 166 mScoreManager.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache); 167 } else { 168 qnsLoge("No network score service: Couldn't register as a WiFi score Manager, type=" 169 + NetworkKey.TYPE_WIFI + " service= " + Context.NETWORK_SCORE_SERVICE); 170 mNetworkScoreCache = null; 171 } 172 173 mRssiScoreSlope = context.getResources().getInteger( 174 R.integer.config_wifi_framework_RSSI_SCORE_SLOPE); 175 mRssiScoreOffset = context.getResources().getInteger( 176 R.integer.config_wifi_framework_RSSI_SCORE_OFFSET); 177 mSameBssidAward = context.getResources().getInteger( 178 R.integer.config_wifi_framework_SAME_BSSID_AWARD); 179 mLastSelectionAward = context.getResources().getInteger( 180 R.integer.config_wifi_framework_LAST_SELECTION_AWARD); 181 mPasspointSecurityAward = context.getResources().getInteger( 182 R.integer.config_wifi_framework_PASSPOINT_SECURITY_AWARD); 183 mSecurityAward = context.getResources().getInteger( 184 R.integer.config_wifi_framework_SECURITY_AWARD); 185 mNoIntnetPenalty = (mWifiConfigManager.mThresholdSaturatedRssi24.get() + mRssiScoreOffset) 186 * mRssiScoreSlope + mWifiConfigManager.mBandAward5Ghz.get() 187 + mWifiConfigManager.mCurrentNetworkBoost.get() + mSameBssidAward + mSecurityAward; 188 } 189 190 void enableVerboseLogging(int verbose) { 191 mDbg = verbose > 0; 192 } 193 194 private String getNetworkString(WifiConfiguration network) { 195 if (network == null) { 196 return null; 197 } 198 199 return (network.SSID + ":" + network.networkId); 200 201 } 202 203 /** 204 * check whether current network is good enough we need not consider any potential switch 205 * 206 * @param currentNetwork -- current connected network 207 * @return true -- qualified and do not consider potential network switch 208 * false -- not good enough and should try potential network switch 209 */ 210 private boolean isNetworkQualified(WifiConfiguration currentNetwork) { 211 212 if (currentNetwork == null) { 213 qnsLog("Disconnected"); 214 return false; 215 } else { 216 qnsLog("Current network is: " + currentNetwork.SSID + " ,ID is: " 217 + currentNetwork.networkId); 218 } 219 220 //if current connected network is an ephemeral network,we will consider 221 // there is no current network 222 if (currentNetwork.ephemeral) { 223 qnsLog("Current is ephemeral. Start reselect"); 224 return false; 225 } 226 227 //if current network is open network, not qualified 228 if (mWifiConfigManager.isOpenNetwork(currentNetwork)) { 229 qnsLog("Current network is open network"); 230 return false; 231 } 232 233 // Current network band must match with user preference selection 234 if (mWifiInfo.is24GHz() && (mUserPreferedBand != WifiManager.WIFI_FREQUENCY_BAND_2GHZ)) { 235 qnsLog("Current band dose not match user preference. Start Qualified Network" 236 + " Selection Current band = " + (mWifiInfo.is24GHz() ? "2.4GHz band" 237 : "5GHz band") + "UserPreference band = " + mUserPreferedBand); 238 return false; 239 } 240 241 int currentRssi = mWifiInfo.getRssi(); 242 if ((mWifiInfo.is24GHz() 243 && currentRssi < mWifiConfigManager.mThresholdQualifiedRssi24.get()) 244 || (mWifiInfo.is5GHz() 245 && currentRssi < mWifiConfigManager.mThresholdQualifiedRssi5.get())) { 246 qnsLog("Current band = " + (mWifiInfo.is24GHz() ? "2.4GHz band" : "5GHz band") 247 + "current RSSI is: " + currentRssi); 248 return false; 249 } 250 251 return true; 252 } 253 254 /** 255 * check whether QualifiedNetworkSelection is needed or not 256 * 257 * @param isLinkDebouncing true -- Link layer is under debouncing 258 * false -- Link layer is not under debouncing 259 * @param isConnected true -- device is connected to an AP currently 260 * false -- device is not connected to an AP currently 261 * @param isDisconnected true -- WifiStateMachine is at disconnected state 262 * false -- WifiStateMachine is not at disconnected state 263 * @param isSupplicantTransientState true -- supplicant is in a transient state now 264 * false -- supplicant is not in a transient state now 265 * @return true -- need a Qualified Network Selection procedure 266 * false -- do not need a QualifiedNetworkSelection procedure 267 */ 268 private boolean needQualifiedNetworkSelection(boolean isLinkDebouncing, boolean isConnected, 269 boolean isDisconnected, boolean isSupplicantTransientState) { 270 if (mScanDetails.size() == 0) { 271 qnsLog("empty scan result"); 272 return false; 273 } 274 275 // Do not trigger Qualified Network Selection during L2 link debouncing procedure 276 if (isLinkDebouncing) { 277 qnsLog("Need not Qualified Network Selection during L2 debouncing"); 278 return false; 279 } 280 281 if (isConnected) { 282 //already connected. Just try to find better candidate 283 //if switch network is not allowed in connected mode, do not trigger Qualified Network 284 //Selection 285 if (!mWifiConfigManager.getEnableAutoJoinWhenAssociated()) { 286 qnsLog("Switch network under connection is not allowed"); 287 return false; 288 } 289 290 //Do not select again if last selection is within 291 //MINIMUM_QUALIFIED_NETWORK_SELECTION_INTERVAL 292 if (mLastQualifiedNetworkSelectionTimeStamp != INVALID_TIME_STAMP) { 293 long gap = mClock.currentTimeMillis() - mLastQualifiedNetworkSelectionTimeStamp; 294 if (gap < MINIMUM_QUALIFIED_NETWORK_SELECTION_INTERVAL) { 295 qnsLog("Too short to last successful Qualified Network Selection Gap is:" + gap 296 + " ms!"); 297 return false; 298 } 299 } 300 301 WifiConfiguration currentNetwork = 302 mWifiConfigManager.getWifiConfiguration(mWifiInfo.getNetworkId()); 303 if (currentNetwork == null) { 304 // WifiStateMachine in connected state but WifiInfo is not. It means there is a race 305 // condition happened. Do not make QNS until WifiStateMachine goes into 306 // disconnected state 307 return false; 308 } 309 310 if (mCurrentConnectedNetwork != null 311 && mCurrentConnectedNetwork.networkId != currentNetwork.networkId) { 312 //If this happens, supplicant switch the connection silently. This is a bug 313 // FIXME: 11/10/15 314 qnsLoge("supplicant switched the network silently" + " last Qualified Network" 315 + " Selection:" + getNetworkString(mCurrentConnectedNetwork) 316 + " current network:" + getNetworkString(currentNetwork)); 317 mCurrentConnectedNetwork = currentNetwork; 318 mCurrentBssid = mWifiInfo.getBSSID(); 319 //We do not believe lower layer choice 320 return true; 321 } 322 323 String bssid = mWifiInfo.getBSSID(); 324 if (mCurrentBssid != null && !mCurrentBssid.equals(bssid)) { 325 //If this happens, supplicant roamed silently. This is a bug 326 // FIXME: 11/10/15 327 qnsLoge("supplicant roamed silently. Last selected BSSID:" + mCurrentBssid 328 + " current BSSID:" + bssid); 329 mCurrentBssid = mWifiInfo.getBSSID(); 330 //We do not believe lower layer choice 331 return true; 332 } 333 334 if (!isNetworkQualified(mCurrentConnectedNetwork)) { 335 //need not trigger Qualified Network Selection if current network is qualified 336 qnsLog("Current network is not qualified"); 337 return true; 338 } else { 339 return false; 340 } 341 } else if (isDisconnected) { 342 mCurrentConnectedNetwork = null; 343 mCurrentBssid = null; 344 //Do not start Qualified Network Selection if current state is a transient state 345 if (isSupplicantTransientState) { 346 return false; 347 } 348 } else { 349 //Do not allow new network selection in other state 350 qnsLog("WifiStateMachine is not on connected or disconnected state"); 351 return false; 352 } 353 354 return true; 355 } 356 357 int calculateBssidScore(ScanResult scanResult, WifiConfiguration network, 358 WifiConfiguration currentNetwork, boolean sameBssid, boolean sameSelect, 359 StringBuffer sbuf) { 360 361 int score = 0; 362 //calculate the RSSI score 363 int rssi = scanResult.level <= mWifiConfigManager.mThresholdSaturatedRssi24.get() 364 ? scanResult.level : mWifiConfigManager.mThresholdSaturatedRssi24.get(); 365 score += (rssi + mRssiScoreOffset) * mRssiScoreSlope; 366 sbuf.append(" RSSI score: " + score); 367 if (scanResult.is5GHz()) { 368 //5GHz band 369 score += mWifiConfigManager.mBandAward5Ghz.get(); 370 sbuf.append(" 5GHz bonus: " + mWifiConfigManager.mBandAward5Ghz.get()); 371 } 372 373 //last user selection award 374 if (sameSelect) { 375 long timeDifference = mClock.currentTimeMillis() 376 - mWifiConfigManager.getLastSelectedTimeStamp(); 377 378 if (timeDifference > 0) { 379 int bonus = mLastSelectionAward - (int) (timeDifference / 1000 / 60); 380 score += bonus > 0 ? bonus : 0; 381 sbuf.append(" User selected it last time " + (timeDifference / 1000 / 60) 382 + " minutes ago, bonus:" + bonus); 383 } 384 } 385 386 //same network award 387 if (network == currentNetwork || network.isLinked(currentNetwork)) { 388 score += mWifiConfigManager.mCurrentNetworkBoost.get(); 389 sbuf.append(" Same network with current associated. Bonus: " 390 + mWifiConfigManager.mCurrentNetworkBoost.get()); 391 } 392 393 //same BSSID award 394 if (sameBssid) { 395 score += mSameBssidAward; 396 sbuf.append(" Same BSSID with current association. Bonus: " + mSameBssidAward); 397 } 398 399 //security award 400 if (network.isPasspoint()) { 401 score += mPasspointSecurityAward; 402 sbuf.append(" Passpoint Bonus:" + mPasspointSecurityAward); 403 } else if (!mWifiConfigManager.isOpenNetwork(network)) { 404 score += mSecurityAward; 405 sbuf.append(" Secure network Bonus:" + mSecurityAward); 406 } 407 408 //Penalty for no internet network. Make sure if there is any network with Internet, 409 //however, if there is no any other network with internet, this network can be chosen 410 if (network.numNoInternetAccessReports > 0 && !network.validatedInternetAccess) { 411 score -= mNoIntnetPenalty; 412 sbuf.append(" No internet Penalty:-" + mNoIntnetPenalty); 413 } 414 415 416 sbuf.append(" Score for scanResult: " + scanResult + " and Network ID: " 417 + network.networkId + " final score:" + score + "\n\n"); 418 419 return score; 420 } 421 422 /** 423 * This API try to update all the saved networks' network selection status 424 */ 425 private void updateSavedNetworkSelectionStatus() { 426 List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks(); 427 if (savedNetworks.size() == 0) { 428 qnsLog("no saved network"); 429 return; 430 } 431 432 StringBuffer sbuf = new StringBuffer("Saved Network List\n"); 433 for (WifiConfiguration network : savedNetworks) { 434 WifiConfiguration config = mWifiConfigManager.getWifiConfiguration(network.networkId); 435 WifiConfiguration.NetworkSelectionStatus status = 436 config.getNetworkSelectionStatus(); 437 438 //If the configuration is temporarily disabled, try to re-enable it 439 if (status.isNetworkTemporaryDisabled()) { 440 mWifiConfigManager.tryEnableQualifiedNetwork(network.networkId); 441 } 442 443 //clean the cached candidate, score and seen 444 status.setCandidate(null); 445 status.setCandidateScore(Integer.MIN_VALUE); 446 status.setSeenInLastQualifiedNetworkSelection(false); 447 448 //print the debug messages 449 sbuf.append(" " + getNetworkString(network) + " " + " User Preferred BSSID:" 450 + network.BSSID + " FQDN:" + network.FQDN + " " 451 + status.getNetworkStatusString() + " Disable account: "); 452 for (int index = status.NETWORK_SELECTION_ENABLE; 453 index < status.NETWORK_SELECTION_DISABLED_MAX; index++) { 454 sbuf.append(status.getDisableReasonCounter(index) + " "); 455 } 456 sbuf.append("Connect Choice:" + status.getConnectChoice() + " set time:" 457 + status.getConnectChoiceTimestamp()); 458 sbuf.append("\n"); 459 } 460 qnsLog(sbuf.toString()); 461 } 462 463 /** 464 * This API is called when user explicitly select a network. Currently, it is used in following 465 * cases: 466 * (1) User explicitly choose to connect to a saved network 467 * (2) User save a network after add a new network 468 * (3) User save a network after modify a saved network 469 * Following actions will be triggered: 470 * 1. if this network is disabled, we need re-enable it again 471 * 2. we considered user prefer this network over all the networks visible in latest network 472 * selection procedure 473 * 474 * @param netId new network ID for either the network the user choose or add 475 * @param persist whether user has the authority to overwrite current connect choice 476 * @return true -- There is change made to connection choice of any saved network 477 * false -- There is no change made to connection choice of any saved network 478 */ 479 public boolean userSelectNetwork(int netId, boolean persist) { 480 WifiConfiguration selected = mWifiConfigManager.getWifiConfiguration(netId); 481 qnsLog("userSelectNetwork:" + netId + " persist:" + persist); 482 if (selected == null || selected.SSID == null) { 483 qnsLoge("userSelectNetwork: Bad configuration with nid=" + netId); 484 return false; 485 } 486 487 488 if (!selected.getNetworkSelectionStatus().isNetworkEnabled()) { 489 mWifiConfigManager.updateNetworkSelectionStatus(netId, 490 WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE); 491 } 492 493 if (!persist) { 494 qnsLog("User has no privilege to overwrite the current priority"); 495 return false; 496 } 497 498 boolean change = false; 499 String key = selected.configKey(); 500 long currentTime = mClock.currentTimeMillis(); 501 List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks(); 502 503 for (WifiConfiguration network : savedNetworks) { 504 WifiConfiguration config = mWifiConfigManager.getWifiConfiguration(network.networkId); 505 WifiConfiguration.NetworkSelectionStatus status = config.getNetworkSelectionStatus(); 506 if (config.networkId == selected.networkId) { 507 if (status.getConnectChoice() != null) { 508 qnsLog("Remove user selection preference of " + status.getConnectChoice() 509 + " Set Time: " + status.getConnectChoiceTimestamp() + " from " 510 + config.SSID + " : " + config.networkId); 511 status.setConnectChoice(null); 512 status.setConnectChoiceTimestamp(WifiConfiguration.NetworkSelectionStatus 513 .INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP); 514 change = true; 515 } 516 continue; 517 } 518 519 if (status.getSeenInLastQualifiedNetworkSelection() 520 && (status.getConnectChoice() == null 521 || !status.getConnectChoice().equals(key))) { 522 qnsLog("Add key:" + key + " Set Time: " + currentTime + " to " 523 + getNetworkString(config)); 524 status.setConnectChoice(key); 525 status.setConnectChoiceTimestamp(currentTime); 526 change = true; 527 } 528 } 529 //Write this change to file 530 if (change) { 531 mWifiConfigManager.writeKnownNetworkHistory(); 532 return true; 533 } 534 535 return false; 536 } 537 538 /** 539 * enable/disable a BSSID for Quality Network Selection 540 * When an association rejection event is obtained, Quality Network Selector will disable this 541 * BSSID but supplicant still can try to connect to this bssid. If supplicant connect to it 542 * successfully later, this bssid can be re-enabled. 543 * 544 * @param bssid the bssid to be enabled / disabled 545 * @param enable -- true enable a bssid if it has been disabled 546 * -- false disable a bssid 547 */ 548 public boolean enableBssidForQualityNetworkSelection(String bssid, boolean enable) { 549 if (enable) { 550 return (mBssidBlacklist.remove(bssid) != null); 551 } else { 552 if (bssid != null) { 553 BssidBlacklistStatus status = mBssidBlacklist.get(bssid); 554 if (status == null) { 555 //first time 556 BssidBlacklistStatus newStatus = new BssidBlacklistStatus(); 557 newStatus.mCounter++; 558 mBssidBlacklist.put(bssid, newStatus); 559 } else if (!status.mIsBlacklisted) { 560 status.mCounter++; 561 if (status.mCounter >= BSSID_BLACKLIST_THRESHOLD) { 562 status.mIsBlacklisted = true; 563 status.mBlacklistedTimeStamp = mClock.currentTimeMillis(); 564 return true; 565 } 566 } 567 } 568 } 569 return false; 570 } 571 572 /** 573 * update the buffered BSSID blacklist 574 * 575 * Go through the whole buffered BSSIDs blacklist and check when the BSSIDs is blocked. If they 576 * were blacked before BSSID_BLACKLIST_EXPIRE_TIME, re-enable it again. 577 */ 578 private void updateBssidBlacklist() { 579 Iterator<BssidBlacklistStatus> iter = mBssidBlacklist.values().iterator(); 580 while (iter.hasNext()) { 581 BssidBlacklistStatus status = iter.next(); 582 if (status != null && status.mIsBlacklisted) { 583 if (mClock.currentTimeMillis() - status.mBlacklistedTimeStamp 584 >= BSSID_BLACKLIST_EXPIRE_TIME) { 585 iter.remove(); 586 } 587 } 588 } 589 } 590 591 /** 592 * Check whether a bssid is disabled 593 * @param bssid -- the bssid to check 594 * @return true -- bssid is disabled 595 * false -- bssid is not disabled 596 */ 597 public boolean isBssidDisabled(String bssid) { 598 BssidBlacklistStatus status = mBssidBlacklist.get(bssid); 599 return status == null ? false : status.mIsBlacklisted; 600 } 601 602 /** 603 * ToDo: This should be called in Connectivity Manager when it gets new scan result 604 * check whether a network slection is needed. If need, check all the new scan results and 605 * select a new qualified network/BSSID to connect to 606 * 607 * @param forceSelectNetwork true -- start a qualified network selection anyway,no matter 608 * current network is already qualified or not. 609 * false -- if current network is already qualified, do not do new 610 * selection 611 * @param isUntrustedConnectionsAllowed true -- user allow to connect to untrusted network 612 * false -- user do not allow to connect to untrusted 613 * network 614 * @param scanDetails latest scan result obtained (should be connectivity scan only) 615 * @param isLinkDebouncing true -- Link layer is under debouncing 616 * false -- Link layer is not under debouncing 617 * @param isConnected true -- device is connected to an AP currently 618 * false -- device is not connected to an AP currently 619 * @param isDisconnected true -- WifiStateMachine is at disconnected state 620 * false -- WifiStateMachine is not at disconnected state 621 * @param isSupplicantTransient true -- supplicant is in a transient state 622 * false -- supplicant is not in a transient state 623 * @return the qualified network candidate found. If no available candidate, return null 624 */ 625 public WifiConfiguration selectQualifiedNetwork(boolean forceSelectNetwork , 626 boolean isUntrustedConnectionsAllowed, List<ScanDetail> scanDetails, 627 boolean isLinkDebouncing, boolean isConnected, boolean isDisconnected, 628 boolean isSupplicantTransient) { 629 qnsLog("==========start qualified Network Selection=========="); 630 mScanDetails = scanDetails; 631 List<ScanDetail> filteredScanDetails = new ArrayList<>(); 632 if (mCurrentConnectedNetwork == null) { 633 mCurrentConnectedNetwork = 634 mWifiConfigManager.getWifiConfiguration(mWifiInfo.getNetworkId()); 635 } 636 637 if (mCurrentBssid == null) { 638 mCurrentBssid = mWifiInfo.getBSSID(); 639 } 640 641 if (!forceSelectNetwork && !needQualifiedNetworkSelection(isLinkDebouncing, isConnected, 642 isDisconnected, isSupplicantTransient)) { 643 qnsLog("Quit qualified Network Selection since it is not forced and current network is" 644 + " qualified already"); 645 mFilteredScanDetails = filteredScanDetails; 646 return null; 647 } 648 649 int currentHighestScore = Integer.MIN_VALUE; 650 ScanResult scanResultCandidate = null; 651 WifiConfiguration networkCandidate = null; 652 int unTrustedHighestScore = WifiNetworkScoreCache.INVALID_NETWORK_SCORE; 653 ScanResult untrustedScanResultCandidate = null; 654 WifiConfiguration unTrustedNetworkCandidate = null; 655 String lastUserSelectedNetWorkKey = mWifiConfigManager.getLastSelectedConfiguration(); 656 WifiConfiguration lastUserSelectedNetwork = 657 mWifiConfigManager.getWifiConfiguration(lastUserSelectedNetWorkKey); 658 if (lastUserSelectedNetwork != null) { 659 qnsLog("Last selection is " + lastUserSelectedNetwork.SSID + " Time to now: " 660 + ((mClock.currentTimeMillis() - mWifiConfigManager.getLastSelectedTimeStamp()) 661 / 1000 / 60 + " minutes")); 662 } 663 664 updateSavedNetworkSelectionStatus(); 665 updateBssidBlacklist(); 666 667 StringBuffer lowSignalScan = new StringBuffer(); 668 StringBuffer notSavedScan = new StringBuffer(); 669 StringBuffer noValidSsid = new StringBuffer(); 670 StringBuffer scoreHistory = new StringBuffer(); 671 ArrayList<NetworkKey> unscoredNetworks = new ArrayList<NetworkKey>(); 672 673 //iterate all scan results and find the best candidate with the highest score 674 for (ScanDetail scanDetail : mScanDetails) { 675 ScanResult scanResult = scanDetail.getScanResult(); 676 //skip bad scan result 677 if (scanResult.SSID == null || TextUtils.isEmpty(scanResult.SSID)) { 678 if (mDbg) { 679 //We should not see this in ePNO 680 noValidSsid.append(scanResult.BSSID + " / "); 681 } 682 continue; 683 } 684 685 String scanId = scanResult.SSID + ":" + scanResult.BSSID; 686 //check whether this BSSID is blocked or not 687 if (mWifiConfigManager.isBssidBlacklisted(scanResult.BSSID) 688 || isBssidDisabled(scanResult.BSSID)) { 689 //We should not see this in ePNO 690 Log.e(TAG, scanId + " is in blacklist."); 691 continue; 692 } 693 694 //skip scan result with too weak signals 695 if ((scanResult.is24GHz() && scanResult.level 696 < mWifiConfigManager.mThresholdMinimumRssi24.get()) 697 || (scanResult.is5GHz() && scanResult.level 698 < mWifiConfigManager.mThresholdMinimumRssi5.get())) { 699 if (mDbg) { 700 lowSignalScan.append(scanId + "(" + (scanResult.is24GHz() ? "2.4GHz" : "5GHz") 701 + ")" + scanResult.level + " / "); 702 } 703 continue; 704 } 705 706 //check if there is already a score for this network 707 if (mNetworkScoreCache != null && !mNetworkScoreCache.isScoredNetwork(scanResult)) { 708 //no score for this network yet. 709 WifiKey wifiKey; 710 711 try { 712 wifiKey = new WifiKey("\"" + scanResult.SSID + "\"", scanResult.BSSID); 713 NetworkKey ntwkKey = new NetworkKey(wifiKey); 714 //add to the unscoredNetworks list so we can request score later 715 unscoredNetworks.add(ntwkKey); 716 } catch (IllegalArgumentException e) { 717 Log.w(TAG, "Invalid SSID=" + scanResult.SSID + " BSSID=" + scanResult.BSSID 718 + " for network score. Skip."); 719 } 720 } 721 722 //check whether this scan result belong to a saved network 723 boolean potentiallyEphemeral = false; 724 List<WifiConfiguration> associatedWifiConfigurations = 725 mWifiConfigManager.updateSavedNetworkWithNewScanDetail(scanDetail); 726 if (associatedWifiConfigurations == null) { 727 potentiallyEphemeral = true; 728 if (mDbg) { 729 notSavedScan.append(scanId + " / "); 730 } 731 } else if (associatedWifiConfigurations.size() == 1) { 732 //if there are more than 1 associated network, it must be a passpoint network 733 WifiConfiguration network = associatedWifiConfigurations.get(0); 734 if (network.ephemeral) { 735 potentiallyEphemeral = true; 736 } 737 } 738 739 if (potentiallyEphemeral) { 740 if (isUntrustedConnectionsAllowed && mNetworkScoreCache != null) { 741 int netScore = mNetworkScoreCache.getNetworkScore(scanResult, false); 742 //get network score (Determine if this is an 'Ephemeral' network) 743 if (netScore != WifiNetworkScoreCache.INVALID_NETWORK_SCORE) { 744 qnsLog(scanId + "has score: " + netScore); 745 if (netScore > unTrustedHighestScore) { 746 unTrustedHighestScore = netScore; 747 untrustedScanResultCandidate = scanResult; 748 qnsLog(scanId + " become the new untrusted candidate"); 749 } 750 // scanDetail is for available ephemeral network 751 filteredScanDetails.add(scanDetail); 752 } 753 } 754 continue; 755 } else { 756 // scanDetail is for available saved network 757 filteredScanDetails.add(scanDetail); 758 } 759 760 // calculate the core of each scanresult whose associated network is not ephemeral. Due 761 // to one scane result can associated with more than 1 network, we need calcualte all 762 // the scores and use the highest one as the scanresults score 763 int highestScore = Integer.MIN_VALUE; 764 int score; 765 WifiConfiguration configurationCandidateForThisScan = null; 766 767 for (WifiConfiguration network : associatedWifiConfigurations) { 768 WifiConfiguration.NetworkSelectionStatus status = 769 network.getNetworkSelectionStatus(); 770 status.setSeenInLastQualifiedNetworkSelection(true); 771 if (!status.isNetworkEnabled()) { 772 continue; 773 } else if (network.BSSID != null && !network.BSSID.equals("any") 774 && !network.BSSID.equals(scanResult.BSSID)) { 775 //in such scenario, user (APP) has specified the only BSSID to connect for this 776 // configuration. So only the matched scan result can be candidate 777 qnsLog("Network: " + getNetworkString(network) + " has specified" + "BSSID:" 778 + network.BSSID + ". Skip " + scanResult.BSSID); 779 continue; 780 } 781 score = calculateBssidScore(scanResult, network, mCurrentConnectedNetwork, 782 (mCurrentBssid == null ? false : mCurrentBssid.equals(scanResult.BSSID)), 783 (lastUserSelectedNetwork == null ? false : lastUserSelectedNetwork.networkId 784 == network.networkId), scoreHistory); 785 if (score > highestScore) { 786 highestScore = score; 787 configurationCandidateForThisScan = network; 788 } 789 //update the cached candidate 790 if (score > status.getCandidateScore()) { 791 status.setCandidate(scanResult); 792 status.setCandidateScore(score); 793 } 794 } 795 796 if (highestScore > currentHighestScore || (highestScore == currentHighestScore 797 && scanResultCandidate != null 798 && scanResult.level > scanResultCandidate.level)) { 799 currentHighestScore = highestScore; 800 scanResultCandidate = scanResult; 801 networkCandidate = configurationCandidateForThisScan; 802 } 803 } 804 805 mFilteredScanDetails = filteredScanDetails; 806 807 //kick the score manager if there is any unscored network 808 if (mScoreManager != null && unscoredNetworks.size() != 0) { 809 NetworkKey[] unscoredNetworkKeys = 810 unscoredNetworks.toArray(new NetworkKey[unscoredNetworks.size()]); 811 mScoreManager.requestScores(unscoredNetworkKeys); 812 } 813 814 if (mDbg) { 815 qnsLog(lowSignalScan + " skipped due to low signal\n"); 816 qnsLog(notSavedScan + " skipped due to not saved\n "); 817 qnsLog(noValidSsid + " skipped due to not valid SSID\n"); 818 qnsLog(scoreHistory.toString()); 819 } 820 821 //we need traverse the whole user preference to choose the one user like most now 822 if (scanResultCandidate != null) { 823 WifiConfiguration tempConfig = networkCandidate; 824 825 while (tempConfig.getNetworkSelectionStatus().getConnectChoice() != null) { 826 String key = tempConfig.getNetworkSelectionStatus().getConnectChoice(); 827 tempConfig = mWifiConfigManager.getWifiConfiguration(key); 828 829 if (tempConfig != null) { 830 WifiConfiguration.NetworkSelectionStatus tempStatus = 831 tempConfig.getNetworkSelectionStatus(); 832 if (tempStatus.getCandidate() != null && tempStatus.isNetworkEnabled()) { 833 scanResultCandidate = tempStatus.getCandidate(); 834 networkCandidate = tempConfig; 835 } 836 } else { 837 //we should not come here in theory 838 qnsLoge("Connect choice: " + key + " has no corresponding saved config"); 839 break; 840 } 841 } 842 qnsLog("After user choice adjust, the final candidate is:" 843 + getNetworkString(networkCandidate) + " : " + scanResultCandidate.BSSID); 844 } 845 846 // if we can not find scanCadidate in saved network 847 if (scanResultCandidate == null && isUntrustedConnectionsAllowed) { 848 849 if (untrustedScanResultCandidate == null) { 850 qnsLog("Can not find any candidate"); 851 return null; 852 } 853 854 if (unTrustedNetworkCandidate == null) { 855 unTrustedNetworkCandidate = 856 mWifiConfigManager.wifiConfigurationFromScanResult( 857 untrustedScanResultCandidate); 858 859 unTrustedNetworkCandidate.ephemeral = true; 860 if (mNetworkScoreCache != null) { 861 boolean meteredHint = 862 mNetworkScoreCache.getMeteredHint(untrustedScanResultCandidate); 863 unTrustedNetworkCandidate.meteredHint = meteredHint; 864 } 865 mWifiConfigManager.saveNetwork(unTrustedNetworkCandidate, 866 WifiConfiguration.UNKNOWN_UID); 867 868 869 qnsLog(String.format("new ephemeral candidate %s:%s network ID:%d, meteredHint=%b", 870 untrustedScanResultCandidate.SSID, untrustedScanResultCandidate.BSSID, 871 unTrustedNetworkCandidate.networkId, 872 unTrustedNetworkCandidate.meteredHint)); 873 874 } else { 875 qnsLog(String.format("choose existing ephemeral candidate %s:%s network ID:%d, " 876 + "meteredHint=%b", 877 untrustedScanResultCandidate.SSID, untrustedScanResultCandidate.BSSID, 878 unTrustedNetworkCandidate.networkId, 879 unTrustedNetworkCandidate.meteredHint)); 880 } 881 unTrustedNetworkCandidate.getNetworkSelectionStatus() 882 .setCandidate(untrustedScanResultCandidate); 883 scanResultCandidate = untrustedScanResultCandidate; 884 networkCandidate = unTrustedNetworkCandidate; 885 } 886 887 if (scanResultCandidate == null) { 888 qnsLog("Can not find any suitable candidates"); 889 return null; 890 } 891 892 String currentAssociationId = mCurrentConnectedNetwork == null ? "Disconnected" : 893 getNetworkString(mCurrentConnectedNetwork); 894 String targetAssociationId = getNetworkString(networkCandidate); 895 //In passpoint, saved configuration has garbage SSID. We need update it with the SSID of 896 //the scan result. 897 if (networkCandidate.isPasspoint()) { 898 // This will updateb the passpoint configuration in WifiConfigManager 899 networkCandidate.SSID = "\"" + scanResultCandidate.SSID + "\""; 900 } 901 902 //For debug purpose only 903 if (scanResultCandidate.BSSID.equals(mCurrentBssid)) { 904 qnsLog(currentAssociationId + " is already the best choice!"); 905 } else if (mCurrentConnectedNetwork != null 906 && (mCurrentConnectedNetwork.networkId == networkCandidate.networkId 907 || mCurrentConnectedNetwork.isLinked(networkCandidate))) { 908 qnsLog("Roaming from " + currentAssociationId + " to " + targetAssociationId); 909 } else { 910 qnsLog("reconnect from " + currentAssociationId + " to " + targetAssociationId); 911 } 912 913 mCurrentBssid = scanResultCandidate.BSSID; 914 mCurrentConnectedNetwork = networkCandidate; 915 mLastQualifiedNetworkSelectionTimeStamp = mClock.currentTimeMillis(); 916 return networkCandidate; 917 } 918 919 //Dump the logs 920 void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 921 pw.println("Dump of Quality Network Selection"); 922 pw.println(" - Log Begin ----"); 923 mLocalLog.dump(fd, pw, args); 924 pw.println(" - Log End ----"); 925 } 926} 927