WifiAutoJoinController.java revision 3a2a3d226881cce8a4e511302231d843b0def303
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.server.wifi; 18 19import android.content.Context; 20 21import android.net.NetworkKey; 22import android.net.NetworkScoreManager; 23import android.net.WifiKey; 24import android.net.wifi.WifiConfiguration; 25import android.net.wifi.ScanResult; 26import android.net.wifi.WifiManager; 27 28import android.os.SystemClock; 29import android.os.Process; 30import android.util.Log; 31 32import java.util.ArrayList; 33import java.util.Collection; 34import java.util.Iterator; 35import java.util.HashMap; 36import java.util.List; 37import java.util.Date; 38 39/** 40 * AutoJoin controller is responsible for WiFi Connect decision 41 * 42 * It runs in the thread context of WifiStateMachine 43 * 44 */ 45public class WifiAutoJoinController { 46 47 private Context mContext; 48 private WifiStateMachine mWifiStateMachine; 49 private WifiConfigStore mWifiConfigStore; 50 private WifiTrafficPoller mWifiTrafficPoller; 51 private WifiNative mWifiNative; 52 53 private NetworkScoreManager scoreManager; 54 private WifiNetworkScoreCache mNetworkScoreCache; 55 56 private static final String TAG = "WifiAutoJoinController "; 57 private static boolean DBG = false; 58 private static boolean VDBG = false; 59 private static final boolean mStaStaSupported = false; 60 private static final int SCAN_RESULT_CACHE_SIZE = 80; 61 62 private String mCurrentConfigurationKey = null; //used by autojoin 63 64 private HashMap<String, ScanResult> scanResultCache = 65 new HashMap<String, ScanResult>(); 66 67 //lose the non-auth failure blacklisting after 8 hours 68 private final static long loseBlackListHardMilli = 1000 * 60 * 60 * 8; 69 //lose some temporary blacklisting after 30 minutes 70 private final static long loseBlackListSoftMilli = 1000 * 60 * 30; 71 72 public static final int AUTO_JOIN_IDLE = 0; 73 public static final int AUTO_JOIN_ROAMING = 1; 74 public static final int AUTO_JOIN_EXTENDED_ROAMING = 2; 75 public static final int AUTO_JOIN_OUT_OF_NETWORK_ROAMING = 3; 76 77 WifiAutoJoinController(Context c, WifiStateMachine w, WifiConfigStore s, 78 WifiTrafficPoller t, WifiNative n) { 79 mContext = c; 80 mWifiStateMachine = w; 81 mWifiConfigStore = s; 82 mWifiTrafficPoller = t; 83 mWifiNative = n; 84 mNetworkScoreCache = null; 85 scoreManager = 86 (NetworkScoreManager) mContext.getSystemService(Context.NETWORK_SCORE_SERVICE); 87 if (scoreManager == null) 88 logDbg("Registered scoreManager NULL " + " service " + Context.NETWORK_SCORE_SERVICE); 89 90 if (scoreManager != null) { 91 mNetworkScoreCache = new WifiNetworkScoreCache(mContext); 92 scoreManager.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache); 93 } else { 94 logDbg("No network score service: Couldnt register as a WiFi score Manager, type=" 95 + Integer.toString(NetworkKey.TYPE_WIFI) 96 + " service " + Context.NETWORK_SCORE_SERVICE); 97 mNetworkScoreCache = null; 98 } 99 } 100 101 void enableVerboseLogging(int verbose) { 102 if (verbose > 0 ) { 103 DBG = true; 104 VDBG = true; 105 } else { 106 DBG = false; 107 VDBG = false; 108 } 109 } 110 111 int mScanResultMaximumAge = 30000; /* milliseconds unit */ 112 113 /* 114 * flush out scan results older than mScanResultMaximumAge 115 * 116 * */ 117 private void ageScanResultsOut(int delay) { 118 if (delay <= 0) { 119 delay = mScanResultMaximumAge; //something sane 120 } 121 long milli = System.currentTimeMillis(); 122 if (VDBG) { 123 logDbg("ageScanResultsOut delay " + Integer.valueOf(delay) + " size " 124 + Integer.valueOf(scanResultCache.size()) + " now " + Long.valueOf(milli)); 125 } 126 127 Iterator<HashMap.Entry<String,ScanResult>> iter = scanResultCache.entrySet().iterator(); 128 while (iter.hasNext()) { 129 HashMap.Entry<String,ScanResult> entry = iter.next(); 130 ScanResult result = entry.getValue(); 131 132 if ((result.seen + delay) < milli) { 133 iter.remove(); 134 } 135 } 136 } 137 138 void addToScanCache(List<ScanResult> scanList) { 139 WifiConfiguration associatedConfig; 140 141 ArrayList<NetworkKey> unknownScanResults = new ArrayList<NetworkKey>(); 142 143 for(ScanResult result: scanList) { 144 if (result.SSID == null) continue; 145 result.seen = System.currentTimeMillis(); 146 147 ScanResult sr = scanResultCache.get(result.BSSID); 148 if (sr != null) { 149 // if there was a previous cache result for this BSSID, average the RSSI values 150 151 int previous_rssi = sr.level; 152 long previously_seen_milli = sr.seen; 153 154 /* average RSSI with previously seen instances of this scan result */ 155 int avg_rssi = result.level; 156 157 if ((previously_seen_milli > 0) 158 && (previously_seen_milli < mScanResultMaximumAge/2)) { 159 /* 160 * 161 * previously_seen_milli = 0 => RSSI = 0.5 * previous_seen_rssi + 0.5 * new_rssi 162 * 163 * If previously_seen_milli is 15+ seconds old: 164 * previously_seen_milli = 15000 => RSSI = new_rssi 165 * 166 */ 167 168 double alpha = 0.5 - (double)previously_seen_milli 169 / (double)mScanResultMaximumAge; 170 171 avg_rssi = (int)((double)avg_rssi * (1-alpha) + (double)previous_rssi * alpha); 172 } 173 result.level = avg_rssi; 174 175 //remove the previous Scan Result 176 scanResultCache.remove(result.BSSID); 177 } else { 178 if (!mNetworkScoreCache.isScoredNetwork(result)) { 179 WifiKey wkey; 180 //TODO : find out how we can get there without a valid UTF-8 encoded SSID 181 //TODO: which will cause WifiKey constructor to fail 182 try { 183 wkey = new WifiKey("\"" + result.SSID + "\"", result.BSSID); 184 } catch (IllegalArgumentException e) { 185 logDbg("AutoJoinController: received badly encoded SSID=[" + result.SSID + 186 "] ->skipping this network"); 187 wkey = null; 188 } 189 if (wkey != null) { 190 NetworkKey nkey = new NetworkKey(wkey); 191 //if we don't know this scan result then request a score to Herrevad 192 unknownScanResults.add(nkey); 193 } 194 } 195 } 196 197 scanResultCache.put(result.BSSID, new ScanResult(result)); 198 199 //add this BSSID to the scanResultCache of the relevant WifiConfiguration 200 associatedConfig = mWifiConfigStore.updateSavedNetworkHistory(result); 201 202 //try to associate this BSSID to an existing Saved WifiConfiguration 203 if (associatedConfig == null) { 204 associatedConfig = mWifiConfigStore.associateWithConfiguration(result); 205 if (associatedConfig != null && associatedConfig.SSID != null) { 206 if (VDBG) { 207 logDbg("addToScanCache save associated config " 208 + associatedConfig.SSID + " with " + associatedConfig.SSID); 209 } 210 mWifiStateMachine.sendMessage(WifiManager.SAVE_NETWORK, associatedConfig); 211 } 212 } 213 } 214 215 if (unknownScanResults.size() != 0) { 216 NetworkKey[] newKeys = 217 unknownScanResults.toArray(new NetworkKey[unknownScanResults.size()]); 218 //kick the score manager, we will get updated scores asynchronously 219 scoreManager.requestScores(newKeys); 220 } 221 } 222 223 void logDbg(String message) { 224 logDbg(message, false); 225 } 226 227 void logDbg(String message, boolean stackTrace) { 228 long now = SystemClock.elapsedRealtimeNanos(); 229 String ts = String.format("[%,d us] ", now / 1000); 230 if (stackTrace) { 231 Log.e(TAG, ts + message + " stack:" 232 + Thread.currentThread().getStackTrace()[2].getMethodName() + " - " 233 + Thread.currentThread().getStackTrace()[3].getMethodName() + " - " 234 + Thread.currentThread().getStackTrace()[4].getMethodName() + " - " 235 + Thread.currentThread().getStackTrace()[5].getMethodName()); 236 } else { 237 Log.e(TAG, ts + message); 238 } 239 } 240 241 /* called directly from WifiStateMachine */ 242 void newSupplicantResults() { 243 List<ScanResult> scanList = mWifiStateMachine.syncGetScanResultsList(); 244 addToScanCache(scanList); 245 ageScanResultsOut(mScanResultMaximumAge); 246 if (DBG) 247 logDbg("newSupplicantResults size=" + Integer.valueOf(scanResultCache.size()) ); 248 249 attemptAutoJoin(); 250 mWifiConfigStore.writeKnownNetworkHistory(); 251 252 } 253 254 255 /* not used at the moment 256 * should be a call back from WifiScanner HAL ?? 257 * this function is not hooked and working yet, it will receive scan results from WifiScanners 258 * with the list of IEs,then populate the capabilities by parsing the IEs and inject the scan 259 * results as normal. 260 */ 261 void newHalScanResults() { 262 List<ScanResult> scanList = null;//mWifiScanner.syncGetScanResultsList(); 263 String akm = WifiParser.parse_akm(null, null); 264 logDbg(akm); 265 addToScanCache(scanList); 266 ageScanResultsOut(0); 267 attemptAutoJoin(); 268 mWifiConfigStore.writeKnownNetworkHistory(); 269 } 270 271 /* network link quality changed, called directly from WifiTrafficPoller, 272 or by listening to Link Quality intent */ 273 void linkQualitySignificantChange() { 274 attemptAutoJoin(); 275 } 276 277 /* 278 * compare a WifiConfiguration against the current network, return a delta score 279 * If not associated, and the candidate will always be better 280 * For instance if the candidate is a home network versus an unknown public wifi, 281 * the delta will be infinite, else compare Kepler scores etc… 282 * Negatve return values from this functions are meaningless per se, just trying to 283 * keep them distinct for debug purpose (i.e. -1, -2 etc...) 284 ***/ 285 private int compareNetwork(WifiConfiguration candidate) { 286 if (candidate == null) 287 return -3; 288 289 WifiConfiguration currentNetwork = mWifiStateMachine.getCurrentWifiConfiguration(); 290 if (currentNetwork == null) { 291 return 1000; 292 } 293 294 if (candidate.configKey(true).equals(currentNetwork.configKey(true))) { 295 return -2; 296 } 297 298 int order = compareWifiConfigurations(currentNetwork, candidate); 299 300 if (order > 0) { 301 //ascending: currentNetwork < candidate 302 return 10; //will try switch over to the candidate 303 } 304 305 return 0; 306 } 307 308 /** 309 * update the network history fields fo that configuration 310 * - if userTriggered, we mark the configuration as "non selfAdded" since the user has seen it 311 * and took over management 312 * - if it is a "connect", remember which network were there at the point of the connect, so 313 * as those networks get a relative lower score than the selected configuration 314 * 315 * @param netId 316 * @param userTriggered : if the update come from WiFiManager 317 * @param connect : if the update includes a connect 318 **/ 319 public void updateConfigurationHistory(int netId, boolean userTriggered, boolean connect) { 320 WifiConfiguration selected = mWifiConfigStore.getWifiConfiguration(netId); 321 if (selected == null) { 322 return; 323 } 324 325 if (selected.SSID == null) { 326 return; 327 } 328 329 if (userTriggered) { 330 // reenable autojoin for this network, 331 // since the user want to connect to this configuration 332 selected.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED); 333 selected.selfAdded = false; 334 } 335 336 if (DBG && userTriggered) { 337 if (selected.connectChoices != null) { 338 logDbg("updateConfigurationHistory will update " 339 + Integer.toString(netId) + " now: " 340 + Integer.toString(selected.connectChoices.size()) 341 + " uid=" + Integer.toString(selected.creatorUid), true); 342 } else { 343 logDbg("updateConfigurationHistory will update " 344 + Integer.toString(netId) 345 + " uid=" + Integer.toString(selected.creatorUid), true); 346 } 347 } 348 349 if (connect && userTriggered) { 350 boolean found = false; 351 List<WifiConfiguration> networks = 352 mWifiConfigStore.getRecentConfiguredNetworks(12000, false); 353 if (networks != null) { 354 for (WifiConfiguration config : networks) { 355 if (DBG) { 356 logDbg("updateConfigurationHistory got " + config.SSID + " nid=" 357 + Integer.toString(config.networkId)); 358 } 359 360 if (selected.configKey(true).equals(config.configKey(true))) { 361 found = true; 362 continue; 363 } 364 365 int rssi = WifiConfiguration.INVALID_RSSI; 366 if (config.visibility != null) { 367 rssi = config.visibility.rssi5; 368 if (config.visibility.rssi24 > rssi) 369 rssi = config.visibility.rssi24; 370 } 371 if (rssi < -80) { 372 continue; 373 } 374 375 //the selected configuration was preferred over a recently seen config 376 //hence remember the user's choice: 377 //add the recently seen config to the selected's connectChoices array 378 379 if (selected.connectChoices == null) { 380 selected.connectChoices = new HashMap<String, Integer>(); 381 } 382 383 logDbg("updateConfigurationHistory add a choice " + selected.configKey(true) 384 + " over " + config.configKey(true) 385 + " RSSI " + Integer.toString(rssi)); 386 387 //add the visible config to the selected's connect choice list 388 selected.connectChoices.put(config.configKey(true), rssi); 389 390 if (config.connectChoices != null) { 391 if (VDBG) { 392 logDbg("updateConfigurationHistory will remove " 393 + selected.configKey(true) + " from " + config.configKey(true)); 394 } 395 //remove the selected from the recently seen config's connectChoice list 396 config.connectChoices.remove(selected.configKey(true)); 397 398 if (selected.linkedConfigurations != null) { 399 //remove the selected's linked configuration from the 400 //recently seen config's connectChoice list 401 for (String key : selected.linkedConfigurations.keySet()) { 402 config.connectChoices.remove(key); 403 } 404 } 405 } 406 } 407 if (found == false) { 408 // log an error for now but do something stringer later 409 // we will need a new scan before attempting to connect to this 410 // configuration anyhow and thus we can process the scan results then 411 logDbg("updateConfigurationHistory try to connect to an old network!! : " 412 + selected.configKey()); 413 } 414 415 if (selected.connectChoices != null) { 416 if (VDBG) 417 logDbg("updateConfigurationHistory " + Integer.toString(netId) 418 + " now: " + Integer.toString(selected.connectChoices.size())); 419 } 420 } 421 } 422 423 //TODO: write only if something changed 424 if (userTriggered || connect) { 425 mWifiConfigStore.writeKnownNetworkHistory(); 426 } 427 } 428 429 void printChoices(WifiConfiguration config) { 430 int num = 0; 431 if (config.connectChoices!= null) { 432 num = config.connectChoices.size(); 433 } 434 435 logDbg("printChoices " + config.SSID + " num choices: " + Integer.toString(num)); 436 if (config.connectChoices!= null) { 437 for (String key : config.connectChoices.keySet()) { 438 logDbg(" " + key); 439 } 440 } 441 } 442 443 boolean hasConnectChoice(WifiConfiguration source, WifiConfiguration target) { 444 boolean found = false; 445 if (source == null) 446 return false; 447 if (target == null) 448 return false; 449 450 if (source.connectChoices != null) { 451 if ( source.connectChoices.get(target.configKey(true)) != null) { 452 found = true; 453 } 454 } 455 456 if (source.linkedConfigurations != null) { 457 for (String key : source.linkedConfigurations.keySet()) { 458 WifiConfiguration config = mWifiConfigStore.getWifiConfiguration(key); 459 if (config != null) { 460 if (config.connectChoices != null) { 461 if (config.connectChoices.get(target.configKey(true)) != null) { 462 found = true; 463 } 464 } 465 } 466 } 467 468 } 469 return found; 470 } 471 472 int compareWifiConfigurationsRSSI(WifiConfiguration a, WifiConfiguration b) { 473 int order = 0; 474 int boost5 = 25; 475 WifiConfiguration.Visibility astatus = a.visibility; 476 WifiConfiguration.Visibility bstatus = b.visibility; 477 if (astatus == null || bstatus == null) { 478 //error -> cant happen, need to throw en exception 479 logDbg("compareWifiConfigurations NULL band status!"); 480 return 0; 481 } 482 if ((astatus.rssi5 > -70) && (bstatus.rssi5 == WifiConfiguration.INVALID_RSSI) 483 && ((astatus.rssi5 + boost5) > (bstatus.rssi24))) { 484 //a is seen on 5GHz with good RSSI, greater rssi than b 485 //a is of higher priority - descending 486 order = -1; 487 } else if ((bstatus.rssi5 > -70) && (astatus.rssi5 == WifiConfiguration.INVALID_RSSI) 488 && ((bstatus.rssi5 + boost5) > (bstatus.rssi24))) { 489 //b is seen on 5GHz with good RSSI, greater rssi than a 490 //a is of lower priority - ascending 491 order = 1; 492 } 493 return order; 494 } 495 496 497 int compareWifiConfigurations(WifiConfiguration a, WifiConfiguration b) { 498 int order = 0; 499 String lastSelectedConfiguration = mWifiConfigStore.getLastSelectedConfiguration(); 500 boolean linked = false; 501 502 if ((a.linkedConfigurations != null) && (b.linkedConfigurations != null) 503 && (a.autoJoinStatus == WifiConfiguration.AUTO_JOIN_ENABLED) 504 && (b.autoJoinStatus == WifiConfiguration.AUTO_JOIN_ENABLED)) { 505 if ((a.linkedConfigurations.get(b.configKey(true))!= null) 506 && (b.linkedConfigurations.get(a.configKey(true))!= null)) { 507 linked = true; 508 } 509 } 510 511 if (a.ephemeral && b.ephemeral == false) { 512 if (VDBG) { 513 logDbg("compareWifiConfigurations ephemeral and prefers " + b.configKey() 514 + " over " + a.configKey()); 515 } 516 return 1; //b is of higher priority - ascending 517 } 518 if (b.ephemeral && a.ephemeral == false) { 519 if (VDBG) { 520 logDbg("compareWifiConfigurations ephemeral and prefers " +a.configKey() 521 + " over " + b.configKey()); 522 } 523 return -1; //a is of higher priority - descending 524 } 525 526 int aRssiBoost5 = 0; 527 int bRssiBoost5 = 0; 528 //apply Hysteresis: boost the RSSI value of the currently connected configuration 529 int aRssiBoost = 0; 530 int bRssiBoost = 0; 531 if (null != mCurrentConfigurationKey) { 532 if (a.configKey().equals(mCurrentConfigurationKey)) { 533 aRssiBoost += 10; 534 } else if (b.configKey().equals(mCurrentConfigurationKey)) { 535 bRssiBoost += 10; 536 } 537 } 538 if (linked) { 539 int ascore; 540 int bscore; 541 // then we try prefer 5GHz, and try to ignore user's choice 542 WifiConfiguration.Visibility astatus = a.visibility; 543 WifiConfiguration.Visibility bstatus = b.visibility; 544 if (astatus == null || bstatus == null) { 545 //error 546 logDbg("compareWifiConfigurations NULL band status!"); 547 return 0; 548 } 549 550 if (VDBG) { 551 logDbg("compareWifiConfigurations linked: " + Integer.toString(astatus.rssi5) 552 + "," + Integer.toString(astatus.rssi24) + " " 553 + Integer.toString(bstatus.rssi5) + "," 554 + Integer.toString(bstatus.rssi24)); 555 } 556 557 //Boost RSSI value of 5GHz bands iff the base value is better than -65 558 //This implements band preference where we prefer 5GHz if RSSI5 is good enough, whereas 559 //we prefer 2.4GHz otherwise. 560 //Note that 2.4GHz doesn't need a boost since at equal power the RSSI is 6-10 dB higher 561 if ((astatus.rssi5+aRssiBoost) > WifiConfiguration.A_BAND_PREFERENCE_RSSI_THRESHOLD) { 562 aRssiBoost5 = 25; 563 } 564 if ((bstatus.rssi5+bRssiBoost) > WifiConfiguration.A_BAND_PREFERENCE_RSSI_THRESHOLD) { 565 bRssiBoost5 = 25; 566 } 567 568 if (astatus.rssi5+aRssiBoost5 > astatus.rssi24) { 569 //prefer a's 5GHz 570 ascore = astatus.rssi5 + aRssiBoost5 + aRssiBoost; 571 } else { 572 //prefer a's 2.4GHz 573 ascore = astatus.rssi24 + aRssiBoost; 574 } 575 if (bstatus.rssi5+bRssiBoost5 > bstatus.rssi24) { 576 //prefer b's 5GHz 577 bscore = bstatus.rssi5 + bRssiBoost5 + bRssiBoost; 578 } else { 579 //prefer b's 2.4GHz 580 bscore = bstatus.rssi24 + bRssiBoost; 581 } 582 if (ascore > bscore) { 583 //a is seen on 5GHz with good RSSI, greater rssi than b 584 //a is of higher priority - descending 585 order = -10; 586 if (VDBG) { 587 logDbg("compareWifiConfigurations linked and prefers " + a.configKey() 588 + " rssi=(" + a.visibility.rssi24 589 + "," + a.visibility.rssi5 590 + ") num=(" + a.visibility.num24 591 + "," + a.visibility.num5 + ")" 592 + " over " + b.configKey() 593 + " rssi=(" + b.visibility.rssi24 594 + "," + b.visibility.rssi5 595 + ") num=(" + b.visibility.num24 596 + "," + b.visibility.num5 + ")" 597 + " due to RSSI"); 598 } 599 } else if (bscore > ascore) { 600 //b is seen on 5GHz with good RSSI, greater rssi than a 601 //a is of lower priority - ascending 602 order = 10; 603 if (VDBG) { 604 logDbg("compareWifiConfigurations linked and prefers " + b.configKey() 605 + " rssi=(" + b.visibility.rssi24 606 + "," + b.visibility.rssi5 607 + ") num=(" + b.visibility.num24 608 + "," + b.visibility.num5 + ")" 609 + " over " + a.configKey() 610 + " rssi=(" + a.visibility.rssi24 611 + "," + a.visibility.rssi5 612 + ") num=(" + a.visibility.num24 613 + "," + a.visibility.num5 + ")" 614 + " due to RSSI"); 615 } 616 } 617 } 618 619 //compare by user's choice. 620 if (hasConnectChoice(a, b)) { 621 //a is of higher priority - descending 622 order = order -2; 623 if (VDBG) { 624 logDbg("compareWifiConfigurations prefers -2 " + a.configKey() 625 + " over " + b.configKey() 626 + " due to user choice order -> " + Integer.toString(order)); 627 } 628 } 629 630 if (hasConnectChoice(b, a)) { 631 //a is of lower priority - ascending 632 order = order + 2; 633 if (VDBG) { 634 logDbg("compareWifiConfigurations prefers +2 " + b.configKey() + " over " 635 + a.configKey() + " due to user choice order ->" + Integer.toString(order)); 636 } 637 } 638 639 //TODO count the number of association rejection 640 // and use this to adjust the order by more than +/- 3 641 if ((a.status == WifiConfiguration.Status.DISABLED) 642 && (a.disableReason == WifiConfiguration.DISABLED_ASSOCIATION_REJECT)) { 643 //a is of lower priority - ascending 644 //lower the comparison score a bit 645 order = order +3; 646 } 647 if ((b.status == WifiConfiguration.Status.DISABLED) 648 && (b.disableReason == WifiConfiguration.DISABLED_ASSOCIATION_REJECT)) { 649 //a is of higher priority - descending 650 //lower the comparison score a bit 651 order = order -3; 652 } 653 654 if ((lastSelectedConfiguration != null) 655 && a.configKey().equals(lastSelectedConfiguration)) { 656 // a is the last selected configuration, so keep it above connect choices (+/-2) and 657 // above RSSI based selection of linked configuration (+/- 11) 658 // by giving a -11 659 // Additional other factors like BAD RSSI (still to do) and 660 // ASSOC_REJECTION high counts will then still 661 // tip the auto-join to roam 662 order = order - 11; 663 if (VDBG) { 664 logDbg("compareWifiConfigurations prefers -11 " + a.configKey() 665 + " over " + b.configKey() + " because a is the last selected -> " 666 + Integer.toString(order)); 667 } 668 } else if ((lastSelectedConfiguration != null) 669 && b.configKey().equals(lastSelectedConfiguration)) { 670 // b is the last selected configuration, so keep it above connect choices (+/-2) and 671 // above RSSI based selection of linked configuration (+/- 11) 672 // by giving a +11 673 // Additional other factors like BAD RSSI (still to do) and 674 // ASSOC_REJECTION high counts will then still 675 // tip the auto-join to roam 676 order = order + 11; 677 if (VDBG) { 678 logDbg("compareWifiConfigurations prefers +11 " + a.configKey() 679 + " over " + b.configKey() + " because b is the last selected -> " 680 + Integer.toString(order)); 681 } 682 } 683 684 if (order == 0) { 685 //we don't know anything - pick the last seen i.e. K behavior 686 //we should do this only for recently picked configurations 687 if (a.priority > b.priority) { 688 //a is of higher priority - descending 689 if (VDBG) { 690 logDbg("compareWifiConfigurations prefers -1 " + a.configKey() + " over " 691 + b.configKey() + " due to priority"); 692 } 693 694 order = -1; 695 } else if (a.priority < b.priority) { 696 //a is of lower priority - ascending 697 if (VDBG) { 698 logDbg("compareWifiConfigurations prefers +1 " + b.configKey() + " over " 699 + a.configKey() + " due to priority"); 700 } 701 702 order = 1; 703 } else { 704 //maybe just look at RSSI or band 705 if (VDBG) { 706 logDbg("compareWifiConfigurations prefers +1 " + b.configKey() + " over " 707 + a.configKey() + " due to nothing"); 708 } 709 710 order = compareWifiConfigurationsRSSI(a, b); //compare RSSI 711 } 712 } 713 714 String sorder = " == "; 715 if (order > 0) 716 sorder = " < "; 717 if (order < 0) 718 sorder = " > "; 719 720 if (VDBG) { 721 logDbg("compareWifiConfigurations Done: " + a.configKey() + sorder 722 + b.configKey() + " order " + Integer.toString(order)); 723 } 724 725 return order; 726 } 727 728 /* attemptRoam function implement the core of the same SSID switching algorithm */ 729 ScanResult attemptRoam(WifiConfiguration current, int age) { 730 ScanResult a = null; 731 if (current == null) { 732 if (VDBG) { 733 logDbg("attemptRoam not associated"); 734 } 735 return null; 736 } 737 if (current.scanResultCache == null) { 738 if (VDBG) { 739 logDbg("attemptRoam no scan cache"); 740 } 741 return null; 742 } 743 if (current.scanResultCache.size() > 6) { 744 if (VDBG) { 745 logDbg("attemptRoam scan cache size " 746 + current.scanResultCache.size() + " --> bail"); 747 } 748 //implement same SSID roaming only for configurations 749 // that have less than 4 BSSIDs 750 return null; 751 } 752 String currentBSSID = mWifiStateMachine.getCurrentBSSID(); 753 if (currentBSSID == null) { 754 if (DBG) { 755 logDbg("attemptRoam currentBSSID unknown"); 756 } 757 return null; 758 } 759 760 if (current.bssidOwnerUid!= 0 && current.bssidOwnerUid != Process.WIFI_UID) { 761 if (DBG) { 762 logDbg("attemptRoam BSSID owner is " 763 + Long.toString(current.bssidOwnerUid) + " -> bail"); 764 } 765 return null; 766 } 767 768 //determine which BSSID we want to associate to, taking account 769 // relative strength of 5 and 2.4 GHz BSSIDs 770 long now_ms = System.currentTimeMillis(); 771 int bRssiBoost5 = 0; 772 int aRssiBoost5 = 0; 773 int bRssiBoost = 0; 774 int aRssiBoost = 0; 775 for (ScanResult b : current.scanResultCache.values()) { 776 777 if ((b.seen == 0) || (b.BSSID == null)) { 778 continue; 779 } 780 781 if (b.status != ScanResult.ENABLED) { 782 continue; 783 } 784 785 if ((now_ms - b.seen) > age) continue; 786 787 //pick first one 788 if (a == null) { 789 a = b; 790 continue; 791 } 792 793 if (currentBSSID.equals(b.BSSID)) { 794 //reduce the benefit of hysteresis if RSSI <= -75 795 if (b.level <= WifiConfiguration.G_BAND_PREFERENCE_RSSI_THRESHOLD) { 796 bRssiBoost = +6; 797 } else { 798 bRssiBoost = +10; 799 } 800 } 801 if (currentBSSID.equals(a.BSSID)) { 802 if (a.level <= WifiConfiguration.G_BAND_PREFERENCE_RSSI_THRESHOLD) { 803 //reduce the benefit of hysteresis if RSSI <= -75 804 aRssiBoost = +6; 805 } else { 806 aRssiBoost = +10; 807 } 808 } 809 if (b.is5GHz() && (b.level+bRssiBoost) 810 > WifiConfiguration.A_BAND_PREFERENCE_RSSI_THRESHOLD) { 811 bRssiBoost5 = 25; 812 } else if (b.is5GHz() && (b.level+bRssiBoost) 813 < WifiConfiguration.G_BAND_PREFERENCE_RSSI_THRESHOLD) { 814 bRssiBoost5 = -10; 815 } 816 if (a.is5GHz() && (a.level+aRssiBoost) 817 > WifiConfiguration.A_BAND_PREFERENCE_RSSI_THRESHOLD) { 818 aRssiBoost5 = 25; 819 } else if (a.is5GHz() && (a.level+aRssiBoost) 820 < WifiConfiguration.G_BAND_PREFERENCE_RSSI_THRESHOLD) { 821 aRssiBoost5 = -10; 822 } 823 824 if (VDBG) { 825 String comp = " < "; 826 if (b.level + bRssiBoost + bRssiBoost5 > a.level +aRssiBoost + aRssiBoost5) { 827 comp = " > "; 828 } 829 logDbg("attemptRoam: " 830 + b.BSSID + " rssi=" + b.level + " boost=" + Integer.toString(bRssiBoost) 831 + "/" + Integer.toString(bRssiBoost5) + " freq=" + b.frequency + comp 832 + a.BSSID + " rssi=" + a.level + " boost=" + Integer.toString(aRssiBoost) 833 + "/" + Integer.toString(aRssiBoost5) + " freq=" + a.frequency); 834 } 835 836 if (b.level + bRssiBoost + bRssiBoost5 > a.level +aRssiBoost + aRssiBoost5) { 837 //b is the better BSSID 838 a = b; 839 } 840 } 841 if (a != null) { 842 if (VDBG) { 843 logDbg("attemptRoam: Found " 844 + a.BSSID + " rssi=" + a.level + " freq=" + a.frequency 845 + " Current: " + currentBSSID); 846 } 847 if (currentBSSID.equals(a.BSSID)) { 848 return null; 849 } 850 } 851 return a; 852 } 853 854 /* attemptAutoJoin function implement the core of the a network switching algorithm */ 855 void attemptAutoJoin() { 856 int networkSwitchType = AUTO_JOIN_IDLE; 857 858 String lastSelectedConfiguration = mWifiConfigStore.getLastSelectedConfiguration(); 859 860 // reset the currentConfiguration Key, and set it only if WifiStateMachine and 861 // supplicant agree 862 mCurrentConfigurationKey = null; 863 WifiConfiguration currentConfiguration = mWifiStateMachine.getCurrentWifiConfiguration(); 864 865 WifiConfiguration candidate = null; 866 867 /* obtain the subset of recently seen networks */ 868 List<WifiConfiguration> list = mWifiConfigStore.getRecentConfiguredNetworks(3000, false); 869 if (list == null) { 870 if (VDBG) logDbg("attemptAutoJoin nothing"); 871 return; 872 } 873 874 /* find the currently connected network: ask the supplicant directly */ 875 String val = mWifiNative.status(); 876 String status[] = val.split("\\r?\\n"); 877 if (VDBG) { 878 logDbg("attemptAutoJoin() status=" + val + " split=" 879 + Integer.toString(status.length)); 880 } 881 882 int supplicantNetId = -1; 883 for (String key : status) { 884 if (key.regionMatches(0, "id=", 0, 3)) { 885 int idx = 3; 886 supplicantNetId = 0; 887 while (idx < key.length()) { 888 char c = key.charAt(idx); 889 890 if ((c >= 0x30) && (c <= 0x39)) { 891 supplicantNetId *= 10; 892 supplicantNetId += c - 0x30; 893 idx++; 894 } else { 895 break; 896 } 897 } 898 } 899 } 900 if (DBG) { 901 logDbg("attemptAutoJoin() num recent config " + Integer.toString(list.size()) 902 + " ---> suppId=" + Integer.toString(supplicantNetId)); 903 } 904 905 if (currentConfiguration != null) { 906 if (supplicantNetId != currentConfiguration.networkId) { 907 logDbg("attemptAutoJoin() ERROR wpa_supplicant out of sync nid=" 908 + Integer.toString(supplicantNetId) + " WifiStateMachine=" 909 + Integer.toString(currentConfiguration.networkId)); 910 mWifiStateMachine.disconnectCommand(); 911 return; 912 } else { 913 mCurrentConfigurationKey = currentConfiguration.configKey(); 914 } 915 } 916 917 int currentNetId = -1; 918 if (currentConfiguration != null) { 919 // if we are associated to a configuration, it will 920 // be compared thru the compareNetwork function 921 currentNetId = currentConfiguration.networkId; 922 } 923 924 /* run thru all visible configurations without looking at the one we 925 * are currently associated to 926 * select Best Network candidate from known WifiConfigurations 927 * */ 928 for (WifiConfiguration config : list) { 929 if ((config.status == WifiConfiguration.Status.DISABLED) 930 && (config.disableReason == WifiConfiguration.DISABLED_AUTH_FAILURE)) { 931 if (DBG) { 932 logDbg("attemptAutoJoin skip candidate due to auth failure: " 933 + config.configKey(true)); 934 } 935 continue; 936 } 937 938 if (config.SSID == null) { 939 continue; 940 } 941 942 if (config.autoJoinStatus >= 943 WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE) { 944 //avoid temporarily disabled networks altogether 945 //TODO: implement a better logic which will re-enable the network after some time 946 if (DBG) { 947 logDbg("attemptAutoJoin skip candidate due to auto join status " 948 + Integer.toString(config.autoJoinStatus) + " key " 949 + config.configKey(true)); 950 } 951 continue; 952 } 953 954 //try to unblacklist based on elapsed time 955 if (config.blackListTimestamp > 0) { 956 long now = System.currentTimeMillis(); 957 if (now < config.blackListTimestamp) { 958 //looks like there was a change in the system clock since we black listed, and 959 //timestamp is not meaningful anymore, hence lose it. 960 //this event should be rare enough so that we still want to lose the black list 961 config.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED); 962 } else { 963 if ((now - config.blackListTimestamp) > loseBlackListHardMilli) { 964 //reenable it after 18 hours, i.e. next day 965 config.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED); 966 } else if ((now - config.blackListTimestamp) > loseBlackListSoftMilli) { 967 //lose blacklisting due to bad link 968 config.setAutoJoinStatus(config.autoJoinStatus - 8); 969 } 970 } 971 } 972 973 //try to unblacklist based on good visibility 974 if (config.visibility.rssi5 < WifiConfiguration.UNBLACKLIST_THRESHOLD_5_SOFT 975 && config.visibility.rssi24 < WifiConfiguration.UNBLACKLIST_THRESHOLD_24_SOFT) { 976 if (DBG) { 977 logDbg("attemptAutoJoin skip candidate due to auto join status " 978 + config.autoJoinStatus 979 + " key " + config.configKey(true) 980 + " rssi=(" + config.visibility.rssi24 981 + "," + config.visibility.rssi5 982 + ") num=(" + config.visibility.num24 983 + "," + config.visibility.num5 + ")"); 984 } 985 } else if (config.visibility.rssi5 < WifiConfiguration.UNBLACKLIST_THRESHOLD_5_HARD 986 && config.visibility.rssi24 < WifiConfiguration.UNBLACKLIST_THRESHOLD_24_HARD) { 987 // if the network is simply temporary disabled, don't allow reconnect until 988 // rssi becomes good enough 989 config.setAutoJoinStatus(config.autoJoinStatus - 1); 990 if (DBG) { 991 logDbg("attemptAutoJoin good candidate seen, bumped soft -> status=" 992 + config.autoJoinStatus 993 + " key " + config.configKey(true) + " rssi=(" 994 + config.visibility.rssi24 + "," + config.visibility.rssi5 995 + ") num=(" + config.visibility.num24 996 + "," + config.visibility.num5 + ")"); 997 } 998 } else { 999 config.setAutoJoinStatus(config.autoJoinStatus - 3); 1000 if (DBG) { 1001 logDbg("attemptAutoJoin good candidate seen, bumped hard -> status=" 1002 + config.autoJoinStatus 1003 + " key " + config.configKey(true) + " rssi=(" 1004 + config.visibility.rssi24 + "," + config.visibility.rssi5 1005 + ") num=(" + config.visibility.num24 1006 + "," + config.visibility.num5 + ")"); 1007 } 1008 } 1009 1010 if (config.autoJoinStatus >= 1011 WifiConfiguration.AUTO_JOIN_TEMPORARY_DISABLED) { 1012 //network is blacklisted, skip 1013 if (DBG) { 1014 logDbg("attemptAutoJoin skip blacklisted -> status=" 1015 + config.autoJoinStatus 1016 + " key " + config.configKey(true) + " rssi=(" 1017 + config.visibility.rssi24 + "," + config.visibility.rssi5 1018 + ") num=(" + config.visibility.num24 1019 + "," + config.visibility.num5 + ")"); 1020 } 1021 continue; 1022 } 1023 if (config.networkId == currentNetId) { 1024 if (DBG) { 1025 logDbg("attemptAutoJoin skip current candidate " 1026 + Integer.toString(currentNetId) 1027 + " key " + config.configKey(true)); 1028 } 1029 continue; 1030 } 1031 1032 if (lastSelectedConfiguration == null || 1033 !config.configKey().equals(lastSelectedConfiguration)) { 1034 //don't try to autojoin a network that is too far 1035 if (config.visibility == null) { 1036 continue; 1037 } 1038 if (config.visibility.rssi5 < WifiConfiguration.INITIAL_AUTO_JOIN_ATTEMPT_MIN_5 1039 && config.visibility.rssi24 1040 < WifiConfiguration.INITIAL_AUTO_JOIN_ATTEMPT_MIN_24) { 1041 if (DBG) { 1042 logDbg("attemptAutoJoin gskip due to low visibility -> status=" 1043 + config.autoJoinStatus 1044 + " key " + config.configKey(true) + " rssi=" 1045 + config.visibility.rssi24 + ", " + config.visibility.rssi5 1046 + " num=" + config.visibility.num24 1047 + ", " + config.visibility.num5); 1048 } 1049 continue; 1050 } 1051 } 1052 1053 if (DBG) { 1054 logDbg("attemptAutoJoin trying candidate id=" 1055 + Integer.toString(config.networkId) + " " 1056 + config.SSID + " key " + config.configKey(true) 1057 + " status=" + config.autoJoinStatus); 1058 } 1059 1060 if (candidate == null) { 1061 candidate = config; 1062 } else { 1063 if (VDBG) { 1064 logDbg("attemptAutoJoin will compare candidate " + candidate.configKey() 1065 + " with " + config.configKey()); 1066 } 1067 1068 int order = compareWifiConfigurations(candidate, config); 1069 if (order > 0) { 1070 //ascending : candidate < config 1071 candidate = config; 1072 } 1073 } 1074 } 1075 1076 /* now, go thru scan result to try finding a better Herrevad network */ 1077 if (mNetworkScoreCache != null) { 1078 int rssi5 = WifiConfiguration.INVALID_RSSI; 1079 int rssi24 = WifiConfiguration.INVALID_RSSI; 1080 WifiConfiguration.Visibility visibility; 1081 if (candidate != null) { 1082 rssi5 = candidate.visibility.rssi5; 1083 rssi24 = candidate.visibility.rssi24; 1084 } 1085 1086 //get current date 1087 long now_ms = System.currentTimeMillis(); 1088 1089 if (rssi5 < -60 && rssi24 < -70) { 1090 for (ScanResult result : scanResultCache.values()) { 1091 if ((now_ms - result.seen) < 3000) { 1092 int score = mNetworkScoreCache.getNetworkScore(result); 1093 if (score > 0) { 1094 // try any arbitrary formula for now, adding apple and oranges, 1095 // i.e. adding network score and "dBm over noise" 1096 if (result.is24GHz()) { 1097 if ((result.level + score) > (rssi24 -40)) { 1098 // force it as open, TBD should we otherwise verify that this 1099 // BSSID only supports open?? 1100 result.capabilities = ""; 1101 1102 //switch to this scan result 1103 candidate = 1104 mWifiConfigStore.wifiConfigurationFromScanResult(result); 1105 candidate.ephemeral = true; 1106 } 1107 } else { 1108 if ((result.level + score) > (rssi5 -30)) { 1109 // force it as open, TBD should we otherwise verify that this 1110 // BSSID only supports open?? 1111 result.capabilities = ""; 1112 1113 //switch to this scan result 1114 candidate = 1115 mWifiConfigStore.wifiConfigurationFromScanResult(result); 1116 candidate.ephemeral = true; 1117 } 1118 } 1119 } 1120 } 1121 } 1122 } 1123 } 1124 1125 /* if candidate is found, check the state of the connection so as 1126 to decide if we should be acting on this candidate and switching over */ 1127 int networkDelta = compareNetwork(candidate); 1128 if (DBG && candidate != null) { 1129 logDbg("attemptAutoJoin compare SSID candidate : delta=" 1130 + Integer.toString(networkDelta) + " " 1131 + candidate.configKey() 1132 + " linked=" + (currentConfiguration != null 1133 && currentConfiguration.isLinked(candidate))); 1134 } 1135 1136 /* ASK WifiStateMachine permission to switch: 1137 for instance, 1138 if user is currently streaming voice traffic, 1139 then don’t switch regardless of the delta 1140 */ 1141 if (mWifiStateMachine.shouldSwitchNetwork(networkDelta)) { 1142 if (mStaStaSupported) { 1143 logDbg("mStaStaSupported --> error do nothing now "); 1144 } else { 1145 if (currentConfiguration != null && currentConfiguration.isLinked(candidate)) { 1146 networkSwitchType = AUTO_JOIN_EXTENDED_ROAMING; 1147 } else { 1148 networkSwitchType = AUTO_JOIN_OUT_OF_NETWORK_ROAMING; 1149 } 1150 if (DBG) { 1151 logDbg("AutoJoin auto connect with netId " 1152 + Integer.toString(candidate.networkId) 1153 + " to " + candidate.configKey()); 1154 } 1155 mWifiStateMachine.sendMessage(WifiStateMachine.CMD_AUTO_CONNECT, 1156 candidate.networkId, networkSwitchType, candidate); 1157 } 1158 } 1159 1160 if (networkSwitchType == AUTO_JOIN_IDLE) { 1161 //attempt same WifiConfiguration roaming 1162 ScanResult roamCandidate = attemptRoam(currentConfiguration, 3000); 1163 if (roamCandidate != null) { 1164 if (DBG) { 1165 logDbg("AutoJoin auto roam with netId " 1166 + Integer.toString(currentConfiguration.networkId) 1167 + " " + currentConfiguration.configKey() + " to BSSID=" 1168 + roamCandidate.BSSID + " freq=" + roamCandidate.frequency 1169 + " RSSI=" + roamCandidate.frequency); 1170 } 1171 networkSwitchType = AUTO_JOIN_ROAMING; 1172 mWifiStateMachine.sendMessage(WifiStateMachine.CMD_AUTO_ROAM, 1173 currentConfiguration.networkId, 1, roamCandidate); 1174 } 1175 } 1176 if (VDBG) logDbg("Done attemptAutoJoin status=" + Integer.toString(networkSwitchType)); 1177 } 1178} 1179 1180