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