WifiAutoJoinController.java revision dfcb187e5608e125fe161d70226c300bd7b781ad
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.util.Log; 30 31import java.util.ArrayList; 32import java.util.Collection; 33import java.util.Iterator; 34import java.util.HashMap; 35import java.util.List; 36import java.util.Date; 37 38/** 39 * AutoJoin controller is responsible for WiFi Connect decision 40 * 41 * It runs in the thread context of WifiStateMachine 42 * 43 */ 44public class WifiAutoJoinController { 45 46 private Context mContext; 47 private WifiStateMachine mWifiStateMachine; 48 private WifiConfigStore mWifiConfigStore; 49 private WifiTrafficPoller mWifiTrafficPoller; 50 private WifiNative mWifiNative; 51 52 private NetworkScoreManager scoreManager; 53 private WifiNetworkScoreCache mNetworkScoreCache; 54 55 private static final String TAG = "WifiAutoJoinController "; 56 private static boolean DBG = false; 57 private static boolean VDBG = false; 58 private static final boolean mStaStaSupported = false; 59 private static final int SCAN_RESULT_CACHE_SIZE = 80; 60 61 private String mCurrentConfigurationKey = null; //used by autojoin 62 63 private HashMap<String, ScanResult> scanResultCache = 64 new HashMap<String, ScanResult>(); 65 66 WifiAutoJoinController(Context c, WifiStateMachine w, WifiConfigStore s, 67 WifiTrafficPoller t, WifiNative n) { 68 mContext = c; 69 mWifiStateMachine = w; 70 mWifiConfigStore = s; 71 mWifiTrafficPoller = t; 72 mWifiNative = n; 73 mNetworkScoreCache = null; 74 scoreManager = (NetworkScoreManager) mContext.getSystemService(Context.NETWORK_SCORE_SERVICE); 75 if (scoreManager == null) 76 logDbg("Registered scoreManager NULL " + " service " + Context.NETWORK_SCORE_SERVICE); 77 else 78 logDbg("Registered scoreManager NOT NULL" + " service " + Context.NETWORK_SCORE_SERVICE); 79 80 if (scoreManager != null) { 81 mNetworkScoreCache = new WifiNetworkScoreCache(mContext); 82 scoreManager.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache); 83 } else { 84 logDbg("No network score service: Couldnt register as a WiFi score Manager, type=" 85 + Integer.toString(NetworkKey.TYPE_WIFI) 86 + " service " + Context.NETWORK_SCORE_SERVICE); 87 mNetworkScoreCache = null; 88 } 89 } 90 91 void enableVerboseLogging(int verbose) { 92 if (verbose > 0 ) { 93 VDBG = true; 94 } else { 95 VDBG = false; 96 } 97 } 98 99 100 int mScanResultMaximumAge = 30000; /* milliseconds unit */ 101 102 /* 103 * flush out scan results older than mScanResultMaximumAge 104 * 105 * */ 106 private void ageScanResultsOut(int delay) { 107 if (delay <= 0) { 108 delay = mScanResultMaximumAge; //something sane 109 } 110 Date now = new Date(); 111 long milli = now.getTime(); 112 if (VDBG) { 113 logDbg("ageScanResultsOut delay " + Integer.valueOf(delay) + " size " 114 + Integer.valueOf(scanResultCache.size()) + " now " + Long.valueOf(milli)); 115 } 116 117 Iterator<HashMap.Entry<String,ScanResult>> iter = scanResultCache.entrySet().iterator(); 118 while (iter.hasNext()) { 119 HashMap.Entry<String,ScanResult> entry = iter.next(); 120 ScanResult result = entry.getValue(); 121 122 if ((result.seen + delay) < milli) { 123 iter.remove(); 124 } 125 } 126 } 127 128 void addToScanCache(List<ScanResult> scanList) { 129 WifiConfiguration associatedConfig; 130 131 ArrayList<NetworkKey> unknownScanResults = new ArrayList<NetworkKey>(); 132 133 for(ScanResult result: scanList) { 134 result.seen = System.currentTimeMillis(); 135 136 ScanResult sr = scanResultCache.get(result.BSSID); 137 if (sr != null) { 138 // if there was a previous cache result for this BSSID, average the RSSI values 139 140 int previous_rssi = sr.level; 141 long previously_seen_milli = sr.seen; 142 143 /* average RSSI with previously seen instances of this scan result */ 144 int avg_rssi = result.level; 145 146 if ((previously_seen_milli > 0) 147 && (previously_seen_milli < mScanResultMaximumAge/2)) { 148 /* 149 * 150 * previously_seen_milli = 0 => RSSI = 0.5 * previous_seen_rssi + 0.5 * new_rssi 151 * 152 * If previously_seen_milli is 15+ seconds old: 153 * previously_seen_milli = 15000 => RSSI = new_rssi 154 * 155 */ 156 157 double alpha = 0.5 - (double)previously_seen_milli 158 / (double)mScanResultMaximumAge; 159 160 avg_rssi = (int)((double)avg_rssi * (1-alpha) + (double)previous_rssi * alpha); 161 } 162 result.level = avg_rssi; 163 164 //remove the previous Scan Result 165 scanResultCache.remove(result.BSSID); 166 } else { 167 if (!mNetworkScoreCache.isScoredNetwork(result)) { 168 //TODO : properly handle SSID formatting, i.e. among others check for string 169 //TODO : representing hexadecimal SSIDs 170 WifiKey wkey = new WifiKey("\"" + result.SSID + "\"", result.BSSID); 171 NetworkKey nkey = new NetworkKey(wkey); 172 //if we don't know this scan result then request a score to Herrevad 173 unknownScanResults.add(nkey); 174 } 175 } 176 177 scanResultCache.put(result.BSSID, new ScanResult(result)); 178 179 ScanResult srn = scanResultCache.get(result.BSSID); 180 181 //add this BSSID to the scanResultCache of the relevant WifiConfiguration 182 associatedConfig = mWifiConfigStore.updateSavedNetworkHistory(result); 183 184 //try to associate this BSSID to an existing Saved WifiConfiguration 185 if (associatedConfig == null) { 186 associatedConfig = mWifiConfigStore.associateWithConfiguration(result); 187 if (associatedConfig != null) { 188 if (VDBG) { 189 logDbg("addToScanCache save associated config " 190 + associatedConfig.SSID + " with " + associatedConfig.SSID); 191 } 192 mWifiStateMachine.sendMessage(WifiManager.SAVE_NETWORK, associatedConfig); 193 } 194 } 195 } 196 197 if (unknownScanResults.size() != 0) { 198 NetworkKey[] newKeys = 199 unknownScanResults.toArray(new NetworkKey[unknownScanResults.size()]); 200 //kick the score manager, we will get updated scores asynchronously 201 scoreManager.requestScores(newKeys); 202 } 203 } 204 205 void logDbg(String message) { 206 logDbg(message, false); 207 } 208 209 void logDbg(String message, boolean stackTrace) { 210 long now = SystemClock.elapsedRealtimeNanos(); 211 String ts = String.format("[%,d us] ", now / 1000); 212 if (stackTrace) { 213 Log.e(TAG, ts + message + " stack:" 214 + Thread.currentThread().getStackTrace()[2].getMethodName() + " - " 215 + Thread.currentThread().getStackTrace()[3].getMethodName() + " - " 216 + Thread.currentThread().getStackTrace()[4].getMethodName() + " - " 217 + Thread.currentThread().getStackTrace()[5].getMethodName()); 218 } else { 219 Log.e(TAG, ts + message); 220 } 221 } 222 223 /* called directly from WifiStateMachine */ 224 void newSupplicantResults() { 225 List<ScanResult> scanList = mWifiStateMachine.syncGetScanResultsList(); 226 addToScanCache(scanList); 227 ageScanResultsOut(mScanResultMaximumAge); 228 if (DBG) 229 logDbg("newSupplicantResults size=" + Integer.valueOf(scanResultCache.size()) ); 230 231 attemptAutoJoin(); 232 mWifiConfigStore.writeKnownNetworkHistory(); 233 234 } 235 236 237 /* not used at the moment 238 * should be a call back from WifiScanner HAL ?? 239 * this function is not hooked and working yet, it will receive scan results from WifiScanners 240 * with the list of IEs,then populate the capabilities by parsing the IEs and inject the scan 241 * results as normal. 242 */ 243 void newHalScanResults() { 244 List<ScanResult> scanList = null;//mWifiScanner.syncGetScanResultsList(); 245 String akm = WifiParser.parse_akm(null, null); 246 logDbg(akm); 247 addToScanCache(scanList); 248 ageScanResultsOut(0); 249 attemptAutoJoin(); 250 mWifiConfigStore.writeKnownNetworkHistory(); 251 } 252 253 /* network link quality changed, called directly from WifiTrafficPoller, 254 or by listening to Link Quality intent */ 255 void linkQualitySignificantChange() { 256 attemptAutoJoin(); 257 } 258 259 /* 260 * compare a WifiConfiguration against the current network, return a delta score 261 * If not associated, and the candidate will always be better 262 * For instance if the candidate is a home network versus an unknown public wifi, 263 * the delta will be infinite, else compare Kepler scores etc… 264 ***/ 265 private int compareNetwork(WifiConfiguration candidate) { 266 WifiConfiguration currentNetwork = mWifiStateMachine.getCurrentWifiConfiguration(); 267 if (currentNetwork == null) 268 return 1000; 269 270 if (candidate.configKey(true).equals(currentNetwork.configKey(true))) { 271 return -1; 272 } 273 274 int order = compareWifiConfigurations(currentNetwork, candidate); 275 276 if (order > 0) { 277 //ascending: currentNetwork < candidate 278 return 10; //will try switch over to the candidate 279 } 280 281 return 0; 282 } 283 284 /** 285 * update the network history fields fo that configuration 286 * - if userTriggered, we mark the configuration as "non selfAdded" since the user has seen it 287 * and took over management 288 * - if it is a "connect", remember which network were there at the point of the connect, so 289 * as those networks get a relative lower score than the selected configuration 290 * 291 * @param netId 292 * @param userTriggered : if the update come from WiFiManager 293 * @param connect : if the update includes a connect 294 **/ 295 public void updateConfigurationHistory(int netId, boolean userTriggered, boolean connect) { 296 WifiConfiguration selected = mWifiConfigStore.getWifiConfiguration(netId); 297 if (selected == null) { 298 return; 299 } 300 301 if (selected.SSID == null) { 302 return; 303 } 304 305 if (userTriggered) { 306 // reenable autojoin for this network, 307 // since the user want to connect to this configuration 308 selected.autoJoinStatus = WifiConfiguration.AUTO_JOIN_ENABLED; 309 selected.selfAdded = false; 310 } 311 312 if (DBG) { 313 if (selected.connectChoices != null) { 314 logDbg("updateConfigurationHistory will update " 315 + Integer.toString(netId) + " now: " 316 + Integer.toString(selected.connectChoices.size()), true); 317 } else { 318 logDbg("updateConfigurationHistory will update " 319 + Integer.toString(netId), true); 320 } 321 } 322 323 if (connect && userTriggered) { 324 boolean found = false; 325 List<WifiConfiguration> networks = 326 mWifiConfigStore.getRecentConfiguredNetworks(12000, false); 327 if (networks != null) { 328 for (WifiConfiguration config : networks) { 329 if (DBG) 330 logDbg("updateConfigurationHistory got " + config.SSID); 331 332 if (selected.configKey(true).equals(config.configKey(true))) { 333 found = true; 334 continue; 335 } 336 337 int rssi = WifiConfiguration.INVALID_RSSI; 338 if (config.visibility != null) { 339 rssi = config.visibility.rssi5; 340 if (config.visibility.rssi24 > rssi) 341 rssi = config.visibility.rssi24; 342 } 343 if (rssi < -80) { 344 continue; 345 } 346 347 //the selected configuration was preferred over a recently seen config 348 //hence remember the user's choice: 349 //add the recently seen config to the selected's connectChoices array 350 351 if (selected.connectChoices == null) { 352 selected.connectChoices = new HashMap<String, Integer>(); 353 } 354 355 logDbg("updateConfigurationHistory add a choice " + selected.configKey(true) 356 + " over " + config.configKey(true) 357 + " RSSI " + Integer.toString(rssi), true); 358 //add the visible config to the selected's connect choice list 359 selected.connectChoices.put(config.configKey(true), rssi); 360 361 if (config.connectChoices != null) { 362 if (VDBG) { 363 logDbg("updateConfigurationHistory will remove " 364 + selected.configKey(true) + " from " + config.configKey(true)); 365 } 366 //remove the selected from the recently seen config's connectChoice list 367 config.connectChoices.remove(selected.configKey(true)); 368 369 if (selected.linkedConfigurations != null) { 370 //remove the selected's linked configuration from the 371 //recently seen config's connectChoice list 372 for (String key : selected.linkedConfigurations.keySet()) { 373 config.connectChoices.remove(key); 374 } 375 } 376 } 377 } 378 if (found == false) { 379 // log an error for now but do something stringer later 380 // we will need a new scan before attempting to connect to this 381 // configuration anyhow and thus we can process the scan results then 382 logDbg("updateConfigurationHistory try to connect to an old network!! : " 383 + selected.configKey()); 384 } 385 386 if (selected.connectChoices != null) { 387 if (VDBG) 388 logDbg("updateConfigurationHistory " + Integer.toString(netId) 389 + " now: " + Integer.toString(selected.connectChoices.size())); 390 } 391 392 mWifiConfigStore.writeKnownNetworkHistory(); 393 } 394 } 395 } 396 397 void printChoices(WifiConfiguration config) { 398 int num = 0; 399 if (config.connectChoices!= null) { 400 num = config.connectChoices.size(); 401 } 402 403 logDbg("printChoices " + config.SSID + " num choices: " + Integer.toString(num)); 404 if (config.connectChoices!= null) { 405 for (String key : config.connectChoices.keySet()) { 406 logDbg(" " + key); 407 } 408 } 409 } 410 411 boolean hasConnectChoice(WifiConfiguration source, WifiConfiguration target) { 412 boolean found = false; 413 if (source == null) 414 return false; 415 if (target == null) 416 return false; 417 418 if (source.connectChoices != null) { 419 if ( source.connectChoices.get(target.configKey(true)) != null) { 420 found = true; 421 } 422 } 423 424 if (source.linkedConfigurations != null) { 425 for (String key : source.linkedConfigurations.keySet()) { 426 WifiConfiguration config = mWifiConfigStore.getWifiConfiguration(key); 427 if (config != null) { 428 if (config.connectChoices != null) { 429 if (config.connectChoices.get(target.configKey(true)) != null) { 430 found = true; 431 } 432 } 433 } 434 } 435 } 436 return found; 437 } 438 439 int compareWifiConfigurationsRSSI(WifiConfiguration a, WifiConfiguration b) { 440 int order = 0; 441 int boost5 = 25; 442 443 WifiConfiguration.Visibility astatus = a.visibility; 444 WifiConfiguration.Visibility bstatus = b.visibility; 445 if (astatus == null || bstatus == null) { 446 //error -> cant happen, need to throw en exception 447 logDbg("compareWifiConfigurations NULL band status!"); 448 return 0; 449 } 450 if ((astatus.rssi5 > -70) && (bstatus.rssi5 == WifiConfiguration.INVALID_RSSI) 451 && ((astatus.rssi5+boost5) > (bstatus.rssi24))) { 452 //a is seen on 5GHz with good RSSI, greater rssi than b 453 //a is of higher priority - descending 454 order = -1; 455 } else if ((bstatus.rssi5 > -70) && (astatus.rssi5 == WifiConfiguration.INVALID_RSSI) 456 && ((bstatus.rssi5+boost5) > (bstatus.rssi24))) { 457 //b is seen on 5GHz with good RSSI, greater rssi than a 458 //a is of lower priority - ascending 459 order = 1; 460 } 461 return order; 462 } 463 464 int compareWifiConfigurations(WifiConfiguration a, WifiConfiguration b) { 465 int order = 0; 466 String lastSelectedConfiguration = mWifiConfigStore.getLastSelectedConfiguration(); 467 boolean linked = false; 468 469 if ((a.linkedConfigurations != null) && (b.linkedConfigurations != null) 470 && (a.autoJoinStatus == WifiConfiguration.AUTO_JOIN_ENABLED) 471 && (b.autoJoinStatus == WifiConfiguration.AUTO_JOIN_ENABLED)) { 472 if ((a.linkedConfigurations.get(b.configKey(true))!= null) 473 && (b.linkedConfigurations.get(a.configKey(true))!= null)) { 474 linked = true; 475 } 476 } 477 478 if (a.ephemeral && b.ephemeral == false) { 479 if (VDBG) { 480 logDbg("compareWifiConfigurations ephemeral and prefers " + b.configKey() 481 + " over " + a.configKey()); 482 } 483 return 1; //b is of higher priority - ascending 484 } 485 if (b.ephemeral && a.ephemeral == false) { 486 if (VDBG) { 487 logDbg("compareWifiConfigurations ephemeral and prefers " +a.configKey() 488 + " over " + b.configKey()); 489 } 490 return -1; //a is of higher priority - descending 491 } 492 493 int boost5 = 25; 494 //apply Hysteresis: boost the RSSI value of the currently connected configuration 495 int aRssiBoost = 0; 496 int bRssiBoost = 0; 497 if (null != mCurrentConfigurationKey) { 498 if (a.configKey().equals(mCurrentConfigurationKey)) { 499 aRssiBoost += 10; 500 } else if (b.configKey().equals(mCurrentConfigurationKey)) { 501 bRssiBoost += 10; 502 } 503 } 504 if (linked) { 505 // then we try prefer 5GHz, and try to ignore user's choice 506 WifiConfiguration.Visibility astatus = a.visibility; 507 WifiConfiguration.Visibility bstatus = b.visibility; 508 if (astatus == null || bstatus == null) { 509 //error 510 logDbg("compareWifiConfigurations NULL band status!"); 511 return 0; 512 } 513 514 if (VDBG) { 515 logDbg("compareWifiConfigurations linked: " + Integer.toString(astatus.rssi5) 516 + "," + Integer.toString(astatus.rssi24) + " " 517 + Integer.toString(bstatus.rssi5) + "," 518 + Integer.toString(bstatus.rssi24)); 519 } 520 521 if ((astatus.rssi5 > -70) && (bstatus.rssi5 <= WifiConfiguration.INVALID_RSSI) 522 && (astatus.rssi5+boost5+aRssiBoost) > (bstatus.rssi24+bRssiBoost)) { 523 //in this case: a has 5GHz and b doesn't have 5GHz 524 //compare a's 5GHz RSSI to b's 5GHz RSSI 525 526 //a is seen on 5GHz with good RSSI, greater rssi than b 527 //a is of higher priority - descending 528 order = -10; 529 530 if (VDBG) { 531 logDbg("compareWifiConfigurations linked and prefers " + a.configKey() 532 + " over " + b.configKey() 533 + " due to 5GHz RSSI " + Integer.toString(astatus.rssi5) 534 + " over: 5=" + Integer.toString(bstatus.rssi5) 535 + ", 2.4=" + Integer.toString(bstatus.rssi5)); 536 } 537 } else if ((bstatus.rssi5 > -70) && (astatus.rssi5 <= WifiConfiguration.INVALID_RSSI) 538 && ((bstatus.rssi5+boost5+bRssiBoost) > (astatus.rssi24+aRssiBoost))) { 539 //in this case: b has 5GHz and a doesn't have 5GHz 540 541 //b is seen on 5GHz with good RSSI, greater rssi than a 542 //a is of lower priority - ascending 543 if (VDBG) { 544 logDbg("compareWifiConfigurations linked and prefers " + b.configKey() 545 + " over " + a.configKey() + " due to 5GHz RSSI " 546 + Integer.toString(astatus.rssi5) + " over: 5=" 547 + Integer.toString(bstatus.rssi5) + ", 2.4=" 548 + Integer.toString(bstatus.rssi5)); 549 } 550 order = 10; 551 } else { 552 //TODO: handle cases where configurations are dual band 553 } 554 } 555 556 //compare by user's choice. 557 if (hasConnectChoice(a, b)) { 558 //a is of higher priority - descending 559 order = order -2; 560 if (VDBG) { 561 logDbg("compareWifiConfigurations prefers -2 " + a.configKey() 562 + " over " + b.configKey() 563 + " due to user choice order -> " + Integer.toString(order)); 564 } 565 } 566 567 if (hasConnectChoice(b, a)) { 568 //a is of lower priority - ascending 569 order = order + 2; 570 if (VDBG) { 571 logDbg("compareWifiConfigurations prefers +2 " + b.configKey() + " over " 572 + a.configKey() + " due to user choice order ->" + Integer.toString(order)); 573 } 574 } 575 576 //TODO count the number of association rejection 577 // and use this to adjust the order by more than +/- 3 578 if ((a.status == WifiConfiguration.Status.DISABLED) 579 && (a.disableReason == WifiConfiguration.DISABLED_ASSOCIATION_REJECT)) { 580 //a is of lower priority - ascending 581 //lower the comparison score a bit 582 order = order +3; 583 } 584 if ((b.status == WifiConfiguration.Status.DISABLED) 585 && (b.disableReason == WifiConfiguration.DISABLED_ASSOCIATION_REJECT)) { 586 //a is of higher priority - descending 587 //lower the comparison score a bit 588 order = order -3; 589 } 590 591 if ((lastSelectedConfiguration != null) 592 && a.configKey().equals(lastSelectedConfiguration)) { 593 // a is the last selected configuration, so keep it above connect choices 594 //by giving a -4 (whereas connect choice preference gives +2) 595 order = order - 4; 596 if (VDBG) { 597 logDbg("compareWifiConfigurations prefers -4 " + a.configKey() 598 + " over " + b.configKey() + " because a is the last selected -> " 599 + Integer.toString(order)); 600 } 601 } else if ((lastSelectedConfiguration != null) 602 && b.configKey().equals(lastSelectedConfiguration)) { 603 // b is the last selected configuration, so keep it above connect choices 604 //by giving a +4 (whereas connect choice preference gives -2) 605 order = order + 4; 606 if (VDBG) { 607 logDbg("compareWifiConfigurations prefers +4 " + a.configKey() 608 + " over " + b.configKey() + " because b is the last selected -> " 609 + Integer.toString(order)); 610 } 611 } 612 613 if (order == 0) { 614 //we don't know anything - pick the last seen i.e. K behavior 615 //we should do this only for recently picked configurations 616 if (a.priority > b.priority) { 617 //a is of higher priority - descending 618 if (VDBG) { 619 logDbg("compareWifiConfigurations prefers -1 " + a.configKey() + " over " 620 + b.configKey() + " due to priority"); 621 } 622 623 order = -1; 624 } else if (a.priority < b.priority) { 625 //a is of lower priority - ascending 626 if (VDBG) { 627 logDbg("compareWifiConfigurations prefers +1 " + b.configKey() + " over " 628 + a.configKey() + " due to priority"); 629 } 630 631 order = 1; 632 } else { 633 //maybe just look at RSSI or band 634 if (VDBG) { 635 logDbg("compareWifiConfigurations prefers +1 " + b.configKey() + " over " 636 + a.configKey() + " due to nothing"); 637 } 638 639 order = compareWifiConfigurationsRSSI(a, b); //compare RSSI 640 } 641 } 642 643 String sorder = " == "; 644 if (order > 0) 645 sorder = " < "; 646 if (order < 0) 647 sorder = " > "; 648 649 if (VDBG) { 650 logDbg("compareWifiConfigurations Done: " + a.configKey() + sorder 651 + b.configKey() + " order " + Integer.toString(order)); 652 } 653 654 return order; 655 } 656 657 /* attemptAutoJoin function implement the core of the a network switching algorithm */ 658 void attemptAutoJoin() { 659 String lastSelectedConfiguration = mWifiConfigStore.getLastSelectedConfiguration(); 660 661 //reset the currentConfiguration Key, and set it only if WifiStateMachine and 662 // supplicant agree 663 mCurrentConfigurationKey = null; 664 WifiConfiguration currentConfiguration = mWifiStateMachine.getCurrentWifiConfiguration(); 665 666 WifiConfiguration candidate = null; 667 668 /* obtain the subset of recently seen networks */ 669 List<WifiConfiguration> list = mWifiConfigStore.getRecentConfiguredNetworks(3000, true); 670 if (list == null) { 671 if (VDBG) logDbg("attemptAutoJoin nothing"); 672 return; 673 } 674 675 /* find the currently connected network: ask the supplicant directly */ 676 String val = mWifiNative.status(); 677 String status[] = val.split("\\r?\\n"); 678 if (VDBG) { 679 logDbg("attemptAutoJoin() status=" + val + " split=" 680 + Integer.toString(status.length)); 681 } 682 683 int currentNetId = -1; 684 for (String key : status) { 685 if (key.regionMatches(0, "id=", 0, 3)) { 686 int idx = 3; 687 currentNetId = 0; 688 while (idx < key.length()) { 689 char c = key.charAt(idx); 690 691 if ((c >= 0x30) && (c <= 0x39)) { 692 currentNetId *= 10; 693 currentNetId += c - 0x30; 694 idx++; 695 } else { 696 break; 697 } 698 } 699 } 700 } 701 if (DBG) { 702 logDbg("attemptAutoJoin() num recent config " + Integer.toString(list.size()) 703 + " ---> currentId=" + Integer.toString(currentNetId)); 704 } 705 706 if (currentConfiguration != null) { 707 if (currentNetId != currentConfiguration.networkId) { 708 logDbg("attemptAutoJoin() ERROR wpa_supplicant out of sync nid=" 709 + Integer.toString(currentNetId) + " WifiStateMachine=" 710 + Integer.toString(currentConfiguration.networkId)); 711 //I think this can happen due do race conditions, now what to do?? 712 // -> throw an exception, or, 713 // -> dont use the current configuration at all for autojoin 714 //and hope that autojoining will kick us out of this state. 715 currentConfiguration = null; 716 } else { 717 mCurrentConfigurationKey = currentConfiguration.configKey(); 718 } 719 } 720 721 /* select Best Network candidate from known WifiConfigurations */ 722 for (WifiConfiguration config : list) { 723 if ((config.status == WifiConfiguration.Status.DISABLED) 724 && (config.disableReason == WifiConfiguration.DISABLED_AUTH_FAILURE)) { 725 if (DBG) { 726 logDbg("attemptAutoJoin skip candidate due to auth failure key " 727 + config.configKey(true)); 728 } 729 continue; 730 } 731 732 if (config.SSID == null) { 733 return; 734 } 735 736 if (config.autoJoinStatus >= WifiConfiguration.AUTO_JOIN_TEMPORARY_DISABLED) { 737 //avoid temporarily disabled networks altogether 738 //TODO: implement a better logic which will reenable the network after some time 739 if (DBG) { 740 logDbg("attemptAutoJoin skip candidate due to auto join status " 741 + Integer.toString(config.autoJoinStatus) + " key " 742 + config.configKey(true)); 743 } 744 continue; 745 } 746 747 if (config.networkId == currentNetId) { 748 if (DBG) { 749 logDbg("attemptAutoJoin skip current candidate " + Integer.toString(currentNetId) 750 + " key " + config.configKey(true)); 751 } 752 continue; 753 } 754 755 if (lastSelectedConfiguration == null || !config.configKey().equals(lastSelectedConfiguration)) { 756 //don't try to autojoin a network that is too far 757 if (config.visibility == null) { 758 continue; 759 } 760 if (config.visibility.rssi5 < -70 && config.visibility.rssi24 < -80) { 761 continue; 762 } 763 } 764 765 if (DBG) { 766 logDbg("attemptAutoJoin trying candidate id=" + config.networkId + " " 767 + config.SSID + " key " + config.configKey(true)); 768 } 769 770 if (candidate == null) { 771 candidate = config; 772 } else { 773 if (VDBG) { 774 logDbg("attemptAutoJoin will compare candidate " + candidate.configKey() 775 + " with " + config.configKey() + " key " + config.configKey(true)); 776 } 777 778 int order = compareWifiConfigurations(candidate, config); 779 if (order > 0) { 780 //ascending : candidate < config 781 candidate = config; 782 } 783 } 784 } 785 786 /* now, go thru scan result to try finding a better Herrevad network */ 787 if (mNetworkScoreCache != null) { 788 int rssi5 = WifiConfiguration.INVALID_RSSI; 789 int rssi24 = WifiConfiguration.INVALID_RSSI; 790 WifiConfiguration.Visibility visibility; 791 if (candidate != null) { 792 rssi5 = candidate.visibility.rssi5; 793 rssi24 = candidate.visibility.rssi24; 794 } 795 796 //get current date 797 Date now = new Date(); 798 long now_ms = now.getTime(); 799 800 if (rssi5 < -60 && rssi24 < -70) { 801 for (ScanResult result : scanResultCache.values()) { 802 if ((now_ms - result.seen) < 3000) { 803 int score = mNetworkScoreCache.getNetworkScore(result); 804 if (score > 0) { 805 // try any arbitrary formula for now, adding apple and oranges, 806 // i.e. adding network score and "dBm over noise" 807 if (result.frequency < 4000) { 808 if ((result.level + score) > (rssi24 -40)) { 809 // force it as open, TBD should we otherwise verify that this 810 // BSSID only supports open?? 811 result.capabilities = ""; 812 813 //switch to this scan result 814 candidate = 815 mWifiConfigStore.wifiConfigurationFromScanResult(result); 816 candidate.ephemeral = true; 817 } 818 } else { 819 if ((result.level + score) > (rssi5 -30)) { 820 // force it as open, TBD should we otherwise verify that this 821 // BSSID only supports open?? 822 result.capabilities = ""; 823 824 //switch to this scan result 825 candidate = 826 mWifiConfigStore.wifiConfigurationFromScanResult(result); 827 candidate.ephemeral = true; 828 } 829 } 830 } 831 } 832 } 833 } 834 } 835 if (candidate != null) { 836 /* if candidate is found, check the state of the connection so as 837 to decide if we should be acting on this candidate and switching over */ 838 int networkDelta = compareNetwork(candidate); 839 if (DBG && (networkDelta > 0)) { 840 logDbg("attemptAutoJoin did find candidate " + candidate.configKey() 841 + " for delta " + Integer.toString(networkDelta)); 842 } 843 /* ASK traffic poller permission to switch: 844 for instance, 845 if user is currently streaming voice traffic, 846 then don’t switch regardless of the delta */ 847 848 if (mWifiTrafficPoller.shouldSwitchNetwork(networkDelta)) { 849 if (mStaStaSupported) { 850 logDbg("mStaStaSupported --> error do nothing now "); 851 } else { 852 if (DBG) { 853 logDbg("AutoJoin auto connect with netId " 854 + Integer.toString(candidate.networkId) 855 + " to " + candidate.configKey()); 856 } 857 858 mWifiStateMachine.sendMessage(WifiStateMachine.CMD_AUTO_CONNECT, 859 candidate.networkId); 860 //mWifiConfigStore.enableNetworkWithoutBroadcast(candidate.networkId, true); 861 862 //we would do the below only if we want to persist the new choice 863 //mWifiConfigStore.selectNetwork(candidate.networkId); 864 865 } 866 } 867 } 868 if (VDBG) logDbg("Done attemptAutoJoin"); 869 } 870} 871 872