1/* 2 * Copyright (C) 2014 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.*; 24import android.net.wifi.WifiConfiguration.KeyMgmt; 25import android.os.SystemClock; 26import android.provider.Settings; 27import android.os.Process; 28import android.text.TextUtils; 29import android.util.Log; 30 31import java.util.ArrayList; 32import java.util.Iterator; 33import java.util.HashMap; 34import java.util.List; 35 36/** 37 * AutoJoin controller is responsible for WiFi Connect decision 38 * 39 * It runs in the thread context of WifiStateMachine 40 * 41 */ 42public class WifiAutoJoinController { 43 44 private Context mContext; 45 private WifiStateMachine mWifiStateMachine; 46 private WifiConfigStore mWifiConfigStore; 47 private WifiNative mWifiNative; 48 49 private NetworkScoreManager scoreManager; 50 private WifiNetworkScoreCache mNetworkScoreCache; 51 52 private static final String TAG = "WifiAutoJoinController "; 53 private static boolean DBG = false; 54 private static boolean VDBG = false; 55 private static final boolean mStaStaSupported = false; 56 57 public static int mScanResultMaximumAge = 40000; /* milliseconds unit */ 58 public static int mScanResultAutoJoinAge = 5000; /* milliseconds unit */ 59 60 private String mCurrentConfigurationKey = null; //used by autojoin 61 62 private HashMap<String, ScanResult> scanResultCache = 63 new HashMap<String, ScanResult>(); 64 65 private WifiConnectionStatistics mWifiConnectionStatistics; 66 67 /** Whether to allow connections to untrusted networks. */ 68 private boolean mAllowUntrustedConnections = false; 69 70 /* For debug purpose only: if the scored override a score */ 71 boolean didOverride = false; 72 73 // Lose the non-auth failure blacklisting after 8 hours 74 private final static long loseBlackListHardMilli = 1000 * 60 * 60 * 8; 75 // Lose some temporary blacklisting after 30 minutes 76 private final static long loseBlackListSoftMilli = 1000 * 60 * 30; 77 78 /** @see android.provider.Settings.Global#WIFI_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS */ 79 private static final long DEFAULT_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS = 1000 * 60; // 1 minute 80 81 public static final int AUTO_JOIN_IDLE = 0; 82 public static final int AUTO_JOIN_ROAMING = 1; 83 public static final int AUTO_JOIN_EXTENDED_ROAMING = 2; 84 public static final int AUTO_JOIN_OUT_OF_NETWORK_ROAMING = 3; 85 86 public static final int HIGH_THRESHOLD_MODIFIER = 5; 87 88 // Below are AutoJoin wide parameters indicating if we should be aggressive before joining 89 // weak network. Note that we cannot join weak network that are going to be marked as unanted by 90 // ConnectivityService because this will trigger link flapping. 91 /** 92 * There was a non-blacklisted configuration that we bailed from because of a weak signal 93 */ 94 boolean didBailDueToWeakRssi = false; 95 /** 96 * number of time we consecutively bailed out of an eligible network because its signal 97 * was too weak 98 */ 99 int weakRssiBailCount = 0; 100 101 WifiAutoJoinController(Context c, WifiStateMachine w, WifiConfigStore s, 102 WifiConnectionStatistics st, WifiNative n) { 103 mContext = c; 104 mWifiStateMachine = w; 105 mWifiConfigStore = s; 106 mWifiNative = n; 107 mNetworkScoreCache = null; 108 mWifiConnectionStatistics = st; 109 scoreManager = 110 (NetworkScoreManager) mContext.getSystemService(Context.NETWORK_SCORE_SERVICE); 111 if (scoreManager == null) 112 logDbg("Registered scoreManager NULL " + " service " + Context.NETWORK_SCORE_SERVICE); 113 114 if (scoreManager != null) { 115 mNetworkScoreCache = new WifiNetworkScoreCache(mContext); 116 scoreManager.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache); 117 } else { 118 logDbg("No network score service: Couldnt register as a WiFi score Manager, type=" 119 + Integer.toString(NetworkKey.TYPE_WIFI) 120 + " service " + Context.NETWORK_SCORE_SERVICE); 121 mNetworkScoreCache = null; 122 } 123 } 124 125 void enableVerboseLogging(int verbose) { 126 if (verbose > 0 ) { 127 DBG = true; 128 VDBG = true; 129 } else { 130 DBG = false; 131 VDBG = false; 132 } 133 } 134 135 /** 136 * Flush out scan results older than mScanResultMaximumAge 137 * 138 */ 139 private void ageScanResultsOut(int delay) { 140 if (delay <= 0) { 141 delay = mScanResultMaximumAge; // Something sane 142 } 143 long milli = System.currentTimeMillis(); 144 if (VDBG) { 145 logDbg("ageScanResultsOut delay " + Integer.valueOf(delay) + " size " 146 + Integer.valueOf(scanResultCache.size()) + " now " + Long.valueOf(milli)); 147 } 148 149 Iterator<HashMap.Entry<String,ScanResult>> iter = scanResultCache.entrySet().iterator(); 150 while (iter.hasNext()) { 151 HashMap.Entry<String,ScanResult> entry = iter.next(); 152 ScanResult result = entry.getValue(); 153 154 if ((result.seen + delay) < milli) { 155 iter.remove(); 156 } 157 } 158 } 159 160 int addToScanCache(List<ScanResult> scanList) { 161 int numScanResultsKnown = 0; // Record number of scan results we knew about 162 WifiConfiguration associatedConfig = null; 163 boolean didAssociate = false; 164 long now = System.currentTimeMillis(); 165 166 ArrayList<NetworkKey> unknownScanResults = new ArrayList<NetworkKey>(); 167 168 for(ScanResult result: scanList) { 169 if (result.SSID == null) continue; 170 171 // Make sure we record the last time we saw this result 172 result.seen = System.currentTimeMillis(); 173 174 // Fetch the previous instance for this result 175 ScanResult sr = scanResultCache.get(result.BSSID); 176 if (sr != null) { 177 if (mWifiConfigStore.scanResultRssiLevelPatchUp != 0 178 && result.level == 0 179 && sr.level < -20) { 180 // A 'zero' RSSI reading is most likely a chip problem which returns 181 // an unknown RSSI, hence ignore it 182 result.level = sr.level; 183 } 184 185 // If there was a previous cache result for this BSSID, average the RSSI values 186 result.averageRssi(sr.level, sr.seen, mScanResultMaximumAge); 187 188 // Remove the previous Scan Result - this is not necessary 189 scanResultCache.remove(result.BSSID); 190 } else if (mWifiConfigStore.scanResultRssiLevelPatchUp != 0 && result.level == 0) { 191 // A 'zero' RSSI reading is most likely a chip problem which returns 192 // an unknown RSSI, hence initialize it to a sane value 193 result.level = mWifiConfigStore.scanResultRssiLevelPatchUp; 194 } 195 196 if (!mNetworkScoreCache.isScoredNetwork(result)) { 197 WifiKey wkey; 198 // Quoted SSIDs are the only one valid at this stage 199 try { 200 wkey = new WifiKey("\"" + result.SSID + "\"", result.BSSID); 201 } catch (IllegalArgumentException e) { 202 logDbg("AutoJoinController: received badly encoded SSID=[" + result.SSID + 203 "] ->skipping this network"); 204 wkey = null; 205 } 206 if (wkey != null) { 207 NetworkKey nkey = new NetworkKey(wkey); 208 //if we don't know this scan result then request a score from the scorer 209 unknownScanResults.add(nkey); 210 } 211 if (VDBG) { 212 String cap = ""; 213 if (result.capabilities != null) 214 cap = result.capabilities; 215 logDbg(result.SSID + " " + result.BSSID + " rssi=" 216 + result.level + " cap " + cap + " is not scored"); 217 } 218 } else { 219 if (VDBG) { 220 String cap = ""; 221 if (result.capabilities != null) 222 cap = result.capabilities; 223 int score = mNetworkScoreCache.getNetworkScore(result); 224 logDbg(result.SSID + " " + result.BSSID + " rssi=" 225 + result.level + " cap " + cap + " is scored : " + score); 226 } 227 } 228 229 // scanResultCache.put(result.BSSID, new ScanResult(result)); 230 scanResultCache.put(result.BSSID, result); 231 // Add this BSSID to the scanResultCache of a Saved WifiConfiguration 232 didAssociate = mWifiConfigStore.updateSavedNetworkHistory(result); 233 234 // If not successful, try to associate this BSSID to an existing Saved WifiConfiguration 235 if (!didAssociate) { 236 // We couldn't associate the scan result to a Saved WifiConfiguration 237 // Hence it is untrusted 238 result.untrusted = true; 239 associatedConfig = mWifiConfigStore.associateWithConfiguration(result); 240 if (associatedConfig != null && associatedConfig.SSID != null) { 241 if (VDBG) { 242 logDbg("addToScanCache save associated config " 243 + associatedConfig.SSID + " with " + result.SSID 244 + " status " + associatedConfig.autoJoinStatus 245 + " reason " + associatedConfig.disableReason 246 + " tsp " + associatedConfig.blackListTimestamp 247 + " was " + (now - associatedConfig.blackListTimestamp)); 248 } 249 mWifiStateMachine.sendMessage( 250 WifiStateMachine.CMD_AUTO_SAVE_NETWORK, associatedConfig); 251 didAssociate = true; 252 } 253 } else { 254 // If the scan result has been blacklisted fir 18 hours -> unblacklist 255 if ((now - result.blackListTimestamp) > loseBlackListHardMilli) { 256 result.setAutoJoinStatus(ScanResult.ENABLED); 257 } 258 } 259 if (didAssociate) { 260 numScanResultsKnown++; 261 result.isAutoJoinCandidate ++; 262 } else { 263 result.isAutoJoinCandidate = 0; 264 } 265 } 266 267 if (unknownScanResults.size() != 0) { 268 NetworkKey[] newKeys = 269 unknownScanResults.toArray(new NetworkKey[unknownScanResults.size()]); 270 // Kick the score manager, we will get updated scores asynchronously 271 scoreManager.requestScores(newKeys); 272 } 273 return numScanResultsKnown; 274 } 275 276 void logDbg(String message) { 277 logDbg(message, false); 278 } 279 280 void logDbg(String message, boolean stackTrace) { 281 if (stackTrace) { 282 Log.e(TAG, message + " stack:" 283 + Thread.currentThread().getStackTrace()[2].getMethodName() + " - " 284 + Thread.currentThread().getStackTrace()[3].getMethodName() + " - " 285 + Thread.currentThread().getStackTrace()[4].getMethodName() + " - " 286 + Thread.currentThread().getStackTrace()[5].getMethodName()); 287 } else { 288 Log.e(TAG, message); 289 } 290 } 291 292 // Called directly from WifiStateMachine 293 int newSupplicantResults(boolean doAutoJoin) { 294 int numScanResultsKnown; 295 List<ScanResult> scanList = mWifiStateMachine.getScanResultsListNoCopyUnsync(); 296 numScanResultsKnown = addToScanCache(scanList); 297 ageScanResultsOut(mScanResultMaximumAge); 298 if (DBG) { 299 logDbg("newSupplicantResults size=" + Integer.valueOf(scanResultCache.size()) 300 + " known=" + numScanResultsKnown + " " 301 + doAutoJoin); 302 } 303 if (doAutoJoin) { 304 attemptAutoJoin(); 305 } 306 mWifiConfigStore.writeKnownNetworkHistory(false); 307 return numScanResultsKnown; 308 } 309 310 311 /** 312 * Not used at the moment 313 * should be a call back from WifiScanner HAL ?? 314 * this function is not hooked and working yet, it will receive scan results from WifiScanners 315 * with the list of IEs,then populate the capabilities by parsing the IEs and inject the scan 316 * results as normal. 317 */ 318 void newHalScanResults() { 319 List<ScanResult> scanList = null;//mWifiScanner.syncGetScanResultsList(); 320 String akm = WifiParser.parse_akm(null, null); 321 logDbg(akm); 322 addToScanCache(scanList); 323 ageScanResultsOut(0); 324 attemptAutoJoin(); 325 mWifiConfigStore.writeKnownNetworkHistory(false); 326 } 327 328 /** 329 * network link quality changed, called directly from WifiTrafficPoller, 330 * or by listening to Link Quality intent 331 */ 332 void linkQualitySignificantChange() { 333 attemptAutoJoin(); 334 } 335 336 /** 337 * compare a WifiConfiguration against the current network, return a delta score 338 * If not associated, and the candidate will always be better 339 * For instance if the candidate is a home network versus an unknown public wifi, 340 * the delta will be infinite, else compare Kepler scores etc… 341 * Negatve return values from this functions are meaningless per se, just trying to 342 * keep them distinct for debug purpose (i.e. -1, -2 etc...) 343 */ 344 private int compareNetwork(WifiConfiguration candidate, 345 String lastSelectedConfiguration) { 346 if (candidate == null) 347 return -3; 348 349 WifiConfiguration currentNetwork = mWifiStateMachine.getCurrentWifiConfiguration(); 350 if (currentNetwork == null) { 351 // Return any absurdly high score, if we are not connected there is no current 352 // network to... 353 return 1000; 354 } 355 356 if (candidate.configKey(true).equals(currentNetwork.configKey(true))) { 357 return -2; 358 } 359 360 if (DBG) { 361 logDbg("compareNetwork will compare " + candidate.configKey() 362 + " with current " + currentNetwork.configKey()); 363 } 364 int order = compareWifiConfigurations(currentNetwork, candidate); 365 366 // The lastSelectedConfiguration is the configuration the user has manually selected 367 // thru WifiPicker, or that a 3rd party app asked us to connect to via the 368 // enableNetwork with disableOthers=true WifiManager API 369 // As this is a direct user choice, we strongly prefer this configuration, 370 // hence give +/-100 371 if ((lastSelectedConfiguration != null) 372 && currentNetwork.configKey().equals(lastSelectedConfiguration)) { 373 // currentNetwork is the last selected configuration, 374 // so keep it above connect choices (+/-60) and 375 // above RSSI/scorer based selection of linked configuration (+/- 50) 376 // by reducing order by -100 377 order = order - 100; 378 if (VDBG) { 379 logDbg(" ...and prefers -100 " + currentNetwork.configKey() 380 + " over " + candidate.configKey() 381 + " because it is the last selected -> " 382 + Integer.toString(order)); 383 } 384 } else if ((lastSelectedConfiguration != null) 385 && candidate.configKey().equals(lastSelectedConfiguration)) { 386 // candidate is the last selected configuration, 387 // so keep it above connect choices (+/-60) and 388 // above RSSI/scorer based selection of linked configuration (+/- 50) 389 // by increasing order by +100 390 order = order + 100; 391 if (VDBG) { 392 logDbg(" ...and prefers +100 " + candidate.configKey() 393 + " over " + currentNetwork.configKey() 394 + " because it is the last selected -> " 395 + Integer.toString(order)); 396 } 397 } 398 399 return order; 400 } 401 402 /** 403 * update the network history fields fo that configuration 404 * - if userTriggered, we mark the configuration as "non selfAdded" since the user has seen it 405 * and took over management 406 * - if it is a "connect", remember which network were there at the point of the connect, so 407 * as those networks get a relative lower score than the selected configuration 408 * 409 * @param netId 410 * @param userTriggered : if the update come from WiFiManager 411 * @param connect : if the update includes a connect 412 */ 413 public void updateConfigurationHistory(int netId, boolean userTriggered, boolean connect) { 414 WifiConfiguration selected = mWifiConfigStore.getWifiConfiguration(netId); 415 if (selected == null) { 416 logDbg("updateConfigurationHistory nid=" + netId + " no selected configuration!"); 417 return; 418 } 419 420 if (selected.SSID == null) { 421 logDbg("updateConfigurationHistory nid=" + netId + 422 " no SSID in selected configuration!"); 423 return; 424 } 425 426 if (userTriggered) { 427 // Reenable autojoin for this network, 428 // since the user want to connect to this configuration 429 selected.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED); 430 selected.selfAdded = false; 431 selected.dirty = true; 432 } 433 434 if (DBG && userTriggered) { 435 if (selected.connectChoices != null) { 436 logDbg("updateConfigurationHistory will update " 437 + Integer.toString(netId) + " now: " 438 + Integer.toString(selected.connectChoices.size()) 439 + " uid=" + Integer.toString(selected.creatorUid), true); 440 } else { 441 logDbg("updateConfigurationHistory will update " 442 + Integer.toString(netId) 443 + " uid=" + Integer.toString(selected.creatorUid), true); 444 } 445 } 446 447 if (connect && userTriggered) { 448 boolean found = false; 449 int choice = 0; 450 int size = 0; 451 452 // Reset the triggered disabled count, because user wanted to connect to this 453 // configuration, and we were not. 454 selected.numUserTriggeredWifiDisableLowRSSI = 0; 455 selected.numUserTriggeredWifiDisableBadRSSI = 0; 456 selected.numUserTriggeredWifiDisableNotHighRSSI = 0; 457 selected.numUserTriggeredJoinAttempts++; 458 459 List<WifiConfiguration> networks = 460 mWifiConfigStore.getRecentConfiguredNetworks(12000, false); 461 if (networks != null) size = networks.size(); 462 logDbg("updateConfigurationHistory found " + size + " networks"); 463 if (networks != null) { 464 for (WifiConfiguration config : networks) { 465 if (DBG) { 466 logDbg("updateConfigurationHistory got " + config.SSID + " nid=" 467 + Integer.toString(config.networkId)); 468 } 469 470 if (selected.configKey(true).equals(config.configKey(true))) { 471 found = true; 472 continue; 473 } 474 475 // Compare RSSI values so as to evaluate the strength of the user preference 476 int order = compareWifiConfigurationsRSSI(config, selected, null); 477 478 if (order < -30) { 479 // Selected configuration is worse than the visible configuration 480 // hence register a strong choice so as autojoin cannot override this 481 // for instance, the user has select a network 482 // with 1 bar over a network with 3 bars... 483 choice = 60; 484 } else if (order < -20) { 485 choice = 50; 486 } else if (order < -10) { 487 choice = 40; 488 } else if (order < 20) { 489 // Selected configuration is about same or has a slightly better RSSI 490 // hence register a weaker choice, here a difference of at least +/-30 in 491 // RSSI comparison triggered by autoJoin will override the choice 492 choice = 30; 493 } else { 494 // Selected configuration is better than the visible configuration 495 // hence we do not know if the user prefers this configuration strongly 496 choice = 20; 497 } 498 499 // The selected configuration was preferred over a recently seen config 500 // hence remember the user's choice: 501 // add the recently seen config to the selected's connectChoices array 502 503 if (selected.connectChoices == null) { 504 selected.connectChoices = new HashMap<String, Integer>(); 505 } 506 507 logDbg("updateConfigurationHistory add a choice " + selected.configKey(true) 508 + " over " + config.configKey(true) 509 + " choice " + Integer.toString(choice)); 510 511 Integer currentChoice = selected.connectChoices.get(config.configKey(true)); 512 if (currentChoice != null) { 513 // User has made this choice multiple time in a row, so bump up a lot 514 choice += currentChoice.intValue(); 515 } 516 // Add the visible config to the selected's connect choice list 517 selected.connectChoices.put(config.configKey(true), choice); 518 519 if (config.connectChoices != null) { 520 if (VDBG) { 521 logDbg("updateConfigurationHistory will remove " 522 + selected.configKey(true) + " from " + config.configKey(true)); 523 } 524 // Remove the selected from the recently seen config's connectChoice list 525 config.connectChoices.remove(selected.configKey(true)); 526 527 if (selected.linkedConfigurations != null) { 528 // Remove the selected's linked configuration from the 529 // recently seen config's connectChoice list 530 for (String key : selected.linkedConfigurations.keySet()) { 531 config.connectChoices.remove(key); 532 } 533 } 534 } 535 } 536 if (found == false) { 537 // We haven't found the configuration that the user just selected in our 538 // scan cache. 539 // In that case we will need a new scan before attempting to connect to this 540 // configuration anyhow and thus we can process the scan results then. 541 logDbg("updateConfigurationHistory try to connect to an old network!! : " 542 + selected.configKey()); 543 } 544 545 if (selected.connectChoices != null) { 546 if (VDBG) 547 logDbg("updateConfigurationHistory " + Integer.toString(netId) 548 + " now: " + Integer.toString(selected.connectChoices.size())); 549 } 550 } 551 } 552 553 // TODO: write only if something changed 554 if (userTriggered || connect) { 555 mWifiConfigStore.writeKnownNetworkHistory(false); 556 } 557 } 558 559 int getConnectChoice(WifiConfiguration source, WifiConfiguration target) { 560 Integer choice = null; 561 if (source == null || target == null) { 562 return 0; 563 } 564 565 if (source.connectChoices != null 566 && source.connectChoices.containsKey(target.configKey(true))) { 567 choice = source.connectChoices.get(target.configKey(true)); 568 } else if (source.linkedConfigurations != null) { 569 for (String key : source.linkedConfigurations.keySet()) { 570 WifiConfiguration config = mWifiConfigStore.getWifiConfiguration(key); 571 if (config != null) { 572 if (config.connectChoices != null) { 573 choice = config.connectChoices.get(target.configKey(true)); 574 } 575 } 576 } 577 } 578 579 if (choice == null) { 580 //We didn't find the connect choice 581 return 0; 582 } else { 583 if (choice.intValue() < 0) { 584 choice = 20; // Compatibility with older files 585 } 586 return choice.intValue(); 587 } 588 } 589 590 int compareWifiConfigurationsFromVisibility(WifiConfiguration a, int aRssiBoost, 591 WifiConfiguration b, int bRssiBoost) { 592 593 int aRssiBoost5 = 0; // 5GHz RSSI boost to apply for purpose band selection (5GHz pref) 594 int bRssiBoost5 = 0; // 5GHz RSSI boost to apply for purpose band selection (5GHz pref) 595 596 int aScore = 0; 597 int bScore = 0; 598 599 boolean aPrefers5GHz = false; 600 boolean bPrefers5GHz = false; 601 602 /** 603 * Calculate a boost to apply to RSSI value of configuration we want to join on 5GHz: 604 * Boost RSSI value of 5GHz bands iff the base value is better than threshold, 605 * penalize the RSSI value of 5GHz band iff the base value is lower than threshold 606 * This implements band preference where we prefer 5GHz if RSSI5 is good enough, whereas 607 * we prefer 2.4GHz otherwise. 608 */ 609 aRssiBoost5 = rssiBoostFrom5GHzRssi(a.visibility.rssi5, a.configKey() + "->"); 610 bRssiBoost5 = rssiBoostFrom5GHzRssi(b.visibility.rssi5, b.configKey() + "->"); 611 612 // Select which band to use for a 613 if (a.visibility.rssi5 + aRssiBoost5 > a.visibility.rssi24) { 614 // Prefer a's 5GHz 615 aPrefers5GHz = true; 616 } 617 618 // Select which band to use for b 619 if (b.visibility.rssi5 + bRssiBoost5 > b.visibility.rssi24) { 620 // Prefer b's 5GHz 621 bPrefers5GHz = true; 622 } 623 624 if (aPrefers5GHz) { 625 if (bPrefers5GHz) { 626 // If both a and b are on 5GHz then we don't apply the 5GHz RSSI boost to either 627 // one, but directly compare the RSSI values, this improves stability, 628 // since the 5GHz RSSI boost can introduce large fluctuations 629 aScore = a.visibility.rssi5 + aRssiBoost; 630 } else { 631 // If only a is on 5GHz, then apply the 5GHz preference boost to a 632 aScore = a.visibility.rssi5 + aRssiBoost + aRssiBoost5; 633 } 634 } else { 635 aScore = a.visibility.rssi24 + aRssiBoost; 636 } 637 638 if (bPrefers5GHz) { 639 if (aPrefers5GHz) { 640 // If both a and b are on 5GHz then we don't apply the 5GHz RSSI boost to either 641 // one, but directly compare the RSSI values, this improves stability, 642 // since the 5GHz RSSI boost can introduce large fluctuations 643 bScore = b.visibility.rssi5 + bRssiBoost; 644 } else { 645 // If only b is on 5GHz, then apply the 5GHz preference boost to b 646 bScore = b.visibility.rssi5 + bRssiBoost + bRssiBoost5; 647 } 648 } else { 649 bScore = b.visibility.rssi24 + bRssiBoost; 650 } 651 if (VDBG) { 652 logDbg(" " + a.configKey() + " is5=" + aPrefers5GHz + " score=" + aScore 653 + " " + b.configKey() + " is5=" + bPrefers5GHz + " score=" + bScore); 654 } 655 656 // Debug only, record RSSI comparison parameters 657 if (a.visibility != null) { 658 a.visibility.score = aScore; 659 a.visibility.currentNetworkBoost = aRssiBoost; 660 a.visibility.bandPreferenceBoost = aRssiBoost5; 661 } 662 if (b.visibility != null) { 663 b.visibility.score = bScore; 664 b.visibility.currentNetworkBoost = bRssiBoost; 665 b.visibility.bandPreferenceBoost = bRssiBoost5; 666 } 667 668 // Compare a and b 669 // If a score is higher then a > b and the order is descending (negative) 670 // If b score is higher then a < b and the order is ascending (positive) 671 return bScore - aScore; 672 } 673 674 // Compare WifiConfiguration by RSSI, and return a comparison value in the range [-50, +50] 675 // The result represents "approximately" an RSSI difference measured in dBM 676 // Adjusted with various parameters: 677 // +) current network gets a +15 boost 678 // +) 5GHz signal, if they are strong enough, get a +15 or +25 boost, representing the 679 // fact that at short range we prefer 5GHz band as it is cleaner of interference and 680 // provides for wider channels 681 int compareWifiConfigurationsRSSI(WifiConfiguration a, WifiConfiguration b, 682 String currentConfiguration) { 683 int order = 0; 684 685 // Boost used so as to favor current config 686 int aRssiBoost = 0; 687 int bRssiBoost = 0; 688 689 int scoreA; 690 int scoreB; 691 692 // Retrieve the visibility 693 WifiConfiguration.Visibility astatus = a.visibility; 694 WifiConfiguration.Visibility bstatus = b.visibility; 695 if (astatus == null || bstatus == null) { 696 // Error visibility wasn't set 697 logDbg(" compareWifiConfigurations NULL band status!"); 698 return 0; 699 } 700 701 // Apply Hysteresis, boost RSSI of current configuration 702 if (null != currentConfiguration) { 703 if (a.configKey().equals(currentConfiguration)) { 704 aRssiBoost = mWifiConfigStore.currentNetworkBoost; 705 } else if (b.configKey().equals(currentConfiguration)) { 706 bRssiBoost = mWifiConfigStore.currentNetworkBoost; 707 } 708 } 709 710 if (VDBG) { 711 logDbg(" compareWifiConfigurationsRSSI: " + a.configKey() 712 + " rssi=" + Integer.toString(astatus.rssi24) 713 + "," + Integer.toString(astatus.rssi5) 714 + " boost=" + Integer.toString(aRssiBoost) 715 + " " + b.configKey() + " rssi=" 716 + Integer.toString(bstatus.rssi24) + "," 717 + Integer.toString(bstatus.rssi5) 718 + " boost=" + Integer.toString(bRssiBoost) 719 ); 720 } 721 722 order = compareWifiConfigurationsFromVisibility(a, aRssiBoost, b, bRssiBoost); 723 724 // Normalize the order to [-50, +50] 725 if (order > 50) order = 50; 726 else if (order < -50) order = -50; 727 728 if (VDBG) { 729 String prefer = " = "; 730 if (order > 0) { 731 prefer = " < "; // Ascending 732 } else if (order < 0) { 733 prefer = " > "; // Descending 734 } 735 logDbg(" compareWifiConfigurationsRSSI " + a.configKey() 736 + " rssi=(" + a.visibility.rssi24 737 + "," + a.visibility.rssi5 738 + ") num=(" + a.visibility.num24 739 + "," + a.visibility.num5 + ")" 740 + prefer + b.configKey() 741 + " rssi=(" + b.visibility.rssi24 742 + "," + b.visibility.rssi5 743 + ") num=(" + b.visibility.num24 744 + "," + b.visibility.num5 + ")" 745 + " -> " + order); 746 } 747 748 return order; 749 } 750 751 /** 752 * b/18490330 only use scorer for untrusted networks 753 * 754 int compareWifiConfigurationsWithScorer(WifiConfiguration a, WifiConfiguration b) { 755 756 boolean aIsActive = false; 757 boolean bIsActive = false; 758 759 // Apply Hysteresis : boost RSSI of current configuration before 760 // looking up the score 761 if (null != mCurrentConfigurationKey) { 762 if (a.configKey().equals(mCurrentConfigurationKey)) { 763 aIsActive = true; 764 } else if (b.configKey().equals(mCurrentConfigurationKey)) { 765 bIsActive = true; 766 } 767 } 768 int scoreA = getConfigNetworkScore(a, mScanResultAutoJoinAge, aIsActive); 769 int scoreB = getConfigNetworkScore(b, mScanResultAutoJoinAge, bIsActive); 770 771 // Both configurations need to have a score for the scorer to be used 772 // ...and the scores need to be different:-) 773 if (scoreA == WifiNetworkScoreCache.INVALID_NETWORK_SCORE 774 || scoreB == WifiNetworkScoreCache.INVALID_NETWORK_SCORE) { 775 if (VDBG) { 776 logDbg(" compareWifiConfigurationsWithScorer no-scores: " 777 + a.configKey() 778 + " " 779 + b.configKey()); 780 } 781 return 0; 782 } 783 784 if (VDBG) { 785 String prefer = " = "; 786 if (scoreA < scoreB) { 787 prefer = " < "; 788 } if (scoreA > scoreB) { 789 prefer = " > "; 790 } 791 logDbg(" compareWifiConfigurationsWithScorer " + a.configKey() 792 + " rssi=(" + a.visibility.rssi24 793 + "," + a.visibility.rssi5 794 + ") num=(" + a.visibility.num24 795 + "," + a.visibility.num5 + ")" 796 + " sc=" + scoreA 797 + prefer + b.configKey() 798 + " rssi=(" + b.visibility.rssi24 799 + "," + b.visibility.rssi5 800 + ") num=(" + b.visibility.num24 801 + "," + b.visibility.num5 + ")" 802 + " sc=" + scoreB 803 + " -> " + Integer.toString(scoreB - scoreA)); 804 } 805 806 // If scoreA > scoreB, the comparison is descending hence the return value is negative 807 return scoreB - scoreA; 808 } 809 */ 810 811 int compareWifiConfigurations(WifiConfiguration a, WifiConfiguration b) { 812 int order = 0; 813 boolean linked = false; 814 815 if ((a.linkedConfigurations != null) && (b.linkedConfigurations != null) 816 && (a.autoJoinStatus == WifiConfiguration.AUTO_JOIN_ENABLED) 817 && (b.autoJoinStatus == WifiConfiguration.AUTO_JOIN_ENABLED)) { 818 if ((a.linkedConfigurations.get(b.configKey(true)) != null) 819 && (b.linkedConfigurations.get(a.configKey(true)) != null)) { 820 linked = true; 821 } 822 } 823 824 if (a.ephemeral && b.ephemeral == false) { 825 if (VDBG) { 826 logDbg(" compareWifiConfigurations ephemeral and prefers " + b.configKey() 827 + " over " + a.configKey()); 828 } 829 return 1; // b is of higher priority - ascending 830 } 831 if (b.ephemeral && a.ephemeral == false) { 832 if (VDBG) { 833 logDbg(" compareWifiConfigurations ephemeral and prefers " + a.configKey() 834 + " over " + b.configKey()); 835 } 836 return -1; // a is of higher priority - descending 837 } 838 839 // Apply RSSI, in the range [-5, +5] 840 // after band adjustment, +n difference roughly corresponds to +10xn dBm 841 order = order + compareWifiConfigurationsRSSI(a, b, mCurrentConfigurationKey); 842 843 // If the configurations are not linked, compare by user's choice, only a 844 // very high RSSI difference can then override the choice 845 if (!linked) { 846 int choice; 847 848 choice = getConnectChoice(a, b); 849 if (choice > 0) { 850 // a is of higher priority - descending 851 order = order - choice; 852 if (VDBG) { 853 logDbg(" compareWifiConfigurations prefers " + a.configKey() 854 + " over " + b.configKey() 855 + " due to user choice of " + choice 856 + " order -> " + Integer.toString(order)); 857 } 858 if (a.visibility != null) { 859 a.visibility.lastChoiceBoost = choice; 860 a.visibility.lastChoiceConfig = b.configKey(); 861 } 862 } 863 864 choice = getConnectChoice(b, a); 865 if (choice > 0) { 866 // a is of lower priority - ascending 867 order = order + choice; 868 if (VDBG) { 869 logDbg(" compareWifiConfigurations prefers " + b.configKey() + " over " 870 + a.configKey() + " due to user choice of " + choice 871 + " order ->" + Integer.toString(order)); 872 } 873 if (b.visibility != null) { 874 b.visibility.lastChoiceBoost = choice; 875 b.visibility.lastChoiceConfig = a.configKey(); 876 } 877 } 878 } 879 880 if (order == 0) { 881 // We don't know anything - pick the last seen i.e. K behavior 882 // we should do this only for recently picked configurations 883 if (a.priority > b.priority) { 884 // a is of higher priority - descending 885 if (VDBG) { 886 logDbg(" compareWifiConfigurations prefers -1 " + a.configKey() + " over " 887 + b.configKey() + " due to priority"); 888 } 889 890 order = -1; 891 } else if (a.priority < b.priority) { 892 // a is of lower priority - ascending 893 if (VDBG) { 894 logDbg(" compareWifiConfigurations prefers +1 " + b.configKey() + " over " 895 + a.configKey() + " due to priority"); 896 } 897 order = 1; 898 } 899 } 900 901 String sorder = " == "; 902 if (order > 0) { 903 sorder = " < "; 904 } else if (order < 0) { 905 sorder = " > "; 906 } 907 908 if (VDBG) { 909 logDbg("compareWifiConfigurations: " + a.configKey() + sorder 910 + b.configKey() + " order " + Integer.toString(order)); 911 } 912 913 return order; 914 } 915 916 boolean isBadCandidate(int rssi5, int rssi24) { 917 return (rssi5 < -80 && rssi24 < -90); 918 } 919 920 /* 921 int compareWifiConfigurationsTop(WifiConfiguration a, WifiConfiguration b) { 922 int scorerOrder = compareWifiConfigurationsWithScorer(a, b); 923 int order = compareWifiConfigurations(a, b); 924 925 if (scorerOrder * order < 0) { 926 if (VDBG) { 927 logDbg(" -> compareWifiConfigurationsTop: " + 928 "scorer override " + scorerOrder + " " + order); 929 } 930 // For debugging purpose, remember that an override happened 931 // during that autojoin Attempt 932 didOverride = true; 933 a.numScorerOverride++; 934 b.numScorerOverride++; 935 } 936 937 if (scorerOrder != 0) { 938 // If the scorer came up with a result then use the scorer's result, else use 939 // the order provided by the base comparison function 940 order = scorerOrder; 941 } 942 return order; 943 } 944 */ 945 946 public int rssiBoostFrom5GHzRssi(int rssi, String dbg) { 947 if (!mWifiConfigStore.enable5GHzPreference) { 948 return 0; 949 } 950 if (rssi 951 > mWifiConfigStore.bandPreferenceBoostThreshold5) { 952 // Boost by 2 dB for each point 953 // Start boosting at -65 954 // Boost by 20 if above -55 955 // Boost by 40 if abore -45 956 int boost = mWifiConfigStore.bandPreferenceBoostFactor5 957 *(rssi - mWifiConfigStore.bandPreferenceBoostThreshold5); 958 if (boost > 50) { 959 // 50 dB boost allows jumping from 2.4 to 5GHz 960 // consistently 961 boost = 50; 962 } 963 if (VDBG && dbg != null) { 964 logDbg(" " + dbg + ": rssi5 " + rssi + " 5GHz-boost " + boost); 965 } 966 return boost; 967 } 968 969 if (rssi 970 < mWifiConfigStore.bandPreferencePenaltyThreshold5) { 971 // penalize if < -75 972 int boost = mWifiConfigStore.bandPreferencePenaltyFactor5 973 *(rssi - mWifiConfigStore.bandPreferencePenaltyThreshold5); 974 return boost; 975 } 976 return 0; 977 } 978 /** 979 * attemptRoam() function implements the core of the same SSID switching algorithm 980 * 981 * Run thru all recent scan result of a WifiConfiguration and select the 982 * best one. 983 */ 984 public ScanResult attemptRoam(ScanResult a, 985 WifiConfiguration current, int age, String currentBSSID) { 986 if (current == null) { 987 if (VDBG) { 988 logDbg("attemptRoam not associated"); 989 } 990 return a; 991 } 992 if (current.scanResultCache == null) { 993 if (VDBG) { 994 logDbg("attemptRoam no scan cache"); 995 } 996 return a; 997 } 998 if (current.scanResultCache.size() > 6) { 999 if (VDBG) { 1000 logDbg("attemptRoam scan cache size " 1001 + current.scanResultCache.size() + " --> bail"); 1002 } 1003 // Implement same SSID roaming only for configurations 1004 // that have less than 4 BSSIDs 1005 return a; 1006 } 1007 1008 if (current.BSSID != null && !current.BSSID.equals("any")) { 1009 if (DBG) { 1010 logDbg("attemptRoam() BSSID is set " 1011 + current.BSSID + " -> bail"); 1012 } 1013 return a; 1014 } 1015 1016 // Determine which BSSID we want to associate to, taking account 1017 // relative strength of 5 and 2.4 GHz BSSIDs 1018 long nowMs = System.currentTimeMillis(); 1019 1020 for (ScanResult b : current.scanResultCache.values()) { 1021 int bRssiBoost5 = 0; 1022 int aRssiBoost5 = 0; 1023 int bRssiBoost = 0; 1024 int aRssiBoost = 0; 1025 if ((b.seen == 0) || (b.BSSID == null) 1026 || ((nowMs - b.seen) > age) 1027 || b.autoJoinStatus != ScanResult.ENABLED 1028 || b.numIpConfigFailures > 8) { 1029 continue; 1030 } 1031 1032 // Pick first one 1033 if (a == null) { 1034 a = b; 1035 continue; 1036 } 1037 1038 if (b.numIpConfigFailures < (a.numIpConfigFailures - 1)) { 1039 // Prefer a BSSID that doesn't have less number of Ip config failures 1040 logDbg("attemptRoam: " 1041 + b.BSSID + " rssi=" + b.level + " ipfail=" +b.numIpConfigFailures 1042 + " freq=" + b.frequency 1043 + " > " 1044 + a.BSSID + " rssi=" + a.level + " ipfail=" +a.numIpConfigFailures 1045 + " freq=" + a.frequency); 1046 a = b; 1047 continue; 1048 } 1049 1050 // Apply hysteresis: we favor the currentBSSID by giving it a boost 1051 if (currentBSSID != null && currentBSSID.equals(b.BSSID)) { 1052 // Reduce the benefit of hysteresis if RSSI <= -75 1053 if (b.level <= mWifiConfigStore.bandPreferencePenaltyThreshold5) { 1054 bRssiBoost = mWifiConfigStore.associatedHysteresisLow; 1055 } else { 1056 bRssiBoost = mWifiConfigStore.associatedHysteresisHigh; 1057 } 1058 } 1059 if (currentBSSID != null && currentBSSID.equals(a.BSSID)) { 1060 if (a.level <= mWifiConfigStore.bandPreferencePenaltyThreshold5) { 1061 // Reduce the benefit of hysteresis if RSSI <= -75 1062 aRssiBoost = mWifiConfigStore.associatedHysteresisLow; 1063 } else { 1064 aRssiBoost = mWifiConfigStore.associatedHysteresisHigh; 1065 } 1066 } 1067 1068 // Favor 5GHz: give a boost to 5GHz BSSIDs, with a slightly progressive curve 1069 // Boost the BSSID if it is on 5GHz, above a threshold 1070 // But penalize it if it is on 5GHz and below threshold 1071 // 1072 // With he current threshold values, 5GHz network with RSSI above -55 1073 // Are given a boost of 30DB which is enough to overcome the current BSSID 1074 // hysteresis (+14) plus 2.4/5 GHz signal strength difference on most cases 1075 // 1076 // The "current BSSID" Boost must be added to the BSSID's level so as to introduce\ 1077 // soem amount of hysteresis 1078 if (b.is5GHz()) { 1079 bRssiBoost5 = rssiBoostFrom5GHzRssi(b.level + bRssiBoost, b.BSSID); 1080 } 1081 if (a.is5GHz()) { 1082 aRssiBoost5 = rssiBoostFrom5GHzRssi(a.level + aRssiBoost, a.BSSID); 1083 } 1084 1085 if (VDBG) { 1086 String comp = " < "; 1087 if (b.level + bRssiBoost + bRssiBoost5 > a.level +aRssiBoost + aRssiBoost5) { 1088 comp = " > "; 1089 } 1090 logDbg("attemptRoam: " 1091 + b.BSSID + " rssi=" + b.level + " boost=" + Integer.toString(bRssiBoost) 1092 + "/" + Integer.toString(bRssiBoost5) + " freq=" + b.frequency 1093 + comp 1094 + a.BSSID + " rssi=" + a.level + " boost=" + Integer.toString(aRssiBoost) 1095 + "/" + Integer.toString(aRssiBoost5) + " freq=" + a.frequency); 1096 } 1097 1098 // Compare the RSSIs after applying the hysteresis boost and the 5GHz 1099 // boost if applicable 1100 if (b.level + bRssiBoost + bRssiBoost5 > a.level +aRssiBoost + aRssiBoost5) { 1101 // b is the better BSSID 1102 a = b; 1103 } 1104 } 1105 if (a != null) { 1106 if (VDBG) { 1107 StringBuilder sb = new StringBuilder(); 1108 sb.append("attemptRoam: " + current.configKey() + 1109 " Found " + a.BSSID + " rssi=" + a.level + " freq=" + a.frequency); 1110 if (currentBSSID != null) { 1111 sb.append(" Current: " + currentBSSID); 1112 } 1113 sb.append("\n"); 1114 logDbg(sb.toString()); 1115 } 1116 } 1117 return a; 1118 } 1119 1120 /** 1121 * getNetworkScore() 1122 * 1123 * if scorer is present, get the network score of a WifiConfiguration 1124 * 1125 * Note: this should be merge with setVisibility 1126 * 1127 * @param config 1128 * @return score 1129 */ 1130 int getConfigNetworkScore(WifiConfiguration config, int age, boolean isActive) { 1131 1132 if (mNetworkScoreCache == null) { 1133 if (VDBG) { 1134 logDbg(" getConfigNetworkScore for " + config.configKey() 1135 + " -> no scorer, hence no scores"); 1136 } 1137 return WifiNetworkScoreCache.INVALID_NETWORK_SCORE; 1138 } 1139 if (config.scanResultCache == null) { 1140 if (VDBG) { 1141 logDbg(" getConfigNetworkScore for " + config.configKey() 1142 + " -> no scan cache"); 1143 } 1144 return WifiNetworkScoreCache.INVALID_NETWORK_SCORE; 1145 } 1146 1147 // Get current date 1148 long nowMs = System.currentTimeMillis(); 1149 1150 int startScore = -10000; 1151 1152 // Run thru all cached scan results 1153 for (ScanResult result : config.scanResultCache.values()) { 1154 if ((nowMs - result.seen) < age) { 1155 int sc = mNetworkScoreCache.getNetworkScore(result, isActive); 1156 if (sc > startScore) { 1157 startScore = sc; 1158 } 1159 } 1160 } 1161 if (startScore == -10000) { 1162 startScore = WifiNetworkScoreCache.INVALID_NETWORK_SCORE; 1163 } 1164 if (VDBG) { 1165 if (startScore == WifiNetworkScoreCache.INVALID_NETWORK_SCORE) { 1166 logDbg(" getConfigNetworkScore for " + config.configKey() 1167 + " -> no available score"); 1168 } else { 1169 logDbg(" getConfigNetworkScore for " + config.configKey() 1170 + " isActive=" + isActive 1171 + " score = " + Integer.toString(startScore)); 1172 } 1173 } 1174 1175 return startScore; 1176 } 1177 1178 /** 1179 * Set whether connections to untrusted connections are allowed. 1180 */ 1181 void setAllowUntrustedConnections(boolean allow) { 1182 boolean changed = mAllowUntrustedConnections != allow; 1183 mAllowUntrustedConnections = allow; 1184 if (changed) { 1185 // Trigger a scan so as to reattempt autojoin 1186 mWifiStateMachine.startScanForUntrustedSettingChange(); 1187 } 1188 } 1189 1190 private boolean isOpenNetwork(ScanResult result) { 1191 return !result.capabilities.contains("WEP") && 1192 !result.capabilities.contains("PSK") && 1193 !result.capabilities.contains("EAP"); 1194 } 1195 1196 private boolean haveRecentlySeenScoredBssid(WifiConfiguration config) { 1197 long ephemeralOutOfRangeTimeoutMs = Settings.Global.getLong( 1198 mContext.getContentResolver(), 1199 Settings.Global.WIFI_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS, 1200 DEFAULT_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS); 1201 1202 // Check whether the currently selected network has a score curve. If 1203 // ephemeralOutOfRangeTimeoutMs is <= 0, then this is all we check, and we stop here. 1204 // Otherwise, we stop here if the currently selected network has a score. If it doesn't, we 1205 // keep going - it could be that another BSSID is in range (has been seen recently) which 1206 // has a score, even if the one we're immediately connected to doesn't. 1207 ScanResult currentScanResult = mWifiStateMachine.getCurrentScanResult(); 1208 boolean currentNetworkHasScoreCurve = mNetworkScoreCache.hasScoreCurve(currentScanResult); 1209 if (ephemeralOutOfRangeTimeoutMs <= 0 || currentNetworkHasScoreCurve) { 1210 if (DBG) { 1211 if (currentNetworkHasScoreCurve) { 1212 logDbg("Current network has a score curve, keeping network: " 1213 + currentScanResult); 1214 } else { 1215 logDbg("Current network has no score curve, giving up: " + config.SSID); 1216 } 1217 } 1218 return currentNetworkHasScoreCurve; 1219 } 1220 1221 if (config.scanResultCache == null || config.scanResultCache.isEmpty()) { 1222 return false; 1223 } 1224 1225 long currentTimeMs = System.currentTimeMillis(); 1226 for (ScanResult result : config.scanResultCache.values()) { 1227 if (currentTimeMs > result.seen 1228 && currentTimeMs - result.seen < ephemeralOutOfRangeTimeoutMs 1229 && mNetworkScoreCache.hasScoreCurve(result)) { 1230 if (DBG) { 1231 logDbg("Found scored BSSID, keeping network: " + result.BSSID); 1232 } 1233 return true; 1234 } 1235 } 1236 1237 if (DBG) { 1238 logDbg("No recently scored BSSID found, giving up connection: " + config.SSID); 1239 } 1240 return false; 1241 } 1242 1243 /** 1244 * attemptAutoJoin() function implements the core of the a network switching algorithm 1245 * Return false if no acceptable networks were found. 1246 */ 1247 boolean attemptAutoJoin() { 1248 boolean found = false; 1249 didOverride = false; 1250 didBailDueToWeakRssi = false; 1251 int networkSwitchType = AUTO_JOIN_IDLE; 1252 1253 long now = System.currentTimeMillis(); 1254 1255 String lastSelectedConfiguration = mWifiConfigStore.getLastSelectedConfiguration(); 1256 1257 // Reset the currentConfiguration Key, and set it only if WifiStateMachine and 1258 // supplicant agree 1259 mCurrentConfigurationKey = null; 1260 WifiConfiguration currentConfiguration = mWifiStateMachine.getCurrentWifiConfiguration(); 1261 1262 WifiConfiguration candidate = null; 1263 1264 // Obtain the subset of recently seen networks 1265 List<WifiConfiguration> list = 1266 mWifiConfigStore.getRecentConfiguredNetworks(mScanResultAutoJoinAge, false); 1267 if (list == null) { 1268 if (VDBG) logDbg("attemptAutoJoin nothing known=" + 1269 mWifiConfigStore.getconfiguredNetworkSize()); 1270 return false; 1271 } 1272 1273 // Find the currently connected network: ask the supplicant directly 1274 String val = mWifiNative.status(true); 1275 String status[] = val.split("\\r?\\n"); 1276 if (VDBG) { 1277 logDbg("attemptAutoJoin() status=" + val + " split=" 1278 + Integer.toString(status.length)); 1279 } 1280 1281 int supplicantNetId = -1; 1282 for (String key : status) { 1283 if (key.regionMatches(0, "id=", 0, 3)) { 1284 int idx = 3; 1285 supplicantNetId = 0; 1286 while (idx < key.length()) { 1287 char c = key.charAt(idx); 1288 1289 if ((c >= 0x30) && (c <= 0x39)) { 1290 supplicantNetId *= 10; 1291 supplicantNetId += c - 0x30; 1292 idx++; 1293 } else { 1294 break; 1295 } 1296 } 1297 } else if (key.contains("wpa_state=ASSOCIATING") 1298 || key.contains("wpa_state=ASSOCIATED") 1299 || key.contains("wpa_state=FOUR_WAY_HANDSHAKE") 1300 || key.contains("wpa_state=GROUP_KEY_HANDSHAKE")) { 1301 if (DBG) { 1302 logDbg("attemptAutoJoin: bail out due to sup state " + key); 1303 } 1304 // After WifiStateMachine ask the supplicant to associate or reconnect 1305 // we might still obtain scan results from supplicant 1306 // however the supplicant state in the mWifiInfo and supplicant state tracker 1307 // are updated when we get the supplicant state change message which can be 1308 // processed after the SCAN_RESULT message, so at this point the framework doesn't 1309 // know that supplicant is ASSOCIATING. 1310 // A good fix for this race condition would be for the WifiStateMachine to add 1311 // a new transient state where it expects to get the supplicant message indicating 1312 // that it started the association process and within which critical operations 1313 // like autojoin should be deleted. 1314 1315 // This transient state would remove the need for the roam Wathchdog which 1316 // basically does that. 1317 1318 // At the moment, we just query the supplicant state synchronously with the 1319 // mWifiNative.status() command, which allow us to know that 1320 // supplicant has started association process, even though we didnt yet get the 1321 // SUPPLICANT_STATE_CHANGE message. 1322 return false; 1323 } 1324 } 1325 if (DBG) { 1326 String conf = ""; 1327 String last = ""; 1328 if (currentConfiguration != null) { 1329 conf = " current=" + currentConfiguration.configKey(); 1330 } 1331 if (lastSelectedConfiguration != null) { 1332 last = " last=" + lastSelectedConfiguration; 1333 } 1334 logDbg("attemptAutoJoin() num recent config " + Integer.toString(list.size()) 1335 + conf + last 1336 + " ---> suppNetId=" + Integer.toString(supplicantNetId)); 1337 } 1338 1339 if (currentConfiguration != null) { 1340 if (supplicantNetId != currentConfiguration.networkId 1341 // https://b.corp.google.com/issue?id=16484607 1342 // mark this condition as an error only if the mismatched networkId are valid 1343 && supplicantNetId != WifiConfiguration.INVALID_NETWORK_ID 1344 && currentConfiguration.networkId != WifiConfiguration.INVALID_NETWORK_ID) { 1345 logDbg("attemptAutoJoin() ERROR wpa_supplicant out of sync nid=" 1346 + Integer.toString(supplicantNetId) + " WifiStateMachine=" 1347 + Integer.toString(currentConfiguration.networkId)); 1348 mWifiStateMachine.disconnectCommand(); 1349 return false; 1350 } else if (currentConfiguration.ephemeral && (!mAllowUntrustedConnections || 1351 !haveRecentlySeenScoredBssid(currentConfiguration))) { 1352 // The current connection is untrusted (the framework added it), but we're either 1353 // no longer allowed to connect to such networks, the score has been nullified 1354 // since we connected, or the scored BSSID has gone out of range. 1355 // Drop the current connection and perform the rest of autojoin. 1356 logDbg("attemptAutoJoin() disconnecting from unwanted ephemeral network"); 1357 mWifiStateMachine.disconnectCommand(Process.WIFI_UID, 1358 mAllowUntrustedConnections ? 1 : 0); 1359 return false; 1360 } else { 1361 mCurrentConfigurationKey = currentConfiguration.configKey(); 1362 } 1363 } else { 1364 if (supplicantNetId != WifiConfiguration.INVALID_NETWORK_ID) { 1365 // Maybe in the process of associating, skip this attempt 1366 return false; 1367 } 1368 } 1369 1370 int currentNetId = -1; 1371 if (currentConfiguration != null) { 1372 // If we are associated to a configuration, it will 1373 // be compared thru the compareNetwork function 1374 currentNetId = currentConfiguration.networkId; 1375 } 1376 1377 /** 1378 * Run thru all visible configurations without looking at the one we 1379 * are currently associated to 1380 * select Best Network candidate from known WifiConfigurations 1381 */ 1382 for (WifiConfiguration config : list) { 1383 if (config.SSID == null) { 1384 continue; 1385 } 1386 1387 if (config.autoJoinStatus >= 1388 WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE) { 1389 // Wait for 5 minutes before reenabling config that have known, 1390 // repeated connection or DHCP failures 1391 if (config.disableReason == WifiConfiguration.DISABLED_DHCP_FAILURE 1392 || config.disableReason 1393 == WifiConfiguration.DISABLED_ASSOCIATION_REJECT 1394 || config.disableReason 1395 == WifiConfiguration.DISABLED_AUTH_FAILURE) { 1396 if (config.blackListTimestamp == 0 1397 || (config.blackListTimestamp > now)) { 1398 // Sanitize the timestamp 1399 config.blackListTimestamp = now; 1400 } 1401 if ((now - config.blackListTimestamp) > 1402 mWifiConfigStore.wifiConfigBlacklistMinTimeMilli) { 1403 // Re-enable the WifiConfiguration 1404 config.status = WifiConfiguration.Status.ENABLED; 1405 1406 // Reset the blacklist condition 1407 config.numConnectionFailures = 0; 1408 config.numIpConfigFailures = 0; 1409 config.numAuthFailures = 0; 1410 config.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED); 1411 1412 config.dirty = true; 1413 } else { 1414 if (VDBG) { 1415 long delay = mWifiConfigStore.wifiConfigBlacklistMinTimeMilli 1416 - (now - config.blackListTimestamp); 1417 logDbg("attemptautoJoin " + config.configKey() 1418 + " dont unblacklist yet, waiting for " 1419 + delay + " ms"); 1420 } 1421 } 1422 } 1423 // Avoid networks disabled because of AUTH failure altogether 1424 if (DBG) { 1425 logDbg("attemptAutoJoin skip candidate due to auto join status " 1426 + Integer.toString(config.autoJoinStatus) + " key " 1427 + config.configKey(true) 1428 + " reason " + config.disableReason); 1429 } 1430 continue; 1431 } 1432 1433 // Try to un-blacklist based on elapsed time 1434 if (config.blackListTimestamp > 0) { 1435 if (now < config.blackListTimestamp) { 1436 /** 1437 * looks like there was a change in the system clock since we black listed, and 1438 * timestamp is not meaningful anymore, hence lose it. 1439 * this event should be rare enough so that we still want to lose the black list 1440 */ 1441 config.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED); 1442 } else { 1443 if ((now - config.blackListTimestamp) > loseBlackListHardMilli) { 1444 // Reenable it after 18 hours, i.e. next day 1445 config.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED); 1446 } else if ((now - config.blackListTimestamp) > loseBlackListSoftMilli) { 1447 // Lose blacklisting due to bad link 1448 config.setAutoJoinStatus(config.autoJoinStatus - 8); 1449 } 1450 } 1451 } 1452 1453 // Try to unblacklist based on good visibility 1454 if (config.visibility.rssi5 < mWifiConfigStore.thresholdUnblacklistThreshold5Soft 1455 && config.visibility.rssi24 1456 < mWifiConfigStore.thresholdUnblacklistThreshold24Soft) { 1457 if (DBG) { 1458 logDbg("attemptAutoJoin do not unblacklist due to low visibility " 1459 + config.autoJoinStatus 1460 + " key " + config.configKey(true) 1461 + " rssi=(" + config.visibility.rssi24 1462 + "," + config.visibility.rssi5 1463 + ") num=(" + config.visibility.num24 1464 + "," + config.visibility.num5 + ")"); 1465 } 1466 } else if (config.visibility.rssi5 < mWifiConfigStore.thresholdUnblacklistThreshold5Hard 1467 && config.visibility.rssi24 1468 < mWifiConfigStore.thresholdUnblacklistThreshold24Hard) { 1469 // If the network is simply temporary disabled, don't allow reconnect until 1470 // RSSI becomes good enough 1471 config.setAutoJoinStatus(config.autoJoinStatus - 1); 1472 if (DBG) { 1473 logDbg("attemptAutoJoin good candidate seen, bumped soft -> status=" 1474 + config.autoJoinStatus 1475 + " " + config.configKey(true) + " rssi=(" 1476 + config.visibility.rssi24 + "," + config.visibility.rssi5 1477 + ") num=(" + config.visibility.num24 1478 + "," + config.visibility.num5 + ")"); 1479 } 1480 } else { 1481 config.setAutoJoinStatus(config.autoJoinStatus - 3); 1482 if (DBG) { 1483 logDbg("attemptAutoJoin good candidate seen, bumped hard -> status=" 1484 + config.autoJoinStatus 1485 + " " + config.configKey(true) + " rssi=(" 1486 + config.visibility.rssi24 + "," + config.visibility.rssi5 1487 + ") num=(" + config.visibility.num24 1488 + "," + config.visibility.num5 + ")"); 1489 } 1490 } 1491 1492 if (config.autoJoinStatus >= 1493 WifiConfiguration.AUTO_JOIN_TEMPORARY_DISABLED) { 1494 // Network is blacklisted, skip 1495 if (DBG) { 1496 logDbg("attemptAutoJoin skip blacklisted -> status=" 1497 + config.autoJoinStatus 1498 + " " + config.configKey(true) + " rssi=(" 1499 + config.visibility.rssi24 + "," + config.visibility.rssi5 1500 + ") num=(" + config.visibility.num24 1501 + "," + config.visibility.num5 + ")"); 1502 } 1503 continue; 1504 } 1505 if (config.networkId == currentNetId) { 1506 if (DBG) { 1507 logDbg("attemptAutoJoin skip current candidate " 1508 + Integer.toString(currentNetId) 1509 + " key " + config.configKey(true)); 1510 } 1511 continue; 1512 } 1513 1514 boolean isLastSelected = false; 1515 if (lastSelectedConfiguration != null && 1516 config.configKey().equals(lastSelectedConfiguration)) { 1517 isLastSelected = true; 1518 } 1519 1520 if (config.visibility == null) { 1521 continue; 1522 } 1523 1524 if (config.lastRoamingFailure != 0 1525 && currentConfiguration != null 1526 && (lastSelectedConfiguration == null 1527 || !config.configKey().equals(lastSelectedConfiguration))) { 1528 // Apply blacklisting for roaming to this config if: 1529 // - the target config had a recent roaming failure 1530 // - we are currently associated 1531 // - the target config is not the last selected 1532 if (now > config.lastRoamingFailure 1533 && (now - config.lastRoamingFailure) 1534 < config.roamingFailureBlackListTimeMilli) { 1535 if (DBG) { 1536 logDbg("compareNetwork not switching to " + config.configKey() 1537 + " from current " + currentConfiguration.configKey() 1538 + " because it is blacklisted due to roam failure, " 1539 + " blacklist remain time = " 1540 + (now - config.lastRoamingFailure) + " ms"); 1541 } 1542 continue; 1543 } 1544 } 1545 1546 int boost = config.autoJoinUseAggressiveJoinAttemptThreshold + weakRssiBailCount; 1547 if ((config.visibility.rssi5 + boost) 1548 < mWifiConfigStore.thresholdInitialAutoJoinAttemptMin5RSSI 1549 && (config.visibility.rssi24 + boost) 1550 < mWifiConfigStore.thresholdInitialAutoJoinAttemptMin24RSSI) { 1551 if (DBG) { 1552 logDbg("attemptAutoJoin skip due to low visibility -> status=" 1553 + config.autoJoinStatus 1554 + " key " + config.configKey(true) + " rssi=" 1555 + config.visibility.rssi24 + ", " + config.visibility.rssi5 1556 + " num=" + config.visibility.num24 1557 + ", " + config.visibility.num5); 1558 } 1559 1560 // Don't try to autojoin a network that is too far but 1561 // If that configuration is a user's choice however, try anyway 1562 if (!isLastSelected) { 1563 config.autoJoinBailedDueToLowRssi = true; 1564 didBailDueToWeakRssi = true; 1565 continue; 1566 } else { 1567 // Next time, try to be a bit more aggressive in auto-joining 1568 if (config.autoJoinUseAggressiveJoinAttemptThreshold 1569 < WifiConfiguration.MAX_INITIAL_AUTO_JOIN_RSSI_BOOST 1570 && config.autoJoinBailedDueToLowRssi) { 1571 config.autoJoinUseAggressiveJoinAttemptThreshold += 4; 1572 } 1573 } 1574 } 1575 if (config.numNoInternetAccessReports > 0 1576 && !isLastSelected 1577 && !config.validatedInternetAccess) { 1578 // Avoid autoJoining this network because last time we used it, it didn't 1579 // have internet access, and we never manage to validate internet access on this 1580 // network configuration 1581 if (DBG) { 1582 logDbg("attemptAutoJoin skip candidate due to no InternetAccess " 1583 + config.configKey(true) 1584 + " num reports " + config.numNoInternetAccessReports); 1585 } 1586 continue; 1587 } 1588 1589 if (DBG) { 1590 String cur = ""; 1591 if (candidate != null) { 1592 cur = " current candidate " + candidate.configKey(); 1593 } 1594 logDbg("attemptAutoJoin trying id=" 1595 + Integer.toString(config.networkId) + " " 1596 + config.configKey(true) 1597 + " status=" + config.autoJoinStatus 1598 + cur); 1599 } 1600 1601 if (candidate == null) { 1602 candidate = config; 1603 } else { 1604 if (VDBG) { 1605 logDbg("attemptAutoJoin will compare candidate " + candidate.configKey() 1606 + " with " + config.configKey()); 1607 } 1608 int order = compareWifiConfigurations(candidate, config); 1609 1610 // The lastSelectedConfiguration is the configuration the user has manually selected 1611 // thru WifiPicker, or that a 3rd party app asked us to connect to via the 1612 // enableNetwork with disableOthers=true WifiManager API 1613 // As this is a direct user choice, we strongly prefer this configuration, 1614 // hence give +/-100 1615 if ((lastSelectedConfiguration != null) 1616 && candidate.configKey().equals(lastSelectedConfiguration)) { 1617 // candidate is the last selected configuration, 1618 // so keep it above connect choices (+/-60) and 1619 // above RSSI/scorer based selection of linked configuration (+/- 50) 1620 // by reducing order by -100 1621 order = order - 100; 1622 if (VDBG) { 1623 logDbg(" ...and prefers -100 " + candidate.configKey() 1624 + " over " + config.configKey() 1625 + " because it is the last selected -> " 1626 + Integer.toString(order)); 1627 } 1628 } else if ((lastSelectedConfiguration != null) 1629 && config.configKey().equals(lastSelectedConfiguration)) { 1630 // config is the last selected configuration, 1631 // so keep it above connect choices (+/-60) and 1632 // above RSSI/scorer based selection of linked configuration (+/- 50) 1633 // by increasing order by +100 1634 order = order + 100; 1635 if (VDBG) { 1636 logDbg(" ...and prefers +100 " + config.configKey() 1637 + " over " + candidate.configKey() 1638 + " because it is the last selected -> " 1639 + Integer.toString(order)); 1640 } 1641 } 1642 1643 if (order > 0) { 1644 // Ascending : candidate < config 1645 candidate = config; 1646 } 1647 } 1648 } 1649 1650 // Now, go thru scan result to try finding a better untrusted network 1651 if (mNetworkScoreCache != null && mAllowUntrustedConnections) { 1652 int rssi5 = WifiConfiguration.INVALID_RSSI; 1653 int rssi24 = WifiConfiguration.INVALID_RSSI; 1654 if (candidate != null) { 1655 rssi5 = candidate.visibility.rssi5; 1656 rssi24 = candidate.visibility.rssi24; 1657 } 1658 1659 // Get current date 1660 long nowMs = System.currentTimeMillis(); 1661 int currentScore = -10000; 1662 // The untrusted network with highest score 1663 ScanResult untrustedCandidate = null; 1664 // Look for untrusted scored network only if the current candidate is bad 1665 if (isBadCandidate(rssi24, rssi5)) { 1666 for (ScanResult result : scanResultCache.values()) { 1667 // We look only at untrusted networks with a valid SSID 1668 // A trusted result would have been looked at thru it's Wificonfiguration 1669 if (TextUtils.isEmpty(result.SSID) || !result.untrusted || 1670 !isOpenNetwork(result)) { 1671 continue; 1672 } 1673 String quotedSSID = "\"" + result.SSID + "\""; 1674 if (mWifiConfigStore.mDeletedEphemeralSSIDs.contains(quotedSSID)) { 1675 // SSID had been Forgotten by user, then don't score it 1676 continue; 1677 } 1678 if ((nowMs - result.seen) < mScanResultAutoJoinAge) { 1679 // Increment usage count for the network 1680 mWifiConnectionStatistics.incrementOrAddUntrusted(quotedSSID, 0, 1); 1681 1682 boolean isActiveNetwork = currentConfiguration != null 1683 && currentConfiguration.SSID.equals(quotedSSID); 1684 int score = mNetworkScoreCache.getNetworkScore(result, isActiveNetwork); 1685 if (score != WifiNetworkScoreCache.INVALID_NETWORK_SCORE 1686 && score > currentScore) { 1687 // Highest score: Select this candidate 1688 currentScore = score; 1689 untrustedCandidate = result; 1690 if (VDBG) { 1691 logDbg("AutoJoinController: found untrusted candidate " 1692 + result.SSID 1693 + " RSSI=" + result.level 1694 + " freq=" + result.frequency 1695 + " score=" + score); 1696 } 1697 } 1698 } 1699 } 1700 } 1701 if (untrustedCandidate != null) { 1702 // At this point, we have an untrusted network candidate. 1703 // Create the new ephemeral configuration and see if we should switch over 1704 candidate = 1705 mWifiConfigStore.wifiConfigurationFromScanResult(untrustedCandidate); 1706 candidate.allowedKeyManagement.set(KeyMgmt.NONE); 1707 candidate.ephemeral = true; 1708 } 1709 } 1710 1711 long lastUnwanted = 1712 System.currentTimeMillis() 1713 - mWifiConfigStore.lastUnwantedNetworkDisconnectTimestamp; 1714 if (candidate == null 1715 && lastSelectedConfiguration == null 1716 && currentConfiguration == null 1717 && didBailDueToWeakRssi 1718 && (mWifiConfigStore.lastUnwantedNetworkDisconnectTimestamp == 0 1719 || lastUnwanted > (1000 * 60 * 60 * 24 * 7)) 1720 ) { 1721 // We are bailing out of autojoin although we are seeing a weak configuration, and 1722 // - we didn't find another valid candidate 1723 // - we are not connected 1724 // - without a user network selection choice 1725 // - ConnectivityService has not triggered an unwanted network disconnect 1726 // on this device for a week (hence most likely there is no SIM card or cellular) 1727 // If all those conditions are met, then boost the RSSI of the weak networks 1728 // that we are seeing so as we will eventually pick one 1729 if (weakRssiBailCount < 10) 1730 weakRssiBailCount += 1; 1731 } else { 1732 if (weakRssiBailCount > 0) 1733 weakRssiBailCount -= 1; 1734 } 1735 1736 /** 1737 * If candidate is found, check the state of the connection so as 1738 * to decide if we should be acting on this candidate and switching over 1739 */ 1740 int networkDelta = compareNetwork(candidate, lastSelectedConfiguration); 1741 if (DBG && candidate != null) { 1742 String doSwitch = ""; 1743 String current = ""; 1744 if (networkDelta < 0) { 1745 doSwitch = " -> not switching"; 1746 } 1747 if (currentConfiguration != null) { 1748 current = " with current " + currentConfiguration.configKey(); 1749 } 1750 logDbg("attemptAutoJoin networkSwitching candidate " 1751 + candidate.configKey() 1752 + current 1753 + " linked=" + (currentConfiguration != null 1754 && currentConfiguration.isLinked(candidate)) 1755 + " : delta=" 1756 + Integer.toString(networkDelta) + " " 1757 + doSwitch); 1758 } 1759 1760 /** 1761 * Ask WifiStateMachine permission to switch : 1762 * if user is currently streaming voice traffic, 1763 * then we should not be allowed to switch regardless of the delta 1764 */ 1765 if (mWifiStateMachine.shouldSwitchNetwork(networkDelta)) { 1766 if (mStaStaSupported) { 1767 logDbg("mStaStaSupported --> error do nothing now "); 1768 } else { 1769 if (currentConfiguration != null && currentConfiguration.isLinked(candidate)) { 1770 networkSwitchType = AUTO_JOIN_EXTENDED_ROAMING; 1771 } else { 1772 networkSwitchType = AUTO_JOIN_OUT_OF_NETWORK_ROAMING; 1773 } 1774 if (DBG) { 1775 logDbg("AutoJoin auto connect with netId " 1776 + Integer.toString(candidate.networkId) 1777 + " to " + candidate.configKey()); 1778 } 1779 if (didOverride) { 1780 candidate.numScorerOverrideAndSwitchedNetwork++; 1781 } 1782 candidate.numAssociation++; 1783 mWifiConnectionStatistics.numAutoJoinAttempt++; 1784 1785 if (candidate.ephemeral) { 1786 // We found a new candidate that we are going to connect to, then 1787 // increase its connection count 1788 mWifiConnectionStatistics. 1789 incrementOrAddUntrusted(candidate.SSID, 1, 0); 1790 } 1791 1792 if (candidate.BSSID == null || candidate.BSSID.equals("any")) { 1793 // First step we selected the configuration we want to connect to 1794 // Second step: Look for the best Scan result for this configuration 1795 // TODO this algorithm should really be done in one step 1796 String currentBSSID = mWifiStateMachine.getCurrentBSSID(); 1797 ScanResult roamCandidate = 1798 attemptRoam(null, candidate, mScanResultAutoJoinAge, null); 1799 if (roamCandidate != null && currentBSSID != null 1800 && currentBSSID.equals(roamCandidate.BSSID)) { 1801 // Sanity, we were already asociated to that candidate 1802 roamCandidate = null; 1803 } 1804 if (roamCandidate != null && roamCandidate.is5GHz()) { 1805 // If the configuration hasn't a default BSSID selected, and the best 1806 // candidate is 5GHZ, then select this candidate so as WifiStateMachine and 1807 // supplicant will pick it first 1808 candidate.autoJoinBSSID = roamCandidate.BSSID; 1809 if (VDBG) { 1810 logDbg("AutoJoinController: lock to 5GHz " 1811 + candidate.autoJoinBSSID 1812 + " RSSI=" + roamCandidate.level 1813 + " freq=" + roamCandidate.frequency); 1814 } 1815 } else { 1816 // We couldnt find a roam candidate 1817 candidate.autoJoinBSSID = "any"; 1818 } 1819 } 1820 mWifiStateMachine.sendMessage(WifiStateMachine.CMD_AUTO_CONNECT, 1821 candidate.networkId, networkSwitchType, candidate); 1822 found = true; 1823 } 1824 } 1825 1826 if (networkSwitchType == AUTO_JOIN_IDLE) { 1827 String currentBSSID = mWifiStateMachine.getCurrentBSSID(); 1828 // Attempt same WifiConfiguration roaming 1829 ScanResult roamCandidate = 1830 attemptRoam(null, currentConfiguration, mScanResultAutoJoinAge, currentBSSID); 1831 /** 1832 * TODO: (post L initial release) 1833 * consider handling linked configurations roaming (i.e. extended Roaming) 1834 * thru the attemptRoam function which makes use of the RSSI roaming threshold. 1835 * At the moment, extended roaming is only handled thru the attemptAutoJoin() 1836 * function which compare configurations. 1837 * 1838 * The advantage of making use of attemptRoam function is that this function 1839 * will looks at all the BSSID of each configurations, instead of only looking 1840 * at WifiConfiguration.visibility which keeps trackonly of the RSSI/band of the 1841 * two highest BSSIDs. 1842 */ 1843 // Attempt linked WifiConfiguration roaming 1844 /* if (currentConfiguration != null 1845 && currentConfiguration.linkedConfigurations != null) { 1846 for (String key : currentConfiguration.linkedConfigurations.keySet()) { 1847 WifiConfiguration link = mWifiConfigStore.getWifiConfiguration(key); 1848 if (link != null) { 1849 roamCandidate = attemptRoam(roamCandidate, link, mScanResultAutoJoinAge, 1850 currentBSSID); 1851 } 1852 } 1853 }*/ 1854 if (roamCandidate != null && currentBSSID != null 1855 && currentBSSID.equals(roamCandidate.BSSID)) { 1856 roamCandidate = null; 1857 } 1858 if (roamCandidate != null && mWifiStateMachine.shouldSwitchNetwork(999)) { 1859 if (DBG) { 1860 logDbg("AutoJoin auto roam with netId " 1861 + Integer.toString(currentConfiguration.networkId) 1862 + " " + currentConfiguration.configKey() + " to BSSID=" 1863 + roamCandidate.BSSID + " freq=" + roamCandidate.frequency 1864 + " RSSI=" + roamCandidate.level); 1865 } 1866 networkSwitchType = AUTO_JOIN_ROAMING; 1867 mWifiConnectionStatistics.numAutoRoamAttempt++; 1868 1869 mWifiStateMachine.sendMessage(WifiStateMachine.CMD_AUTO_ROAM, 1870 currentConfiguration.networkId, 1, roamCandidate); 1871 found = true; 1872 } 1873 } 1874 if (VDBG) logDbg("Done attemptAutoJoin status=" + Integer.toString(networkSwitchType)); 1875 return found; 1876 } 1877} 1878 1879