Home | History | Annotate | Download | only in wifi
      1 /*
      2  * Copyright (C) 2015 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.annotation.Nullable;
     20 import android.content.Context;
     21 import android.net.NetworkKey;
     22 import android.net.NetworkScoreManager;
     23 import android.net.WifiKey;
     24 import android.net.wifi.ScanResult;
     25 import android.net.wifi.WifiConfiguration;
     26 import android.net.wifi.WifiInfo;
     27 import android.net.wifi.WifiManager;
     28 import android.text.TextUtils;
     29 import android.util.LocalLog;
     30 import android.util.Log;
     31 import android.util.Pair;
     32 
     33 import com.android.internal.R;
     34 import com.android.internal.annotations.VisibleForTesting;
     35 
     36 import java.io.FileDescriptor;
     37 import java.io.PrintWriter;
     38 import java.lang.annotation.Retention;
     39 import java.lang.annotation.RetentionPolicy;
     40 import java.util.ArrayList;
     41 import java.util.HashMap;
     42 import java.util.Iterator;
     43 import java.util.List;
     44 import java.util.Map;
     45 
     46 /**
     47  * This class looks at all the connectivity scan results then
     48  * select an network for the phone to connect/roam to.
     49  */
     50 public class WifiQualifiedNetworkSelector {
     51     private WifiConfigManager mWifiConfigManager;
     52     private WifiInfo mWifiInfo;
     53     private NetworkScoreManager mScoreManager;
     54     private WifiNetworkScoreCache mNetworkScoreCache;
     55     private Clock mClock;
     56     private static final String TAG = "WifiQualifiedNetworkSelector:";
     57     // Always enable debugging logs for now since QNS is still a new feature.
     58     private static final boolean FORCE_DEBUG = true;
     59     private boolean mDbg = FORCE_DEBUG;
     60     private WifiConfiguration mCurrentConnectedNetwork = null;
     61     private String mCurrentBssid = null;
     62     //buffer most recent scan results
     63     private List<ScanDetail> mScanDetails = null;
     64     //buffer of filtered scan results (Scan results considered by network selection) & associated
     65     //WifiConfiguration (if any)
     66     private volatile List<Pair<ScanDetail, WifiConfiguration>> mFilteredScanDetails = null;
     67 
     68     //Minimum time gap between last successful Qualified Network Selection and new selection attempt
     69     //usable only when current state is connected state   default 10 s
     70     private static final int MINIMUM_QUALIFIED_NETWORK_SELECTION_INTERVAL = 10 * 1000;
     71 
     72     //if current network is on 2.4GHz band and has a RSSI over this, need not new network selection
     73     public static final int QUALIFIED_RSSI_24G_BAND = -73;
     74     //if current network is on 5GHz band and has a RSSI over this, need not new network selection
     75     public static final int QUALIFIED_RSSI_5G_BAND = -70;
     76     //any RSSI larger than this will benefit the traffic very limited
     77     public static final int RSSI_SATURATION_2G_BAND = -60;
     78     public static final int RSSI_SATURATION_5G_BAND = -57;
     79     //Any value below this will be considered not usable
     80     public static final int MINIMUM_2G_ACCEPT_RSSI = -85;
     81     public static final int MINIMUM_5G_ACCEPT_RSSI = -82;
     82 
     83     public static final int RSSI_SCORE_SLOPE = 4;
     84     public static final int RSSI_SCORE_OFFSET = 85;
     85 
     86     public static final int BAND_AWARD_5GHz = 40;
     87     public static final int SAME_NETWORK_AWARD = 16;
     88 
     89     public static final int SAME_BSSID_AWARD = 24;
     90     public static final int LAST_SELECTION_AWARD = 480;
     91     public static final int PASSPOINT_SECURITY_AWARD = 40;
     92     public static final int SECURITY_AWARD = 80;
     93     public static final int BSSID_BLACKLIST_THRESHOLD = 3;
     94     public static final int BSSID_BLACKLIST_EXPIRE_TIME = 5 * 60 * 1000;
     95     private final int mNoIntnetPenalty;
     96     //TODO: check whether we still need this one when we update the scan manager
     97     public static final int SCAN_RESULT_MAXIMUNM_AGE = 40000;
     98     private static final int INVALID_TIME_STAMP = -1;
     99     private long mLastQualifiedNetworkSelectionTimeStamp = INVALID_TIME_STAMP;
    100 
    101     private final LocalLog mLocalLog = new LocalLog(512);
    102     private int mRssiScoreSlope = RSSI_SCORE_SLOPE;
    103     private int mRssiScoreOffset = RSSI_SCORE_OFFSET;
    104     private int mSameBssidAward = SAME_BSSID_AWARD;
    105     private int mLastSelectionAward = LAST_SELECTION_AWARD;
    106     private int mPasspointSecurityAward = PASSPOINT_SECURITY_AWARD;
    107     private int mSecurityAward = SECURITY_AWARD;
    108     private int mUserPreferedBand = WifiManager.WIFI_FREQUENCY_BAND_AUTO;
    109     private Map<String, BssidBlacklistStatus> mBssidBlacklist =
    110             new HashMap<String, BssidBlacklistStatus>();
    111 
    112     /**
    113      * class save the blacklist status of a given BSSID
    114      */
    115     private static class BssidBlacklistStatus {
    116         //how many times it is requested to be blacklisted (association rejection trigger this)
    117         int mCounter;
    118         boolean mIsBlacklisted;
    119         long mBlacklistedTimeStamp = INVALID_TIME_STAMP;
    120     }
    121 
    122     private void localLog(String log) {
    123         if (mDbg) {
    124             mLocalLog.log(log);
    125         }
    126     }
    127 
    128     private void localLoge(String log) {
    129         mLocalLog.log(log);
    130     }
    131 
    132     @VisibleForTesting
    133     void setWifiNetworkScoreCache(WifiNetworkScoreCache cache) {
    134         mNetworkScoreCache = cache;
    135     }
    136 
    137     /**
    138      * @return current target connected network
    139      */
    140     public WifiConfiguration getConnetionTargetNetwork() {
    141         return mCurrentConnectedNetwork;
    142     }
    143 
    144     /**
    145      * @return the list of ScanDetails scored as potential candidates by the last run of
    146      * selectQualifiedNetwork, this will be empty if QNS determined no selection was needed on last
    147      * run. This includes scan details of sufficient signal strength, and had an associated
    148      * WifiConfiguration.
    149      */
    150     public List<Pair<ScanDetail, WifiConfiguration>> getFilteredScanDetails() {
    151         return mFilteredScanDetails;
    152     }
    153 
    154     /**
    155      * set the user selected preferred band
    156      *
    157      * @param band preferred band user selected
    158      */
    159     public void setUserPreferredBand(int band) {
    160         mUserPreferedBand = band;
    161     }
    162 
    163     WifiQualifiedNetworkSelector(WifiConfigManager configureStore, Context context,
    164             WifiInfo wifiInfo, Clock clock) {
    165         mWifiConfigManager = configureStore;
    166         mWifiInfo = wifiInfo;
    167         mClock = clock;
    168         mScoreManager =
    169                 (NetworkScoreManager) context.getSystemService(Context.NETWORK_SCORE_SERVICE);
    170         if (mScoreManager != null) {
    171             mNetworkScoreCache = new WifiNetworkScoreCache(context);
    172             mScoreManager.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
    173         } else {
    174             localLoge("No network score service: Couldn't register as a WiFi score Manager, type="
    175                     + NetworkKey.TYPE_WIFI + " service= " + Context.NETWORK_SCORE_SERVICE);
    176             mNetworkScoreCache = null;
    177         }
    178 
    179         mRssiScoreSlope = context.getResources().getInteger(
    180                 R.integer.config_wifi_framework_RSSI_SCORE_SLOPE);
    181         mRssiScoreOffset = context.getResources().getInteger(
    182                 R.integer.config_wifi_framework_RSSI_SCORE_OFFSET);
    183         mSameBssidAward = context.getResources().getInteger(
    184                 R.integer.config_wifi_framework_SAME_BSSID_AWARD);
    185         mLastSelectionAward = context.getResources().getInteger(
    186                 R.integer.config_wifi_framework_LAST_SELECTION_AWARD);
    187         mPasspointSecurityAward = context.getResources().getInteger(
    188                 R.integer.config_wifi_framework_PASSPOINT_SECURITY_AWARD);
    189         mSecurityAward = context.getResources().getInteger(
    190                 R.integer.config_wifi_framework_SECURITY_AWARD);
    191         mNoIntnetPenalty = (mWifiConfigManager.mThresholdSaturatedRssi24.get() + mRssiScoreOffset)
    192                 * mRssiScoreSlope + mWifiConfigManager.mBandAward5Ghz.get()
    193                 + mWifiConfigManager.mCurrentNetworkBoost.get() + mSameBssidAward + mSecurityAward;
    194     }
    195 
    196     void enableVerboseLogging(int verbose) {
    197         mDbg = verbose > 0 || FORCE_DEBUG;
    198     }
    199 
    200     private String getNetworkString(WifiConfiguration network) {
    201         if (network == null) {
    202             return null;
    203         }
    204 
    205         return (network.SSID + ":" + network.networkId);
    206 
    207     }
    208 
    209     /**
    210      * check whether current network is good enough we need not consider any potential switch
    211      *
    212      * @param currentNetwork -- current connected network
    213      * @return true -- qualified and do not consider potential network switch
    214      *         false -- not good enough and should try potential network switch
    215      */
    216     private boolean isNetworkQualified(WifiConfiguration currentNetwork) {
    217 
    218         if (currentNetwork == null) {
    219             localLog("Disconnected");
    220             return false;
    221         } else {
    222             localLog("Current network is: " + currentNetwork.SSID + " ,ID is: "
    223                     + currentNetwork.networkId);
    224         }
    225 
    226         //if current connected network is an ephemeral network,we will consider
    227         // there is no current network
    228         if (currentNetwork.ephemeral) {
    229             localLog("Current is ephemeral. Start reselect");
    230             return false;
    231         }
    232 
    233         //if current network is open network, not qualified
    234         if (mWifiConfigManager.isOpenNetwork(currentNetwork)) {
    235             localLog("Current network is open network");
    236             return false;
    237         }
    238 
    239         // Current network band must match with user preference selection
    240         if (mWifiInfo.is24GHz() && (mUserPreferedBand != WifiManager.WIFI_FREQUENCY_BAND_2GHZ)) {
    241             localLog("Current band dose not match user preference. Start Qualified Network"
    242                     + " Selection Current band = " + (mWifiInfo.is24GHz() ? "2.4GHz band"
    243                     : "5GHz band") + "UserPreference band = " + mUserPreferedBand);
    244             return false;
    245         }
    246 
    247         int currentRssi = mWifiInfo.getRssi();
    248         if ((mWifiInfo.is24GHz()
    249                         && currentRssi < mWifiConfigManager.mThresholdQualifiedRssi24.get())
    250                 || (mWifiInfo.is5GHz()
    251                         && currentRssi < mWifiConfigManager.mThresholdQualifiedRssi5.get())) {
    252             localLog("Current band = " + (mWifiInfo.is24GHz() ? "2.4GHz band" : "5GHz band")
    253                     + "current RSSI is: " + currentRssi);
    254             return false;
    255         }
    256 
    257         return true;
    258     }
    259 
    260     /**
    261      * check whether QualifiedNetworkSelection is needed or not
    262      *
    263      * @param isLinkDebouncing true -- Link layer is under debouncing
    264      *                         false -- Link layer is not under debouncing
    265      * @param isConnected true -- device is connected to an AP currently
    266      *                    false -- device is not connected to an AP currently
    267      * @param isDisconnected true -- WifiStateMachine is at disconnected state
    268      *                       false -- WifiStateMachine is not at disconnected state
    269      * @param isSupplicantTransientState true -- supplicant is in a transient state now
    270      *                                   false -- supplicant is not in a transient state now
    271      * @return true -- need a Qualified Network Selection procedure
    272      *         false -- do not need a QualifiedNetworkSelection procedure
    273      */
    274     private boolean needQualifiedNetworkSelection(boolean isLinkDebouncing, boolean isConnected,
    275             boolean isDisconnected, boolean isSupplicantTransientState) {
    276         if (mScanDetails.size() == 0) {
    277             localLog("empty scan result");
    278             return false;
    279         }
    280 
    281         // Do not trigger Qualified Network Selection during L2 link debouncing procedure
    282         if (isLinkDebouncing) {
    283             localLog("Need not Qualified Network Selection during L2 debouncing");
    284             return false;
    285         }
    286 
    287         if (isConnected) {
    288             //already connected. Just try to find better candidate
    289             //if switch network is not allowed in connected mode, do not trigger Qualified Network
    290             //Selection
    291             if (!mWifiConfigManager.getEnableAutoJoinWhenAssociated()) {
    292                 localLog("Switch network under connection is not allowed");
    293                 return false;
    294             }
    295 
    296             //Do not select again if last selection is within
    297             //MINIMUM_QUALIFIED_NETWORK_SELECTION_INTERVAL
    298             if (mLastQualifiedNetworkSelectionTimeStamp != INVALID_TIME_STAMP) {
    299                 long gap = mClock.elapsedRealtime() - mLastQualifiedNetworkSelectionTimeStamp;
    300                 if (gap < MINIMUM_QUALIFIED_NETWORK_SELECTION_INTERVAL) {
    301                     localLog("Too short to last successful Qualified Network Selection Gap is:"
    302                             + gap + " ms!");
    303                     return false;
    304                 }
    305             }
    306 
    307             WifiConfiguration currentNetwork =
    308                     mWifiConfigManager.getWifiConfiguration(mWifiInfo.getNetworkId());
    309             if (currentNetwork == null) {
    310                 // WifiStateMachine in connected state but WifiInfo is not. It means there is a race
    311                 // condition happened. Do not make QNS until WifiStateMachine goes into
    312                 // disconnected state
    313                 return false;
    314             }
    315 
    316             if (!isNetworkQualified(mCurrentConnectedNetwork)) {
    317                 //need not trigger Qualified Network Selection if current network is qualified
    318                 localLog("Current network is not qualified");
    319                 return true;
    320             } else {
    321                 return false;
    322             }
    323         } else if (isDisconnected) {
    324             mCurrentConnectedNetwork = null;
    325             mCurrentBssid = null;
    326             //Do not start Qualified Network Selection if current state is a transient state
    327             if (isSupplicantTransientState) {
    328                 return false;
    329             }
    330         } else {
    331             //Do not allow new network selection in other state
    332             localLog("WifiStateMachine is not on connected or disconnected state");
    333             return false;
    334         }
    335 
    336         return true;
    337     }
    338 
    339     int calculateBssidScore(ScanResult scanResult, WifiConfiguration network,
    340             WifiConfiguration currentNetwork, boolean sameBssid, boolean sameSelect,
    341             StringBuffer sbuf) {
    342 
    343         int score = 0;
    344         //calculate the RSSI score
    345         int rssi = scanResult.level <= mWifiConfigManager.mThresholdSaturatedRssi24.get()
    346                 ? scanResult.level : mWifiConfigManager.mThresholdSaturatedRssi24.get();
    347         score += (rssi + mRssiScoreOffset) * mRssiScoreSlope;
    348         sbuf.append(" RSSI score: " +  score);
    349         if (scanResult.is5GHz()) {
    350             //5GHz band
    351             score += mWifiConfigManager.mBandAward5Ghz.get();
    352             sbuf.append(" 5GHz bonus: " + mWifiConfigManager.mBandAward5Ghz.get());
    353         }
    354 
    355         //last user selection award
    356         if (sameSelect) {
    357             long timeDifference = mClock.elapsedRealtime()
    358                     - mWifiConfigManager.getLastSelectedTimeStamp();
    359 
    360             if (timeDifference > 0) {
    361                 int bonus = mLastSelectionAward - (int) (timeDifference / 1000 / 60);
    362                 score += bonus > 0 ? bonus : 0;
    363                 sbuf.append(" User selected it last time " + (timeDifference / 1000 / 60)
    364                         + " minutes ago, bonus:" + bonus);
    365             }
    366         }
    367 
    368         //same network award
    369         if (network == currentNetwork || network.isLinked(currentNetwork)) {
    370             score += mWifiConfigManager.mCurrentNetworkBoost.get();
    371             sbuf.append(" Same network with current associated. Bonus: "
    372                     + mWifiConfigManager.mCurrentNetworkBoost.get());
    373         }
    374 
    375         //same BSSID award
    376         if (sameBssid) {
    377             score += mSameBssidAward;
    378             sbuf.append(" Same BSSID with current association. Bonus: " + mSameBssidAward);
    379         }
    380 
    381         //security award
    382         if (network.isPasspoint()) {
    383             score += mPasspointSecurityAward;
    384             sbuf.append(" Passpoint Bonus:" + mPasspointSecurityAward);
    385         } else if (!mWifiConfigManager.isOpenNetwork(network)) {
    386             score += mSecurityAward;
    387             sbuf.append(" Secure network Bonus:" + mSecurityAward);
    388         }
    389 
    390         //Penalty for no internet network. Make sure if there is any network with Internet,
    391         //however, if there is no any other network with internet, this network can be chosen
    392         if (network.numNoInternetAccessReports > 0 && !network.validatedInternetAccess) {
    393             score -= mNoIntnetPenalty;
    394             sbuf.append(" No internet Penalty:-" + mNoIntnetPenalty);
    395         }
    396 
    397 
    398         sbuf.append(" Score for scanResult: " + scanResult +  " and Network ID: "
    399                 + network.networkId + " final score:" + score + "\n\n");
    400 
    401         return score;
    402     }
    403 
    404     /**
    405      * This API try to update all the saved networks' network selection status
    406      */
    407     private void updateSavedNetworkSelectionStatus() {
    408         List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks();
    409         if (savedNetworks.size() == 0) {
    410             localLog("no saved network");
    411             return;
    412         }
    413 
    414         StringBuffer sbuf = new StringBuffer("Saved Network List\n");
    415         for (WifiConfiguration network : savedNetworks) {
    416             WifiConfiguration config = mWifiConfigManager.getWifiConfiguration(network.networkId);
    417             WifiConfiguration.NetworkSelectionStatus status =
    418                     config.getNetworkSelectionStatus();
    419 
    420             //If the configuration is temporarily disabled, try to re-enable it
    421             if (status.isNetworkTemporaryDisabled()) {
    422                 mWifiConfigManager.tryEnableQualifiedNetwork(network.networkId);
    423             }
    424 
    425             //clean the cached candidate, score and seen
    426             status.setCandidate(null);
    427             status.setCandidateScore(Integer.MIN_VALUE);
    428             status.setSeenInLastQualifiedNetworkSelection(false);
    429 
    430             //print the debug messages
    431             sbuf.append("    " + getNetworkString(network) + " " + " User Preferred BSSID:"
    432                     + network.BSSID + " FQDN:" + network.FQDN + " "
    433                     + status.getNetworkStatusString() + " Disable account: ");
    434             for (int index = status.NETWORK_SELECTION_ENABLE;
    435                     index < status.NETWORK_SELECTION_DISABLED_MAX; index++) {
    436                 sbuf.append(status.getDisableReasonCounter(index) + " ");
    437             }
    438             sbuf.append("Connect Choice:" + status.getConnectChoice() + " set time:"
    439                     + status.getConnectChoiceTimestamp());
    440             sbuf.append("\n");
    441         }
    442         localLog(sbuf.toString());
    443     }
    444 
    445     /**
    446      * This API is called when user explicitly select a network. Currently, it is used in following
    447      * cases:
    448      * (1) User explicitly choose to connect to a saved network
    449      * (2) User save a network after add a new network
    450      * (3) User save a network after modify a saved network
    451      * Following actions will be triggered:
    452      * 1. if this network is disabled, we need re-enable it again
    453      * 2. we considered user prefer this network over all the networks visible in latest network
    454      *    selection procedure
    455      *
    456      * @param netId new network ID for either the network the user choose or add
    457      * @param persist whether user has the authority to overwrite current connect choice
    458      * @return true -- There is change made to connection choice of any saved network
    459      *         false -- There is no change made to connection choice of any saved network
    460      */
    461     public boolean userSelectNetwork(int netId, boolean persist) {
    462         WifiConfiguration selected = mWifiConfigManager.getWifiConfiguration(netId);
    463         localLog("userSelectNetwork:" + netId + " persist:" + persist);
    464         if (selected == null || selected.SSID == null) {
    465             localLoge("userSelectNetwork: Bad configuration with nid=" + netId);
    466             return false;
    467         }
    468 
    469 
    470         if (!selected.getNetworkSelectionStatus().isNetworkEnabled()) {
    471             mWifiConfigManager.updateNetworkSelectionStatus(netId,
    472                     WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
    473         }
    474 
    475         if (!persist) {
    476             localLog("User has no privilege to overwrite the current priority");
    477             return false;
    478         }
    479 
    480         boolean change = false;
    481         String key = selected.configKey();
    482         // This is only used for setting the connect choice timestamp for debugging purposes.
    483         long currentTime = mClock.currentTimeMillis();
    484         List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks();
    485 
    486         for (WifiConfiguration network : savedNetworks) {
    487             WifiConfiguration config = mWifiConfigManager.getWifiConfiguration(network.networkId);
    488             WifiConfiguration.NetworkSelectionStatus status = config.getNetworkSelectionStatus();
    489             if (config.networkId == selected.networkId) {
    490                 if (status.getConnectChoice() != null) {
    491                     localLog("Remove user selection preference of " + status.getConnectChoice()
    492                             + " Set Time: " + status.getConnectChoiceTimestamp() + " from "
    493                             + config.SSID + " : " + config.networkId);
    494                     status.setConnectChoice(null);
    495                     status.setConnectChoiceTimestamp(WifiConfiguration.NetworkSelectionStatus
    496                             .INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
    497                     change = true;
    498                 }
    499                 continue;
    500             }
    501 
    502             if (status.getSeenInLastQualifiedNetworkSelection()
    503                     && (status.getConnectChoice() == null
    504                     || !status.getConnectChoice().equals(key))) {
    505                 localLog("Add key:" + key + " Set Time: " + currentTime + " to "
    506                         + getNetworkString(config));
    507                 status.setConnectChoice(key);
    508                 status.setConnectChoiceTimestamp(currentTime);
    509                 change = true;
    510             }
    511         }
    512         //Write this change to file
    513         if (change) {
    514             mWifiConfigManager.writeKnownNetworkHistory();
    515             return true;
    516         }
    517 
    518         return false;
    519     }
    520 
    521     /**
    522      * enable/disable a BSSID for Quality Network Selection
    523      * When an association rejection event is obtained, Quality Network Selector will disable this
    524      * BSSID but supplicant still can try to connect to this bssid. If supplicant connect to it
    525      * successfully later, this bssid can be re-enabled.
    526      *
    527      * @param bssid the bssid to be enabled / disabled
    528      * @param enable -- true enable a bssid if it has been disabled
    529      *               -- false disable a bssid
    530      */
    531     public boolean enableBssidForQualityNetworkSelection(String bssid, boolean enable) {
    532         if (enable) {
    533             return (mBssidBlacklist.remove(bssid) != null);
    534         } else {
    535             if (bssid != null) {
    536                 BssidBlacklistStatus status = mBssidBlacklist.get(bssid);
    537                 if (status == null) {
    538                     //first time
    539                     BssidBlacklistStatus newStatus = new BssidBlacklistStatus();
    540                     newStatus.mCounter++;
    541                     mBssidBlacklist.put(bssid, newStatus);
    542                 } else if (!status.mIsBlacklisted) {
    543                     status.mCounter++;
    544                     if (status.mCounter >= BSSID_BLACKLIST_THRESHOLD) {
    545                         status.mIsBlacklisted = true;
    546                         status.mBlacklistedTimeStamp = mClock.elapsedRealtime();
    547                         return true;
    548                     }
    549                 }
    550             }
    551         }
    552         return false;
    553     }
    554 
    555     /**
    556      * update the buffered BSSID blacklist
    557      *
    558      * Go through the whole buffered BSSIDs blacklist and check when the BSSIDs is blocked. If they
    559      * were blacked before BSSID_BLACKLIST_EXPIRE_TIME, re-enable it again.
    560      */
    561     private void updateBssidBlacklist() {
    562         Iterator<BssidBlacklistStatus> iter = mBssidBlacklist.values().iterator();
    563         while (iter.hasNext()) {
    564             BssidBlacklistStatus status = iter.next();
    565             if (status != null && status.mIsBlacklisted) {
    566                 if (mClock.elapsedRealtime() - status.mBlacklistedTimeStamp
    567                             >= BSSID_BLACKLIST_EXPIRE_TIME) {
    568                     iter.remove();
    569                 }
    570             }
    571         }
    572     }
    573 
    574     /**
    575      * Check whether a bssid is disabled
    576      * @param bssid -- the bssid to check
    577      * @return true -- bssid is disabled
    578      *         false -- bssid is not disabled
    579      */
    580     public boolean isBssidDisabled(String bssid) {
    581         BssidBlacklistStatus status = mBssidBlacklist.get(bssid);
    582         return status == null ? false : status.mIsBlacklisted;
    583     }
    584 
    585     /**
    586      * ToDo: This should be called in Connectivity Manager when it gets new scan result
    587      * check whether a network slection is needed. If need, check all the new scan results and
    588      * select a new qualified network/BSSID to connect to
    589      *
    590      * @param forceSelectNetwork true -- start a qualified network selection anyway,no matter
    591      *                           current network is already qualified or not.
    592      *                           false -- if current network is already qualified, do not do new
    593      *                           selection
    594      * @param isUntrustedConnectionsAllowed true -- user allow to connect to untrusted network
    595      *                                      false -- user do not allow to connect to untrusted
    596      *                                      network
    597      * @param scanDetails latest scan result obtained (should be connectivity scan only)
    598      * @param isLinkDebouncing true -- Link layer is under debouncing
    599      *                         false -- Link layer is not under debouncing
    600      * @param isConnected true -- device is connected to an AP currently
    601      *                    false -- device is not connected to an AP currently
    602      * @param isDisconnected true -- WifiStateMachine is at disconnected state
    603      *                       false -- WifiStateMachine is not at disconnected state
    604      * @param isSupplicantTransient true -- supplicant is in a transient state
    605      *                              false -- supplicant is not in a transient state
    606      * @return the qualified network candidate found. If no available candidate, return null
    607      */
    608     public WifiConfiguration selectQualifiedNetwork(boolean forceSelectNetwork ,
    609             boolean isUntrustedConnectionsAllowed, List<ScanDetail>  scanDetails,
    610             boolean isLinkDebouncing, boolean isConnected, boolean isDisconnected,
    611             boolean isSupplicantTransient) {
    612         localLog("==========start qualified Network Selection==========");
    613         mScanDetails = scanDetails;
    614         List<Pair<ScanDetail, WifiConfiguration>>  filteredScanDetails = new ArrayList<>();
    615         if (mCurrentConnectedNetwork == null) {
    616             mCurrentConnectedNetwork =
    617                     mWifiConfigManager.getWifiConfiguration(mWifiInfo.getNetworkId());
    618         }
    619 
    620         // Always get the current BSSID from WifiInfo in case that firmware initiated roaming
    621         // happened.
    622         mCurrentBssid = mWifiInfo.getBSSID();
    623 
    624         if (!forceSelectNetwork && !needQualifiedNetworkSelection(isLinkDebouncing, isConnected,
    625                 isDisconnected, isSupplicantTransient)) {
    626             localLog("Quit qualified Network Selection since it is not forced and current network"
    627                     + " is qualified already");
    628             mFilteredScanDetails = filteredScanDetails;
    629             return null;
    630         }
    631 
    632         int currentHighestScore = Integer.MIN_VALUE;
    633         ScanResult scanResultCandidate = null;
    634         WifiConfiguration networkCandidate = null;
    635         final ExternalScoreEvaluator externalScoreEvaluator =
    636                 new ExternalScoreEvaluator(mLocalLog, mDbg);
    637         String lastUserSelectedNetWorkKey = mWifiConfigManager.getLastSelectedConfiguration();
    638         WifiConfiguration lastUserSelectedNetwork =
    639                 mWifiConfigManager.getWifiConfiguration(lastUserSelectedNetWorkKey);
    640         if (lastUserSelectedNetwork != null) {
    641             localLog("Last selection is " + lastUserSelectedNetwork.SSID + " Time to now: "
    642                     + ((mClock.elapsedRealtime() - mWifiConfigManager.getLastSelectedTimeStamp())
    643                             / 1000 / 60 + " minutes"));
    644         }
    645 
    646         updateSavedNetworkSelectionStatus();
    647         updateBssidBlacklist();
    648 
    649         StringBuffer lowSignalScan = new StringBuffer();
    650         StringBuffer notSavedScan = new StringBuffer();
    651         StringBuffer noValidSsid = new StringBuffer();
    652         StringBuffer scoreHistory =  new StringBuffer();
    653         ArrayList<NetworkKey> unscoredNetworks = new ArrayList<NetworkKey>();
    654 
    655         //iterate all scan results and find the best candidate with the highest score
    656         for (ScanDetail scanDetail : mScanDetails) {
    657             ScanResult scanResult = scanDetail.getScanResult();
    658             //skip bad scan result
    659             if (scanResult.SSID == null || TextUtils.isEmpty(scanResult.SSID)) {
    660                 if (mDbg) {
    661                     //We should not see this in ePNO
    662                     noValidSsid.append(scanResult.BSSID + " / ");
    663                 }
    664                 continue;
    665             }
    666 
    667             final String scanId = toScanId(scanResult);
    668             //check whether this BSSID is blocked or not
    669             if (mWifiConfigManager.isBssidBlacklisted(scanResult.BSSID)
    670                     || isBssidDisabled(scanResult.BSSID)) {
    671                 //We should not see this in ePNO
    672                 Log.e(TAG, scanId + " is in blacklist.");
    673                 continue;
    674             }
    675 
    676             //skip scan result with too weak signals
    677             if ((scanResult.is24GHz() && scanResult.level
    678                     < mWifiConfigManager.mThresholdMinimumRssi24.get())
    679                     || (scanResult.is5GHz() && scanResult.level
    680                     < mWifiConfigManager.mThresholdMinimumRssi5.get())) {
    681                 if (mDbg) {
    682                     lowSignalScan.append(scanId + "(" + (scanResult.is24GHz() ? "2.4GHz" : "5GHz")
    683                             + ")" + scanResult.level + " / ");
    684                 }
    685                 continue;
    686             }
    687 
    688             //check if there is already a score for this network
    689             if (mNetworkScoreCache != null && !mNetworkScoreCache.isScoredNetwork(scanResult)) {
    690                 //no score for this network yet.
    691                 WifiKey wifiKey;
    692 
    693                 try {
    694                     wifiKey = new WifiKey("\"" + scanResult.SSID + "\"", scanResult.BSSID);
    695                     NetworkKey ntwkKey = new NetworkKey(wifiKey);
    696                     //add to the unscoredNetworks list so we can request score later
    697                     unscoredNetworks.add(ntwkKey);
    698                 } catch (IllegalArgumentException e) {
    699                     Log.w(TAG, "Invalid SSID=" + scanResult.SSID + " BSSID=" + scanResult.BSSID
    700                             + " for network score. Skip.");
    701                 }
    702             }
    703 
    704             //check whether this scan result belong to a saved network
    705             boolean potentiallyEphemeral = false;
    706             // Stores WifiConfiguration of potential connection candidates for scan result filtering
    707             WifiConfiguration potentialEphemeralCandidate = null;
    708             List<WifiConfiguration> associatedWifiConfigurations =
    709                     mWifiConfigManager.updateSavedNetworkWithNewScanDetail(scanDetail,
    710                             isSupplicantTransient || isConnected || isLinkDebouncing);
    711             if (associatedWifiConfigurations == null) {
    712                 potentiallyEphemeral =  true;
    713                 if (mDbg) {
    714                     notSavedScan.append(scanId + " / ");
    715                 }
    716             } else if (associatedWifiConfigurations.size() == 1) {
    717                 //if there are more than 1 associated network, it must be a passpoint network
    718                 WifiConfiguration network = associatedWifiConfigurations.get(0);
    719                 if (network.ephemeral) {
    720                     potentialEphemeralCandidate = network;
    721                     potentiallyEphemeral =  true;
    722                 }
    723             }
    724 
    725             // Evaluate the potentially ephemeral network as a possible candidate if untrusted
    726             // connections are allowed and we have an external score for the scan result.
    727             if (potentiallyEphemeral) {
    728                 if (isUntrustedConnectionsAllowed) {
    729                     Integer netScore = getNetworkScore(scanResult, false);
    730                     if (netScore != null
    731                         && !mWifiConfigManager.wasEphemeralNetworkDeleted(scanResult.SSID)) {
    732                         externalScoreEvaluator.evalUntrustedCandidate(netScore, scanResult);
    733                         // scanDetail is for available ephemeral network
    734                         filteredScanDetails.add(Pair.create(scanDetail,
    735                                 potentialEphemeralCandidate));
    736                     }
    737                 }
    738                 continue;
    739             }
    740 
    741             // calculate the score of each scanresult whose associated network is not ephemeral. Due
    742             // to one scan result can associated with more than 1 network, we need calculate all
    743             // the scores and use the highest one as the scanresults score.
    744             int highestScore = Integer.MIN_VALUE;
    745             int score;
    746             WifiConfiguration configurationCandidateForThisScan = null;
    747             WifiConfiguration potentialCandidate = null;
    748             for (WifiConfiguration network : associatedWifiConfigurations) {
    749                 WifiConfiguration.NetworkSelectionStatus status =
    750                         network.getNetworkSelectionStatus();
    751                 status.setSeenInLastQualifiedNetworkSelection(true);
    752                 if (potentialCandidate == null) {
    753                     potentialCandidate = network;
    754                 }
    755                 if (!status.isNetworkEnabled()) {
    756                     continue;
    757                 } else if (network.BSSID != null && !network.BSSID.equals("any")
    758                         && !network.BSSID.equals(scanResult.BSSID)) {
    759                     //in such scenario, user (APP) has specified the only BSSID to connect for this
    760                     // configuration. So only the matched scan result can be candidate
    761                     localLog("Network: " + getNetworkString(network) + " has specified" + "BSSID:"
    762                             + network.BSSID + ". Skip " + scanResult.BSSID);
    763                     continue;
    764                 }
    765 
    766                 // If the network is marked to use external scores then attempt to fetch the score.
    767                 // These networks will not be considered alongside the other saved networks.
    768                 if (network.useExternalScores) {
    769                     Integer netScore = getNetworkScore(scanResult, false);
    770                     externalScoreEvaluator.evalSavedCandidate(netScore, network, scanResult);
    771                     continue;
    772                 }
    773 
    774                 score = calculateBssidScore(scanResult, network, mCurrentConnectedNetwork,
    775                         (mCurrentBssid == null ? false : mCurrentBssid.equals(scanResult.BSSID)),
    776                         (lastUserSelectedNetwork == null ? false : lastUserSelectedNetwork.networkId
    777                          == network.networkId), scoreHistory);
    778                 if (score > highestScore) {
    779                     highestScore = score;
    780                     configurationCandidateForThisScan = network;
    781                     potentialCandidate = network;
    782                 }
    783                 //update the cached candidate
    784                 if (score > status.getCandidateScore() || (score == status.getCandidateScore()
    785                       && status.getCandidate() != null
    786                       && scanResult.level > status.getCandidate().level)) {
    787                     status.setCandidate(scanResult);
    788                     status.setCandidateScore(score);
    789                 }
    790             }
    791             // Create potential filteredScanDetail entry
    792             filteredScanDetails.add(Pair.create(scanDetail, potentialCandidate));
    793 
    794             if (highestScore > currentHighestScore || (highestScore == currentHighestScore
    795                     && scanResultCandidate != null
    796                     && scanResult.level > scanResultCandidate.level)) {
    797                 currentHighestScore = highestScore;
    798                 scanResultCandidate = scanResult;
    799                 networkCandidate = configurationCandidateForThisScan;
    800                 networkCandidate.getNetworkSelectionStatus().setCandidate(scanResultCandidate);
    801             }
    802         }
    803 
    804         mFilteredScanDetails = filteredScanDetails;
    805 
    806         //kick the score manager if there is any unscored network
    807         if (mScoreManager != null && unscoredNetworks.size() != 0) {
    808             NetworkKey[] unscoredNetworkKeys =
    809                     unscoredNetworks.toArray(new NetworkKey[unscoredNetworks.size()]);
    810             mScoreManager.requestScores(unscoredNetworkKeys);
    811         }
    812 
    813         if (mDbg) {
    814             localLog(lowSignalScan + " skipped due to low signal\n");
    815             localLog(notSavedScan + " skipped due to not saved\n ");
    816             localLog(noValidSsid + " skipped due to not valid SSID\n");
    817             localLog(scoreHistory.toString());
    818         }
    819 
    820         //we need traverse the whole user preference to choose the one user like most now
    821         if (scanResultCandidate != null) {
    822             WifiConfiguration tempConfig = networkCandidate;
    823 
    824             while (tempConfig.getNetworkSelectionStatus().getConnectChoice() != null) {
    825                 String key = tempConfig.getNetworkSelectionStatus().getConnectChoice();
    826                 tempConfig = mWifiConfigManager.getWifiConfiguration(key);
    827 
    828                 if (tempConfig != null) {
    829                     WifiConfiguration.NetworkSelectionStatus tempStatus =
    830                             tempConfig.getNetworkSelectionStatus();
    831                     if (tempStatus.getCandidate() != null && tempStatus.isNetworkEnabled()) {
    832                         scanResultCandidate = tempStatus.getCandidate();
    833                         networkCandidate = tempConfig;
    834                     }
    835                 } else {
    836                     //we should not come here in theory
    837                     localLoge("Connect choice: " + key + " has no corresponding saved config");
    838                     break;
    839                 }
    840             }
    841             localLog("After user choice adjust, the final candidate is:"
    842                     + getNetworkString(networkCandidate) + " : " + scanResultCandidate.BSSID);
    843         }
    844 
    845         // At this point none of the saved networks were good candidates so we fall back to
    846         // externally scored networks if any are available.
    847         if (scanResultCandidate == null) {
    848             localLog("Checking the externalScoreEvaluator for candidates...");
    849             networkCandidate = getExternalScoreCandidate(externalScoreEvaluator);
    850             if (networkCandidate != null) {
    851                 scanResultCandidate = networkCandidate.getNetworkSelectionStatus().getCandidate();
    852             }
    853         }
    854 
    855         if (scanResultCandidate == null) {
    856             localLog("Can not find any suitable candidates");
    857             return null;
    858         }
    859 
    860         String currentAssociationId = mCurrentConnectedNetwork == null ? "Disconnected" :
    861                 getNetworkString(mCurrentConnectedNetwork);
    862         String targetAssociationId = getNetworkString(networkCandidate);
    863         //In passpoint, saved configuration has garbage SSID. We need update it with the SSID of
    864         //the scan result.
    865         if (networkCandidate.isPasspoint()) {
    866             // This will update the passpoint configuration in WifiConfigManager
    867             networkCandidate.SSID = "\"" + scanResultCandidate.SSID + "\"";
    868         }
    869 
    870         //For debug purpose only
    871         if (scanResultCandidate.BSSID.equals(mCurrentBssid)) {
    872             localLog(currentAssociationId + " is already the best choice!");
    873         } else if (mCurrentConnectedNetwork != null
    874                 && (mCurrentConnectedNetwork.networkId == networkCandidate.networkId
    875                 || mCurrentConnectedNetwork.isLinked(networkCandidate))) {
    876             localLog("Roaming from " + currentAssociationId + " to " + targetAssociationId);
    877         } else {
    878             localLog("reconnect from " + currentAssociationId + " to " + targetAssociationId);
    879         }
    880 
    881         mCurrentBssid = scanResultCandidate.BSSID;
    882         mCurrentConnectedNetwork = networkCandidate;
    883         mLastQualifiedNetworkSelectionTimeStamp = mClock.elapsedRealtime();
    884         return networkCandidate;
    885     }
    886 
    887     /**
    888      * Returns the best candidate network according to the given ExternalScoreEvaluator.
    889      */
    890     @Nullable
    891     WifiConfiguration getExternalScoreCandidate(ExternalScoreEvaluator scoreEvaluator) {
    892         WifiConfiguration networkCandidate = null;
    893         switch (scoreEvaluator.getBestCandidateType()) {
    894             case ExternalScoreEvaluator.BestCandidateType.UNTRUSTED_NETWORK:
    895                 ScanResult untrustedScanResultCandidate =
    896                         scoreEvaluator.getScanResultCandidate();
    897                 WifiConfiguration unTrustedNetworkCandidate =
    898                         mWifiConfigManager.wifiConfigurationFromScanResult(
    899                                 untrustedScanResultCandidate);
    900 
    901                 // Mark this config as ephemeral so it isn't persisted.
    902                 unTrustedNetworkCandidate.ephemeral = true;
    903                 if (mNetworkScoreCache != null) {
    904                     unTrustedNetworkCandidate.meteredHint =
    905                             mNetworkScoreCache.getMeteredHint(untrustedScanResultCandidate);
    906                 }
    907                 mWifiConfigManager.saveNetwork(unTrustedNetworkCandidate,
    908                         WifiConfiguration.UNKNOWN_UID);
    909 
    910                 localLog(String.format("new ephemeral candidate %s network ID:%d, "
    911                                 + "meteredHint=%b",
    912                         toScanId(untrustedScanResultCandidate), unTrustedNetworkCandidate.networkId,
    913                         unTrustedNetworkCandidate.meteredHint));
    914 
    915                 unTrustedNetworkCandidate.getNetworkSelectionStatus()
    916                         .setCandidate(untrustedScanResultCandidate);
    917                 networkCandidate = unTrustedNetworkCandidate;
    918                 break;
    919 
    920             case ExternalScoreEvaluator.BestCandidateType.SAVED_NETWORK:
    921                 ScanResult scanResultCandidate = scoreEvaluator.getScanResultCandidate();
    922                 networkCandidate = scoreEvaluator.getSavedConfig();
    923                 networkCandidate.getNetworkSelectionStatus().setCandidate(scanResultCandidate);
    924                 localLog(String.format("new scored candidate %s network ID:%d",
    925                         toScanId(scanResultCandidate), networkCandidate.networkId));
    926                 break;
    927 
    928             case ExternalScoreEvaluator.BestCandidateType.NONE:
    929                 localLog("ExternalScoreEvaluator did not see any good candidates.");
    930                 break;
    931 
    932             default:
    933                 localLoge("Unhandled ExternalScoreEvaluator case. No candidate selected.");
    934                 break;
    935         }
    936         return networkCandidate;
    937     }
    938 
    939     /**
    940      * Returns the available external network score or NULL if no score is available.
    941      *
    942      * @param scanResult The scan result of the network to score.
    943      * @param isActiveNetwork Whether or not the network is currently connected.
    944      * @return A valid external score if one is available or NULL.
    945      */
    946     @Nullable
    947     Integer getNetworkScore(ScanResult scanResult, boolean isActiveNetwork) {
    948         if (mNetworkScoreCache != null && mNetworkScoreCache.isScoredNetwork(scanResult)) {
    949             int networkScore = mNetworkScoreCache.getNetworkScore(scanResult, isActiveNetwork);
    950             localLog(toScanId(scanResult) + " has score: " + networkScore);
    951             return networkScore;
    952         }
    953         return null;
    954     }
    955 
    956     /**
    957      * Formats the given ScanResult as a scan ID for logging.
    958      */
    959     private static String toScanId(@Nullable ScanResult scanResult) {
    960         return scanResult == null ? "NULL"
    961                                   : String.format("%s:%s", scanResult.SSID, scanResult.BSSID);
    962     }
    963 
    964     //Dump the logs
    965     void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    966         pw.println("Dump of WifiQualifiedNetworkSelector");
    967         pw.println("WifiQualifiedNetworkSelector - Log Begin ----");
    968         mLocalLog.dump(fd, pw, args);
    969         pw.println("WifiQualifiedNetworkSelector - Log End ----");
    970     }
    971 
    972     /**
    973      * Used to track and evaluate networks that are assigned external scores.
    974      */
    975     static class ExternalScoreEvaluator {
    976         @Retention(RetentionPolicy.SOURCE)
    977         @interface BestCandidateType {
    978             int NONE = 0;
    979             int SAVED_NETWORK = 1;
    980             int UNTRUSTED_NETWORK = 2;
    981         }
    982         // Always set to the best known candidate.
    983         private @BestCandidateType int mBestCandidateType = BestCandidateType.NONE;
    984         private int mHighScore = WifiNetworkScoreCache.INVALID_NETWORK_SCORE;
    985         private WifiConfiguration mSavedConfig;
    986         private ScanResult mScanResultCandidate;
    987         private final LocalLog mLocalLog;
    988         private final boolean mDbg;
    989 
    990         ExternalScoreEvaluator(LocalLog localLog, boolean dbg) {
    991             mLocalLog = localLog;
    992             mDbg = dbg;
    993         }
    994 
    995         // Determines whether or not the given scan result is the best one its seen so far.
    996         void evalUntrustedCandidate(@Nullable Integer score, ScanResult scanResult) {
    997             if (score != null && score > mHighScore) {
    998                 mHighScore = score;
    999                 mScanResultCandidate = scanResult;
   1000                 mBestCandidateType = BestCandidateType.UNTRUSTED_NETWORK;
   1001                 localLog(toScanId(scanResult) + " become the new untrusted candidate");
   1002             }
   1003         }
   1004 
   1005         // Determines whether or not the given saved network is the best one its seen so far.
   1006         void evalSavedCandidate(@Nullable Integer score, WifiConfiguration config,
   1007                 ScanResult scanResult) {
   1008             // Always take the highest score. If there's a tie and an untrusted network is currently
   1009             // the best then pick the saved network.
   1010             if (score != null
   1011                     && (score > mHighScore
   1012                         || (mBestCandidateType == BestCandidateType.UNTRUSTED_NETWORK
   1013                             && score == mHighScore))) {
   1014                 mHighScore = score;
   1015                 mSavedConfig = config;
   1016                 mScanResultCandidate = scanResult;
   1017                 mBestCandidateType = BestCandidateType.SAVED_NETWORK;
   1018                 localLog(toScanId(scanResult) + " become the new externally scored saved network "
   1019                         + "candidate");
   1020             }
   1021         }
   1022 
   1023         int getBestCandidateType() {
   1024             return mBestCandidateType;
   1025         }
   1026 
   1027         int getHighScore() {
   1028             return mHighScore;
   1029         }
   1030 
   1031         public ScanResult getScanResultCandidate() {
   1032             return mScanResultCandidate;
   1033         }
   1034 
   1035         WifiConfiguration getSavedConfig() {
   1036             return mSavedConfig;
   1037         }
   1038 
   1039         private void localLog(String log) {
   1040             if (mDbg) {
   1041                 mLocalLog.log(log);
   1042             }
   1043         }
   1044     }
   1045 }
   1046