WifiAutoJoinController.java revision 448c9536a302c58a79e271b1721c08b8882f800e
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 (rssi 810 > mWifiConfigStore.thresholdBandPreferenceLowRssi5) { 811 // Boost by 2 dB for each point 812 // Start boosting at -65 813 // Boost by 20 if above -55 814 // Boost by 40 if abore -45 815 int boost = mWifiConfigStore.factorBandPreferenceLowRssi5 816 *(rssi - mWifiConfigStore.thresholdBandPreferenceLowRssi5); 817 if (boost > 50) { 818 // 50 dB boost is set so as to overcome the hysteresis of +20 plus a difference of 819 // 25 dB between 2.4 and 5GHz band. This allows jumping from 2.4 to 5GHz 820 // consistently 821 boost = 50; 822 } 823 if (VDBG && dbg != null) { 824 logDbg(" " + dbg + ": rssi5 " + rssi + " boost " + boost); 825 } 826 return boost; 827 } 828 829 if (rssi 830 < mWifiConfigStore.thresholdBandPreferenceRssi24) { 831 // penalize by 10 if < -75 832 return -10; 833 } 834 return 0; 835 } 836 /** 837 * attemptRoam() function implements the core of the same SSID switching algorithm 838 * 839 * Run thru all recent scan result of a WifiConfiguration and select the 840 * best one. 841 */ 842 public ScanResult attemptRoam(ScanResult a, 843 WifiConfiguration current, int age, String currentBSSID) { 844 if (current == null) { 845 if (VDBG) { 846 logDbg("attemptRoam not associated"); 847 } 848 return a; 849 } 850 if (current.scanResultCache == null) { 851 if (VDBG) { 852 logDbg("attemptRoam no scan cache"); 853 } 854 return a; 855 } 856 if (current.scanResultCache.size() > 6) { 857 if (VDBG) { 858 logDbg("attemptRoam scan cache size " 859 + current.scanResultCache.size() + " --> bail"); 860 } 861 // Implement same SSID roaming only for configurations 862 // that have less than 4 BSSIDs 863 return a; 864 } 865 866 if (current.BSSID != null && !current.BSSID.equals("any")) { 867 if (DBG) { 868 logDbg("attemptRoam() BSSID is set " 869 + current.BSSID + " -> bail"); 870 } 871 return a; 872 } 873 874 // Determine which BSSID we want to associate to, taking account 875 // relative strength of 5 and 2.4 GHz BSSIDs 876 long nowMs = System.currentTimeMillis(); 877 878 for (ScanResult b : current.scanResultCache.values()) { 879 int bRssiBoost5 = 0; 880 int aRssiBoost5 = 0; 881 int bRssiBoost = 0; 882 int aRssiBoost = 0; 883 if ((b.seen == 0) || (b.BSSID == null) 884 || ((nowMs - b.seen) > age) 885 || b.autoJoinStatus != ScanResult.ENABLED) { 886 continue; 887 } 888 889 // Pick first one 890 if (a == null) { 891 a = b; 892 continue; 893 } 894 895 // Apply hysteresis: we favor the currentBSSID by giving it a boost 896 if (currentBSSID != null && currentBSSID.equals(b.BSSID)) { 897 // Reduce the benefit of hysteresis if RSSI <= -75 898 if (b.level <= mWifiConfigStore.thresholdBandPreferenceRssi24) { 899 bRssiBoost = +8; 900 } else { 901 bRssiBoost = +14; 902 } 903 } 904 if (currentBSSID != null && currentBSSID.equals(a.BSSID)) { 905 if (a.level <= mWifiConfigStore.thresholdBandPreferenceRssi24) { 906 // Reduce the benefit of hysteresis if RSSI <= -75 907 aRssiBoost = +8; 908 } else { 909 aRssiBoost = +14; 910 } 911 } 912 913 // Favor 5GHz: give a boost to 5GHz BSSIDs, with a slightly progressive curve 914 // Boost the BSSID if it is on 5GHz, above a threshold 915 // But penalize it if it is on 5GHz and below threshold 916 // 917 // With he current threshold values, 5GHz network with RSSI above -55 918 // Are given a boost of 30DB which is enough to overcome the current BSSID 919 // hysteresis (+14) plus 2.4/5 GHz signal strength difference on most cases 920 // 921 // The "current BSSID" Boost must be added to the BSSID's level so as to introduce\ 922 // soem amount of hysteresis 923 if (b.is5GHz()) { 924 bRssiBoost5 = rssiBoostFrom5GHzRssi(b.level + bRssiBoost, b.BSSID); 925 } 926 if (a.is5GHz()) { 927 aRssiBoost5 = rssiBoostFrom5GHzRssi(a.level + aRssiBoost, a.BSSID); 928 } 929 930 if (VDBG) { 931 String comp = " < "; 932 if (b.level + bRssiBoost + bRssiBoost5 > a.level +aRssiBoost + aRssiBoost5) { 933 comp = " > "; 934 } 935 logDbg("attemptRoam: " 936 + b.BSSID + " rssi=" + b.level + " boost=" + Integer.toString(bRssiBoost) 937 + "/" + Integer.toString(bRssiBoost5) + " freq=" + b.frequency 938 + comp 939 + a.BSSID + " rssi=" + a.level + " boost=" + Integer.toString(aRssiBoost) 940 + "/" + Integer.toString(aRssiBoost5) + " freq=" + a.frequency); 941 } 942 943 // Compare the RSSIs after applying the hysteresis boost and the 5GHz 944 // boost if applicable 945 if (b.level + bRssiBoost + bRssiBoost5 > a.level +aRssiBoost + aRssiBoost5) { 946 // b is the better BSSID 947 a = b; 948 } 949 } 950 if (a != null) { 951 if (VDBG) { 952 StringBuilder sb = new StringBuilder(); 953 sb.append("attemptRoam: " + current.configKey() + 954 " Found " + a.BSSID + " rssi=" + a.level + " freq=" + a.frequency); 955 if (currentBSSID != null) { 956 sb.append(" Current: " + currentBSSID); 957 } 958 sb.append("\n"); 959 logDbg(sb.toString()); 960 } 961 } 962 return a; 963 } 964 965 /** 966 * getNetworkScore() 967 * 968 * if scorer is present, get the network score of a WifiConfiguration 969 * 970 * Note: this should be merge with setVisibility 971 * 972 * @param config 973 * @return score 974 */ 975 int getConfigNetworkScore(WifiConfiguration config, int age, int rssiBoost) { 976 977 if (mNetworkScoreCache == null) { 978 if (VDBG) { 979 logDbg(" getConfigNetworkScore for " + config.configKey() 980 + " -> no scorer, hence no scores"); 981 } 982 return WifiNetworkScoreCache.INVALID_NETWORK_SCORE; 983 } 984 if (config.scanResultCache == null) { 985 if (VDBG) { 986 logDbg(" getConfigNetworkScore for " + config.configKey() 987 + " -> no scan cache"); 988 } 989 return WifiNetworkScoreCache.INVALID_NETWORK_SCORE; 990 } 991 992 // Get current date 993 long nowMs = System.currentTimeMillis(); 994 995 int startScore = -10000; 996 997 // Run thru all cached scan results 998 for (ScanResult result : config.scanResultCache.values()) { 999 if ((nowMs - result.seen) < age) { 1000 int sc = mNetworkScoreCache.getNetworkScore(result, rssiBoost); 1001 if (sc > startScore) { 1002 startScore = sc; 1003 } 1004 } 1005 } 1006 if (startScore == -10000) { 1007 startScore = WifiNetworkScoreCache.INVALID_NETWORK_SCORE; 1008 } 1009 if (VDBG) { 1010 if (startScore == WifiNetworkScoreCache.INVALID_NETWORK_SCORE) { 1011 logDbg(" getConfigNetworkScore for " + config.configKey() 1012 + " -> no available score"); 1013 } else { 1014 logDbg(" getConfigNetworkScore for " + config.configKey() 1015 + " boost=" + Integer.toString(rssiBoost) 1016 + " score = " + Integer.toString(startScore)); 1017 } 1018 } 1019 1020 return startScore; 1021 } 1022 1023 /** 1024 * attemptAutoJoin() function implements the core of the a network switching algorithm 1025 */ 1026 void attemptAutoJoin() { 1027 didOverride = false; 1028 int networkSwitchType = AUTO_JOIN_IDLE; 1029 1030 String lastSelectedConfiguration = mWifiConfigStore.getLastSelectedConfiguration(); 1031 1032 // Reset the currentConfiguration Key, and set it only if WifiStateMachine and 1033 // supplicant agree 1034 mCurrentConfigurationKey = null; 1035 WifiConfiguration currentConfiguration = mWifiStateMachine.getCurrentWifiConfiguration(); 1036 1037 WifiConfiguration candidate = null; 1038 1039 // Obtain the subset of recently seen networks 1040 List<WifiConfiguration> list = mWifiConfigStore.getRecentConfiguredNetworks(3000, false); 1041 if (list == null) { 1042 if (VDBG) logDbg("attemptAutoJoin nothing known=" + 1043 mWifiConfigStore.getconfiguredNetworkSize()); 1044 return; 1045 } 1046 1047 // Find the currently connected network: ask the supplicant directly 1048 String val = mWifiNative.status(); 1049 String status[] = val.split("\\r?\\n"); 1050 if (VDBG) { 1051 logDbg("attemptAutoJoin() status=" + val + " split=" 1052 + Integer.toString(status.length)); 1053 } 1054 1055 int supplicantNetId = -1; 1056 for (String key : status) { 1057 if (key.regionMatches(0, "id=", 0, 3)) { 1058 int idx = 3; 1059 supplicantNetId = 0; 1060 while (idx < key.length()) { 1061 char c = key.charAt(idx); 1062 1063 if ((c >= 0x30) && (c <= 0x39)) { 1064 supplicantNetId *= 10; 1065 supplicantNetId += c - 0x30; 1066 idx++; 1067 } else { 1068 break; 1069 } 1070 } 1071 } else if (key.contains("wpa_state=ASSOCIATING") 1072 || key.contains("wpa_state=ASSOCIATED") 1073 || key.contains("wpa_state=FOUR_WAY_HANDSHAKE") 1074 || key.contains("wpa_state=GROUP_KEY_HANDSHAKE")) { 1075 if (DBG) { 1076 logDbg("attemptAutoJoin: bail out due to sup state " + key); 1077 } 1078 // After WifiStateMachine ask the supplicant to associate or reconnect 1079 // we might still obtain scan results from supplicant 1080 // however the supplicant state in the mWifiInfo and supplicant state tracker 1081 // are updated when we get the supplicant state change message which can be 1082 // processed after the SCAN_RESULT message, so at this point the framework doesn't 1083 // know that supplicant is ASSOCIATING. 1084 // A good fix for this race condition would be for the WifiStateMachine to add 1085 // a new transient state where it expects to get the supplicant message indicating 1086 // that it started the association process and within which critical operations 1087 // like autojoin should be deleted. 1088 1089 // This transient state would remove the need for the roam Wathchdog which 1090 // basically does that. 1091 1092 // At the moment, we just query the supplicant state synchronously with the 1093 // mWifiNative.status() command, which allow us to know that 1094 // supplicant has started association process, even though we didnt yet get the 1095 // SUPPLICANT_STATE_CHANGE message. 1096 return; 1097 } 1098 } 1099 if (DBG) { 1100 String conf = ""; 1101 String last = ""; 1102 if (currentConfiguration != null) { 1103 conf = " curent=" + currentConfiguration.configKey(); 1104 } 1105 if (lastSelectedConfiguration != null) { 1106 last = " last=" + lastSelectedConfiguration; 1107 } 1108 logDbg("attemptAutoJoin() num recent config " + Integer.toString(list.size()) 1109 + conf + last 1110 + " ---> suppNetId=" + Integer.toString(supplicantNetId)); 1111 } 1112 1113 if (currentConfiguration != null) { 1114 if (supplicantNetId != currentConfiguration.networkId 1115 //https://b.corp.google.com/issue?id=16484607 1116 //mark this confition as an error only if the mismatched networkId are valid 1117 && supplicantNetId != WifiConfiguration.INVALID_NETWORK_ID 1118 && currentConfiguration.networkId != WifiConfiguration.INVALID_NETWORK_ID) { 1119 logDbg("attemptAutoJoin() ERROR wpa_supplicant out of sync nid=" 1120 + Integer.toString(supplicantNetId) + " WifiStateMachine=" 1121 + Integer.toString(currentConfiguration.networkId)); 1122 mWifiStateMachine.disconnectCommand(); 1123 return; 1124 } else { 1125 mCurrentConfigurationKey = currentConfiguration.configKey(); 1126 } 1127 } else { 1128 if (supplicantNetId != WifiConfiguration.INVALID_NETWORK_ID) { 1129 // Maybe in the process of associating, skip this attempt 1130 return; 1131 } 1132 } 1133 1134 int currentNetId = -1; 1135 if (currentConfiguration != null) { 1136 // If we are associated to a configuration, it will 1137 // be compared thru the compareNetwork function 1138 currentNetId = currentConfiguration.networkId; 1139 } 1140 1141 /** 1142 * Run thru all visible configurations without looking at the one we 1143 * are currently associated to 1144 * select Best Network candidate from known WifiConfigurations 1145 */ 1146 for (WifiConfiguration config : list) { 1147 if ((config.status == WifiConfiguration.Status.DISABLED) 1148 && (config.disableReason == WifiConfiguration.DISABLED_AUTH_FAILURE)) { 1149 if (DBG) { 1150 logDbg("attemptAutoJoin skip candidate due to auth failure: " 1151 + config.configKey(true)); 1152 } 1153 continue; 1154 } 1155 1156 if (config.SSID == null) { 1157 continue; 1158 } 1159 1160 if (config.autoJoinStatus >= 1161 WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE) { 1162 // Avoid networks disabled because of AUTH failure altogether 1163 if (DBG) { 1164 logDbg("attemptAutoJoin skip candidate due to auto join status " 1165 + Integer.toString(config.autoJoinStatus) + " key " 1166 + config.configKey(true)); 1167 } 1168 continue; 1169 } 1170 1171 // Try to un-blacklist based on elapsed time 1172 if (config.blackListTimestamp > 0) { 1173 long now = System.currentTimeMillis(); 1174 if (now < config.blackListTimestamp) { 1175 /** 1176 * looks like there was a change in the system clock since we black listed, and 1177 * timestamp is not meaningful anymore, hence lose it. 1178 * this event should be rare enough so that we still want to lose the black list 1179 */ 1180 config.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED); 1181 } else { 1182 if ((now - config.blackListTimestamp) > loseBlackListHardMilli) { 1183 // Reenable it after 18 hours, i.e. next day 1184 config.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED); 1185 } else if ((now - config.blackListTimestamp) > loseBlackListSoftMilli) { 1186 // Lose blacklisting due to bad link 1187 config.setAutoJoinStatus(config.autoJoinStatus - 8); 1188 } 1189 } 1190 } 1191 1192 // Try to unblacklist based on good visibility 1193 if (config.visibility.rssi5 < mWifiConfigStore.thresholdUnblacklistThreshold5Soft 1194 && config.visibility.rssi24 1195 < mWifiConfigStore.thresholdUnblacklistThreshold24Soft) { 1196 if (DBG) { 1197 logDbg("attemptAutoJoin do not unblacklist due to low visibility " 1198 + config.autoJoinStatus 1199 + " key " + config.configKey(true) 1200 + " rssi=(" + config.visibility.rssi24 1201 + "," + config.visibility.rssi5 1202 + ") num=(" + config.visibility.num24 1203 + "," + config.visibility.num5 + ")"); 1204 } 1205 } else if (config.visibility.rssi5 < mWifiConfigStore.thresholdUnblacklistThreshold5Hard 1206 && config.visibility.rssi24 1207 < mWifiConfigStore.thresholdUnblacklistThreshold24Hard) { 1208 // If the network is simply temporary disabled, don't allow reconnect until 1209 // RSSI becomes good enough 1210 config.setAutoJoinStatus(config.autoJoinStatus - 1); 1211 if (DBG) { 1212 logDbg("attemptAutoJoin good candidate seen, bumped soft -> status=" 1213 + config.autoJoinStatus 1214 + " " + config.configKey(true) + " rssi=(" 1215 + config.visibility.rssi24 + "," + config.visibility.rssi5 1216 + ") num=(" + config.visibility.num24 1217 + "," + config.visibility.num5 + ")"); 1218 } 1219 } else { 1220 config.setAutoJoinStatus(config.autoJoinStatus - 3); 1221 if (DBG) { 1222 logDbg("attemptAutoJoin good candidate seen, bumped hard -> status=" 1223 + config.autoJoinStatus 1224 + " " + config.configKey(true) + " rssi=(" 1225 + config.visibility.rssi24 + "," + config.visibility.rssi5 1226 + ") num=(" + config.visibility.num24 1227 + "," + config.visibility.num5 + ")"); 1228 } 1229 } 1230 1231 if (config.autoJoinStatus >= 1232 WifiConfiguration.AUTO_JOIN_TEMPORARY_DISABLED) { 1233 // Network is blacklisted, skip 1234 if (DBG) { 1235 logDbg("attemptAutoJoin skip blacklisted -> status=" 1236 + config.autoJoinStatus 1237 + " " + config.configKey(true) + " rssi=(" 1238 + config.visibility.rssi24 + "," + config.visibility.rssi5 1239 + ") num=(" + config.visibility.num24 1240 + "," + config.visibility.num5 + ")"); 1241 } 1242 continue; 1243 } 1244 if (config.networkId == currentNetId) { 1245 if (DBG) { 1246 logDbg("attemptAutoJoin skip current candidate " 1247 + Integer.toString(currentNetId) 1248 + " key " + config.configKey(true)); 1249 } 1250 continue; 1251 } 1252 1253 if (lastSelectedConfiguration == null || 1254 !config.configKey().equals(lastSelectedConfiguration)) { 1255 // Don't try to autojoin a network that is too far but 1256 // If that configuration is a user's choice however, try anyway 1257 if (config.visibility == null) { 1258 continue; 1259 } 1260 if (config.visibility.rssi5 1261 < mWifiConfigStore.thresholdInitialAutoJoinAttemptMin5RSSI 1262 && config.visibility.rssi24 1263 < mWifiConfigStore.thresholdInitialAutoJoinAttemptMin24RSSI) { 1264 if (DBG) { 1265 logDbg("attemptAutoJoin skip due to low visibility -> status=" 1266 + config.autoJoinStatus 1267 + " key " + config.configKey(true) + " rssi=" 1268 + config.visibility.rssi24 + ", " + config.visibility.rssi5 1269 + " num=" + config.visibility.num24 1270 + ", " + config.visibility.num5); 1271 } 1272 continue; 1273 } 1274 if (config.noInternetAccess) { 1275 // Avoid autojoining this network because last time we used it, it didn't 1276 // have internet access 1277 if (DBG) { 1278 logDbg("attemptAutoJoin skip candidate due to noInternetAccess flag " 1279 + config.configKey(true)); 1280 } 1281 continue; 1282 } 1283 } 1284 1285 if (DBG) { 1286 logDbg("attemptAutoJoin trying candidate id=" 1287 + Integer.toString(config.networkId) + " " 1288 + config.configKey(true) 1289 + " status=" + config.autoJoinStatus); 1290 } 1291 1292 if (candidate == null) { 1293 candidate = config; 1294 } else { 1295 if (VDBG) { 1296 logDbg("attemptAutoJoin will compare candidate " + candidate.configKey() 1297 + " with " + config.configKey()); 1298 } 1299 int order = compareWifiConfigurationsTop(candidate, config); 1300 1301 // The lastSelectedConfiguration is the configuration the user has manually selected 1302 // thru WifiPicker, or that a 3rd party app asked us to connect to via the 1303 // enableNetwork with disableOthers=true WifiManager API 1304 // As this is a direct user choice, we strongly prefer this configuration, 1305 // hence give +/-100 1306 if ((lastSelectedConfiguration != null) 1307 && candidate.configKey().equals(lastSelectedConfiguration)) { 1308 // candidate is the last selected configuration, 1309 // so keep it above connect choices (+/-60) and 1310 // above RSSI/scorer based selection of linked configuration (+/- 50) 1311 // by reducing order by -100 1312 order = order - 100; 1313 if (VDBG) { 1314 logDbg(" ...and prefers -100 " + candidate.configKey() 1315 + " over " + config.configKey() 1316 + " because it is the last selected -> " 1317 + Integer.toString(order)); 1318 } 1319 } else if ((lastSelectedConfiguration != null) 1320 && config.configKey().equals(lastSelectedConfiguration)) { 1321 // config is the last selected configuration, 1322 // so keep it above connect choices (+/-60) and 1323 // above RSSI/scorer based selection of linked configuration (+/- 50) 1324 // by increasing order by +100 1325 order = order + 100; 1326 if (VDBG) { 1327 logDbg(" ...and prefers +100 " + config.configKey() 1328 + " over " + candidate.configKey() 1329 + " because it is the last selected -> " 1330 + Integer.toString(order)); 1331 } 1332 } 1333 1334 if (order > 0) { 1335 // Ascending : candidate < config 1336 candidate = config; 1337 } 1338 } 1339 } 1340 1341 // Wait for VPN to be available on the system to make use of this code 1342 // Now, go thru scan result to try finding a better untrusted network 1343 if (mNetworkScoreCache != null) { 1344 int rssi5 = WifiConfiguration.INVALID_RSSI; 1345 int rssi24 = WifiConfiguration.INVALID_RSSI; 1346 WifiConfiguration.Visibility visibility; 1347 if (candidate != null) { 1348 rssi5 = candidate.visibility.rssi5; 1349 rssi24 = candidate.visibility.rssi24; 1350 } 1351 1352 // Get current date 1353 long nowMs = System.currentTimeMillis(); 1354 int currentScore = -10000; 1355 // The untrusted network with highest score 1356 ScanResult untrustedCandidate = null; 1357 // Look for untrusted scored network only if the current candidate is bad 1358 if (isBadCandidate(rssi24, rssi5)) { 1359 for (ScanResult result : scanResultCache.values()) { 1360 int rssiBoost = 0; 1361 // We look only at untrusted networks with a valid SSID 1362 // A trusted result would have been looked at thru it's Wificonfiguration 1363 if (TextUtils.isEmpty(result.SSID) || !result.untrusted) { 1364 continue; 1365 } 1366 if ((nowMs - result.seen) < 3000) { 1367 // Increment usage count for the network 1368 mWifiConnectionStatistics.incrementOrAddUntrusted(result.SSID, 0, 1); 1369 1370 if (lastUntrustedBSSID != null 1371 && result.BSSID.equals(lastUntrustedBSSID)) { 1372 // Apply a large hysteresis to the untrusted network we are connected to 1373 rssiBoost = 25; 1374 } 1375 int score = mNetworkScoreCache.getNetworkScore(result, rssiBoost); 1376 if (score != WifiNetworkScoreCache.INVALID_NETWORK_SCORE 1377 && score > currentScore) { 1378 // Highest score: Select this candidate 1379 currentScore = score; 1380 untrustedCandidate = result; 1381 if (VDBG) { 1382 logDbg("AutoJoinController: found untrusted candidate " 1383 + result.SSID 1384 + " RSSI=" + result.level 1385 + " freq=" + result.frequency 1386 + " score=" + score); 1387 } 1388 } 1389 } 1390 } 1391 } 1392 if (untrustedCandidate != null) { 1393 if (lastUntrustedBSSID == null 1394 || !untrustedCandidate.SSID.equals(lastUntrustedBSSID)) { 1395 // We found a new candidate that we are going to connect to, then 1396 // increase its connection count 1397 mWifiConnectionStatistics. 1398 incrementOrAddUntrusted(untrustedCandidate.SSID, 1, 0); 1399 // Remember which SSID we are connecting to 1400 lastUntrustedBSSID = untrustedCandidate.SSID; 1401 } 1402 } 1403 // Now we don't have VPN, and thus don't actually connect to the untrusted candidate 1404 untrustedCandidate = null; 1405 } 1406 1407 /** 1408 * If candidate is found, check the state of the connection so as 1409 * to decide if we should be acting on this candidate and switching over 1410 */ 1411 int networkDelta = compareNetwork(candidate); 1412 if (DBG && candidate != null) { 1413 String doSwitch = ""; 1414 String current = ""; 1415 if (networkDelta < 0) { 1416 doSwitch = " -> not switching"; 1417 } 1418 if (currentConfiguration != null) { 1419 current = " with current " + currentConfiguration.configKey(); 1420 } 1421 logDbg("attemptAutoJoin networkSwitching candidate " 1422 + candidate.configKey() 1423 + current 1424 + " linked=" + (currentConfiguration != null 1425 && currentConfiguration.isLinked(candidate)) 1426 + " : delta=" 1427 + Integer.toString(networkDelta) + " " 1428 + doSwitch); 1429 } 1430 1431 /** 1432 * Ask WifiStateMachine permission to switch : 1433 * if user is currently streaming voice traffic, 1434 * then we should not be allowed to switch regardless of the delta 1435 */ 1436 if (mWifiStateMachine.shouldSwitchNetwork(networkDelta)) { 1437 if (mStaStaSupported) { 1438 logDbg("mStaStaSupported --> error do nothing now "); 1439 } else { 1440 if (currentConfiguration != null && currentConfiguration.isLinked(candidate)) { 1441 networkSwitchType = AUTO_JOIN_EXTENDED_ROAMING; 1442 } else { 1443 networkSwitchType = AUTO_JOIN_OUT_OF_NETWORK_ROAMING; 1444 } 1445 if (DBG) { 1446 logDbg("AutoJoin auto connect with netId " 1447 + Integer.toString(candidate.networkId) 1448 + " to " + candidate.configKey()); 1449 } 1450 if (didOverride) { 1451 candidate.numScorerOverrideAndSwitchedNetwork++; 1452 } 1453 candidate.numAssociation++; 1454 mWifiConnectionStatistics.numAutoJoinAttempt++; 1455 1456 if (candidate.BSSID == null || candidate.BSSID.equals("any")) { 1457 // First step we selected the configuration we want to connect to 1458 // Second step: Look for the best Scan result for this configuration 1459 // TODO this algorithm should really be done in one step 1460 String currentBSSID = mWifiStateMachine.getCurrentBSSID(); 1461 ScanResult roamCandidate = attemptRoam(null, candidate, 3000, null); 1462 if (roamCandidate != null && currentBSSID != null 1463 && currentBSSID.equals(roamCandidate.BSSID)) { 1464 // Sanity, we were already asociated to that candidate 1465 roamCandidate = null; 1466 } 1467 if (roamCandidate != null && roamCandidate.is5GHz()) { 1468 // If the configuration hasn't a default BSSID selected, and the best 1469 // candidate is 5GHZ, then select this candidate so as WifiStateMachine and 1470 // supplicant will pick it first 1471 candidate.autoJoinBSSID = roamCandidate.BSSID; 1472 if (VDBG) { 1473 logDbg("AutoJoinController: lock to 5GHz " 1474 + candidate.autoJoinBSSID 1475 + " RSSI=" + roamCandidate.level 1476 + " freq=" + roamCandidate.frequency); 1477 } 1478 } else { 1479 // We couldnt find a roam candidate 1480 candidate.autoJoinBSSID = "any"; 1481 } 1482 } 1483 mWifiStateMachine.sendMessage(WifiStateMachine.CMD_AUTO_CONNECT, 1484 candidate.networkId, networkSwitchType, candidate); 1485 } 1486 } 1487 1488 if (networkSwitchType == AUTO_JOIN_IDLE) { 1489 String currentBSSID = mWifiStateMachine.getCurrentBSSID(); 1490 // Attempt same WifiConfiguration roaming 1491 ScanResult roamCandidate = attemptRoam(null, currentConfiguration, 3000, 1492 currentBSSID); 1493 /** 1494 * TODO: (post L initial release) 1495 * consider handling linked configurations roaming (i.e. extended Roaming) 1496 * thru the attemptRoam function which makes use of the RSSI roaming threshold. 1497 * At the moment, extended roaming is only handled thru the attemptAutoJoin() 1498 * function which compare configurations. 1499 * 1500 * The advantage of making use of attemptRoam function is that this function 1501 * will looks at all the BSSID of each configurations, instead of only looking 1502 * at WifiConfiguration.visibility which keeps trackonly of the RSSI/band of the 1503 * two highest BSSIDs. 1504 */ 1505 // Attempt linked WifiConfiguration roaming 1506 /* if (currentConfiguration != null 1507 && currentConfiguration.linkedConfigurations != null) { 1508 for (String key : currentConfiguration.linkedConfigurations.keySet()) { 1509 WifiConfiguration link = mWifiConfigStore.getWifiConfiguration(key); 1510 if (link != null) { 1511 roamCandidate = attemptRoam(roamCandidate, link, 3000, 1512 currentBSSID); 1513 } 1514 } 1515 }*/ 1516 if (roamCandidate != null && currentBSSID != null 1517 && currentBSSID.equals(roamCandidate.BSSID)) { 1518 roamCandidate = null; 1519 } 1520 if (roamCandidate != null && mWifiStateMachine.shouldSwitchNetwork(999)) { 1521 if (DBG) { 1522 logDbg("AutoJoin auto roam with netId " 1523 + Integer.toString(currentConfiguration.networkId) 1524 + " " + currentConfiguration.configKey() + " to BSSID=" 1525 + roamCandidate.BSSID + " freq=" + roamCandidate.frequency 1526 + " RSSI=" + roamCandidate.frequency); 1527 } 1528 networkSwitchType = AUTO_JOIN_ROAMING; 1529 mWifiConnectionStatistics.numAutoRoamAttempt++; 1530 1531 mWifiStateMachine.sendMessage(WifiStateMachine.CMD_AUTO_ROAM, 1532 currentConfiguration.networkId, 1, roamCandidate); 1533 } 1534 } 1535 if (VDBG) logDbg("Done attemptAutoJoin status=" + Integer.toString(networkSwitchType)); 1536 } 1537} 1538 1539