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