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