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