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