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