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