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