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