Home | History | Annotate | Download | only in wifi
      1 /*
      2  * Copyright (C) 2016 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.NonNull;
     20 import android.annotation.Nullable;
     21 import android.content.Context;
     22 import android.net.NetworkKey;
     23 import android.net.wifi.ScanResult;
     24 import android.net.wifi.WifiConfiguration;
     25 import android.net.wifi.WifiInfo;
     26 import android.text.TextUtils;
     27 import android.util.LocalLog;
     28 import android.util.Pair;
     29 
     30 import com.android.internal.R;
     31 import com.android.internal.annotations.VisibleForTesting;
     32 import com.android.server.wifi.util.ScanResultUtil;
     33 
     34 import java.util.ArrayList;
     35 import java.util.HashSet;
     36 import java.util.List;
     37 
     38 /**
     39  * This class looks at all the connectivity scan results then
     40  * selects a network for the phone to connect or roam to.
     41  */
     42 public class WifiNetworkSelector {
     43     private static final String TAG = "WifiNetworkSelector";
     44 
     45     private static final long INVALID_TIME_STAMP = Long.MIN_VALUE;
     46     // Minimum time gap between last successful network selection and a new selection
     47     // attempt.
     48     @VisibleForTesting
     49     public static final int MINIMUM_NETWORK_SELECTION_INTERVAL_MS = 10 * 1000;
     50 
     51     private final WifiConfigManager mWifiConfigManager;
     52     private final Clock mClock;
     53     private final LocalLog mLocalLog;
     54     private long mLastNetworkSelectionTimeStamp = INVALID_TIME_STAMP;
     55     // Buffer of filtered scan results (Scan results considered by network selection) & associated
     56     // WifiConfiguration (if any).
     57     private volatile List<Pair<ScanDetail, WifiConfiguration>> mConnectableNetworks =
     58             new ArrayList<>();
     59     private List<ScanDetail> mFilteredNetworks = new ArrayList<>();
     60     private final ScoringParams mScoringParams;
     61     private final int mStayOnNetworkMinimumTxRate;
     62     private final int mStayOnNetworkMinimumRxRate;
     63     private final boolean mEnableAutoJoinWhenAssociated;
     64 
     65     /**
     66      * WiFi Network Selector supports various types of networks. Each type can
     67      * have its evaluator to choose the best WiFi network for the device to connect
     68      * to. When registering a WiFi network evaluator with the WiFi Network Selector,
     69      * the priority of the network must be specified, and it must be a value between
     70      * 0 and (EVALUATOR_MIN_PIRORITY - 1) with 0 being the highest priority. Wifi
     71      * Network Selector iterates through the registered scorers from the highest priority
     72      * to the lowest till a network is selected.
     73      */
     74     public static final int EVALUATOR_MIN_PRIORITY = 6;
     75 
     76     /**
     77      * Maximum number of evaluators can be registered with Wifi Network Selector.
     78      */
     79     public static final int MAX_NUM_EVALUATORS = EVALUATOR_MIN_PRIORITY;
     80 
     81     /**
     82      * Interface for WiFi Network Evaluator
     83      *
     84      * A network scorer evaulates all the networks from the scan results and
     85      * recommends the best network in its category to connect or roam to.
     86      */
     87     public interface NetworkEvaluator {
     88         /**
     89          * Get the evaluator name.
     90          */
     91         String getName();
     92 
     93         /**
     94          * Update the evaluator.
     95          *
     96          * Certain evaluators have to be updated with the new scan results. For example
     97          * the ExternalScoreEvalutor needs to refresh its Score Cache.
     98          *
     99          * @param scanDetails    a list of scan details constructed from the scan results
    100          */
    101         void update(List<ScanDetail> scanDetails);
    102 
    103         /**
    104          * Evaluate all the networks from the scan results.
    105          *
    106          * @param scanDetails    a list of scan details constructed from the scan results
    107          * @param currentNetwork configuration of the current connected network
    108          *                       or null if disconnected
    109          * @param currentBssid   BSSID of the current connected network or null if
    110          *                       disconnected
    111          * @param connected      a flag to indicate if WifiStateMachine is in connected
    112          *                       state
    113          * @param untrustedNetworkAllowed a flag to indidate if untrusted networks like
    114          *                                ephemeral networks are allowed
    115          * @param connectableNetworks     a list of the ScanDetail and WifiConfiguration
    116          *                                pair which is used by the WifiLastResortWatchdog
    117          * @return configuration of the chosen network;
    118          *         null if no network in this category is available.
    119          */
    120         @Nullable
    121         WifiConfiguration evaluateNetworks(List<ScanDetail> scanDetails,
    122                         WifiConfiguration currentNetwork, String currentBssid,
    123                         boolean connected, boolean untrustedNetworkAllowed,
    124                         List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks);
    125     }
    126 
    127     private final NetworkEvaluator[] mEvaluators = new NetworkEvaluator[MAX_NUM_EVALUATORS];
    128 
    129     // A helper to log debugging information in the local log buffer, which can
    130     // be retrieved in bugreport.
    131     private void localLog(String log) {
    132         mLocalLog.log(log);
    133     }
    134 
    135     private boolean isCurrentNetworkSufficient(WifiInfo wifiInfo, List<ScanDetail> scanDetails) {
    136         WifiConfiguration network =
    137                             mWifiConfigManager.getConfiguredNetwork(wifiInfo.getNetworkId());
    138 
    139         // Currently connected?
    140         if (network == null) {
    141             localLog("No current connected network.");
    142             return false;
    143         } else {
    144             localLog("Current connected network: " + network.SSID
    145                     + " , ID: " + network.networkId);
    146         }
    147 
    148         int currentRssi = wifiInfo.getRssi();
    149         boolean hasQualifiedRssi = currentRssi
    150                 > mScoringParams.getSufficientRssi(wifiInfo.getFrequency());
    151         boolean hasActiveStream = (wifiInfo.txSuccessRate > mStayOnNetworkMinimumTxRate)
    152                 || (wifiInfo.rxSuccessRate > mStayOnNetworkMinimumRxRate);
    153         if (hasQualifiedRssi && hasActiveStream) {
    154             localLog("Stay on current network because of good RSSI and ongoing traffic");
    155             return true;
    156         }
    157 
    158         // Ephemeral network is not qualified.
    159         if (network.ephemeral) {
    160             localLog("Current network is an ephemeral one.");
    161             return false;
    162         }
    163 
    164         // Open network is not qualified.
    165         if (WifiConfigurationUtil.isConfigForOpenNetwork(network)) {
    166             localLog("Current network is a open one.");
    167             return false;
    168         }
    169 
    170         if (wifiInfo.is24GHz()) {
    171             // 2.4GHz networks is not qualified whenever 5GHz is available
    172             if (is5GHzNetworkAvailable(scanDetails)) {
    173                 localLog("Current network is 2.4GHz. 5GHz networks available.");
    174                 return false;
    175             }
    176         }
    177         if (!hasQualifiedRssi) {
    178             localLog("Current network RSSI[" + currentRssi + "]-acceptable but not qualified.");
    179             return false;
    180         }
    181 
    182         // Network with no internet access reports is not qualified.
    183         if (network.numNoInternetAccessReports > 0 && !network.noInternetAccessExpected) {
    184             localLog("Current network has [" + network.numNoInternetAccessReports
    185                     + "] no-internet access reports.");
    186             return false;
    187         }
    188         return true;
    189     }
    190 
    191     // Determine whether there are any 5GHz networks in the scan result
    192     private boolean is5GHzNetworkAvailable(List<ScanDetail> scanDetails) {
    193         for (ScanDetail detail : scanDetails) {
    194             ScanResult result = detail.getScanResult();
    195             if (result.is5GHz()) return true;
    196         }
    197         return false;
    198     }
    199 
    200     private boolean isNetworkSelectionNeeded(List<ScanDetail> scanDetails, WifiInfo wifiInfo,
    201                         boolean connected, boolean disconnected) {
    202         if (scanDetails.size() == 0) {
    203             localLog("Empty connectivity scan results. Skip network selection.");
    204             return false;
    205         }
    206 
    207         if (connected) {
    208             // Is roaming allowed?
    209             if (!mEnableAutoJoinWhenAssociated) {
    210                 localLog("Switching networks in connected state is not allowed."
    211                         + " Skip network selection.");
    212                 return false;
    213             }
    214 
    215             // Has it been at least the minimum interval since last network selection?
    216             if (mLastNetworkSelectionTimeStamp != INVALID_TIME_STAMP) {
    217                 long gap = mClock.getElapsedSinceBootMillis()
    218                             - mLastNetworkSelectionTimeStamp;
    219                 if (gap < MINIMUM_NETWORK_SELECTION_INTERVAL_MS) {
    220                     localLog("Too short since last network selection: " + gap + " ms."
    221                             + " Skip network selection.");
    222                     return false;
    223                 }
    224             }
    225 
    226             if (isCurrentNetworkSufficient(wifiInfo, scanDetails)) {
    227                 localLog("Current connected network already sufficient. Skip network selection.");
    228                 return false;
    229             } else {
    230                 localLog("Current connected network is not sufficient.");
    231                 return true;
    232             }
    233         } else if (disconnected) {
    234             return true;
    235         } else {
    236             // No network selection if WifiStateMachine is in a state other than
    237             // CONNECTED or DISCONNECTED.
    238             localLog("WifiStateMachine is in neither CONNECTED nor DISCONNECTED state."
    239                     + " Skip network selection.");
    240             return false;
    241         }
    242     }
    243 
    244     /**
    245      * Format the given ScanResult as a scan ID for logging.
    246      */
    247     public static String toScanId(@Nullable ScanResult scanResult) {
    248         return scanResult == null ? "NULL"
    249                                   : String.format("%s:%s", scanResult.SSID, scanResult.BSSID);
    250     }
    251 
    252     /**
    253      * Format the given WifiConfiguration as a SSID:netId string
    254      */
    255     public static String toNetworkString(WifiConfiguration network) {
    256         if (network == null) {
    257             return null;
    258         }
    259 
    260         return (network.SSID + ":" + network.networkId);
    261     }
    262 
    263     /**
    264      * Compares ScanResult level against the minimum threshold for its band, returns true if lower
    265      */
    266     public boolean isSignalTooWeak(ScanResult scanResult) {
    267         return (scanResult.level < mScoringParams.getEntryRssi(scanResult.frequency));
    268     }
    269 
    270     private List<ScanDetail> filterScanResults(List<ScanDetail> scanDetails,
    271                 HashSet<String> bssidBlacklist, boolean isConnected, String currentBssid) {
    272         ArrayList<NetworkKey> unscoredNetworks = new ArrayList<NetworkKey>();
    273         List<ScanDetail> validScanDetails = new ArrayList<ScanDetail>();
    274         StringBuffer noValidSsid = new StringBuffer();
    275         StringBuffer blacklistedBssid = new StringBuffer();
    276         StringBuffer lowRssi = new StringBuffer();
    277         boolean scanResultsHaveCurrentBssid = false;
    278 
    279         for (ScanDetail scanDetail : scanDetails) {
    280             ScanResult scanResult = scanDetail.getScanResult();
    281 
    282             if (TextUtils.isEmpty(scanResult.SSID)) {
    283                 noValidSsid.append(scanResult.BSSID).append(" / ");
    284                 continue;
    285             }
    286 
    287             // Check if the scan results contain the currently connected BSSID
    288             if (scanResult.BSSID.equals(currentBssid)) {
    289                 scanResultsHaveCurrentBssid = true;
    290             }
    291 
    292             final String scanId = toScanId(scanResult);
    293 
    294             if (bssidBlacklist.contains(scanResult.BSSID)) {
    295                 blacklistedBssid.append(scanId).append(" / ");
    296                 continue;
    297             }
    298 
    299             // Skip network with too weak signals.
    300             if (isSignalTooWeak(scanResult)) {
    301                 lowRssi.append(scanId).append("(")
    302                     .append(scanResult.is24GHz() ? "2.4GHz" : "5GHz")
    303                     .append(")").append(scanResult.level).append(" / ");
    304                 continue;
    305             }
    306 
    307             validScanDetails.add(scanDetail);
    308         }
    309 
    310         // WNS listens to all single scan results. Some scan requests may not include
    311         // the channel of the currently connected network, so the currently connected
    312         // network won't show up in the scan results. We don't act on these scan results
    313         // to avoid aggressive network switching which might trigger disconnection.
    314         if (isConnected && !scanResultsHaveCurrentBssid) {
    315             localLog("Current connected BSSID " + currentBssid + " is not in the scan results."
    316                     + " Skip network selection.");
    317             validScanDetails.clear();
    318             return validScanDetails;
    319         }
    320 
    321         if (noValidSsid.length() != 0) {
    322             localLog("Networks filtered out due to invalid SSID: " + noValidSsid);
    323         }
    324 
    325         if (blacklistedBssid.length() != 0) {
    326             localLog("Networks filtered out due to blacklist: " + blacklistedBssid);
    327         }
    328 
    329         if (lowRssi.length() != 0) {
    330             localLog("Networks filtered out due to low signal strength: " + lowRssi);
    331         }
    332 
    333         return validScanDetails;
    334     }
    335 
    336     /**
    337      * This returns a list of ScanDetails that were filtered in the process of network selection.
    338      * The list is further filtered for only open unsaved networks.
    339      *
    340      * @return the list of ScanDetails for open unsaved networks that do not have invalid SSIDS,
    341      * blacklisted BSSIDS, or low signal strength. This will return an empty list when there are
    342      * no open unsaved networks, or when network selection has not been run.
    343      */
    344     public List<ScanDetail> getFilteredScanDetailsForOpenUnsavedNetworks() {
    345         List<ScanDetail> openUnsavedNetworks = new ArrayList<>();
    346         for (ScanDetail scanDetail : mFilteredNetworks) {
    347             ScanResult scanResult = scanDetail.getScanResult();
    348 
    349             if (!ScanResultUtil.isScanResultForOpenNetwork(scanResult)) {
    350                 continue;
    351             }
    352 
    353             // Skip saved networks
    354             if (mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(scanDetail) != null) {
    355                 continue;
    356             }
    357 
    358             openUnsavedNetworks.add(scanDetail);
    359         }
    360         return openUnsavedNetworks;
    361     }
    362 
    363     /**
    364      * This returns a list of ScanDetails that were filtered in the process of network selection.
    365      * The list is further filtered for only carrier unsaved networks with EAP encryption.
    366      *
    367      * @param carrierConfig CarrierNetworkConfig used to filter carrier networks
    368      * @return the list of ScanDetails for carrier unsaved networks that do not have invalid SSIDS,
    369      * blacklisted BSSIDS, or low signal strength, and with EAP encryption. This will return an
    370      * empty list when there are no such networks, or when network selection has not been run.
    371      */
    372     public List<ScanDetail> getFilteredScanDetailsForCarrierUnsavedNetworks(
    373             CarrierNetworkConfig carrierConfig) {
    374         List<ScanDetail> carrierUnsavedNetworks = new ArrayList<>();
    375         for (ScanDetail scanDetail : mFilteredNetworks) {
    376             ScanResult scanResult = scanDetail.getScanResult();
    377 
    378             if (!ScanResultUtil.isScanResultForEapNetwork(scanResult)
    379                     || !carrierConfig.isCarrierNetwork(scanResult.SSID)) {
    380                 continue;
    381             }
    382 
    383             // Skip saved networks
    384             if (mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(scanDetail) != null) {
    385                 continue;
    386             }
    387 
    388             carrierUnsavedNetworks.add(scanDetail);
    389         }
    390         return carrierUnsavedNetworks;
    391     }
    392 
    393     /**
    394      * @return the list of ScanDetails scored as potential candidates by the last run of
    395      * selectNetwork, this will be empty if Network selector determined no selection was
    396      * needed on last run. This includes scan details of sufficient signal strength, and
    397      * had an associated WifiConfiguration.
    398      */
    399     public List<Pair<ScanDetail, WifiConfiguration>> getConnectableScanDetails() {
    400         return mConnectableNetworks;
    401     }
    402 
    403     /**
    404      * This API is called when user explicitly selects a network. Currently, it is used in following
    405      * cases:
    406      * (1) User explicitly chooses to connect to a saved network.
    407      * (2) User saves a network after adding a new network.
    408      * (3) User saves a network after modifying a saved network.
    409      * Following actions will be triggered:
    410      * 1. If this network is disabled, we need re-enable it again.
    411      * 2. This network is favored over all the other networks visible in latest network
    412      *    selection procedure.
    413      *
    414      * @param netId  ID for the network chosen by the user
    415      * @return true -- There is change made to connection choice of any saved network.
    416      *         false -- There is no change made to connection choice of any saved network.
    417      */
    418     public boolean setUserConnectChoice(int netId) {
    419         localLog("userSelectNetwork: network ID=" + netId);
    420         WifiConfiguration selected = mWifiConfigManager.getConfiguredNetwork(netId);
    421 
    422         if (selected == null || selected.SSID == null) {
    423             localLog("userSelectNetwork: Invalid configuration with nid=" + netId);
    424             return false;
    425         }
    426 
    427         // Enable the network if it is disabled.
    428         if (!selected.getNetworkSelectionStatus().isNetworkEnabled()) {
    429             mWifiConfigManager.updateNetworkSelectionStatus(netId,
    430                     WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
    431         }
    432 
    433         boolean change = false;
    434         String key = selected.configKey();
    435         // This is only used for setting the connect choice timestamp for debugging purposes.
    436         long currentTime = mClock.getWallClockMillis();
    437         List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks();
    438 
    439         for (WifiConfiguration network : savedNetworks) {
    440             WifiConfiguration.NetworkSelectionStatus status = network.getNetworkSelectionStatus();
    441             if (network.networkId == selected.networkId) {
    442                 if (status.getConnectChoice() != null) {
    443                     localLog("Remove user selection preference of " + status.getConnectChoice()
    444                             + " Set Time: " + status.getConnectChoiceTimestamp() + " from "
    445                             + network.SSID + " : " + network.networkId);
    446                     mWifiConfigManager.clearNetworkConnectChoice(network.networkId);
    447                     change = true;
    448                 }
    449                 continue;
    450             }
    451 
    452             if (status.getSeenInLastQualifiedNetworkSelection()
    453                     && (status.getConnectChoice() == null
    454                     || !status.getConnectChoice().equals(key))) {
    455                 localLog("Add key: " + key + " Set Time: " + currentTime + " to "
    456                         + toNetworkString(network));
    457                 mWifiConfigManager.setNetworkConnectChoice(network.networkId, key, currentTime);
    458                 change = true;
    459             }
    460         }
    461 
    462         return change;
    463     }
    464 
    465     /**
    466      * Overrides the {@code candidate} chosen by the {@link #mEvaluators} with the user chosen
    467      * {@link WifiConfiguration} if one exists.
    468      *
    469      * @return the user chosen {@link WifiConfiguration} if one exists, {@code candidate} otherwise
    470      */
    471     private WifiConfiguration overrideCandidateWithUserConnectChoice(
    472             @NonNull WifiConfiguration candidate) {
    473         WifiConfiguration tempConfig = candidate;
    474         WifiConfiguration originalCandidate = candidate;
    475         ScanResult scanResultCandidate = candidate.getNetworkSelectionStatus().getCandidate();
    476 
    477         while (tempConfig.getNetworkSelectionStatus().getConnectChoice() != null) {
    478             String key = tempConfig.getNetworkSelectionStatus().getConnectChoice();
    479             tempConfig = mWifiConfigManager.getConfiguredNetwork(key);
    480 
    481             if (tempConfig != null) {
    482                 WifiConfiguration.NetworkSelectionStatus tempStatus =
    483                         tempConfig.getNetworkSelectionStatus();
    484                 if (tempStatus.getCandidate() != null && tempStatus.isNetworkEnabled()) {
    485                     scanResultCandidate = tempStatus.getCandidate();
    486                     candidate = tempConfig;
    487                 }
    488             } else {
    489                 localLog("Connect choice: " + key + " has no corresponding saved config.");
    490                 break;
    491             }
    492         }
    493 
    494         if (candidate != originalCandidate) {
    495             localLog("After user selection adjustment, the final candidate is:"
    496                     + WifiNetworkSelector.toNetworkString(candidate) + " : "
    497                     + scanResultCandidate.BSSID);
    498         }
    499         return candidate;
    500     }
    501 
    502     /**
    503      * Select the best network from the ones in range.
    504      *
    505      * @param scanDetails    List of ScanDetail for all the APs in range
    506      * @param bssidBlacklist Blacklisted BSSIDs
    507      * @param wifiInfo       Currently connected network
    508      * @param connected      True if the device is connected
    509      * @param disconnected   True if the device is disconnected
    510      * @param untrustedNetworkAllowed True if untrusted networks are allowed for connection
    511      * @return Configuration of the selected network, or Null if nothing
    512      */
    513     @Nullable
    514     public WifiConfiguration selectNetwork(List<ScanDetail> scanDetails,
    515             HashSet<String> bssidBlacklist, WifiInfo wifiInfo,
    516             boolean connected, boolean disconnected, boolean untrustedNetworkAllowed) {
    517         mFilteredNetworks.clear();
    518         mConnectableNetworks.clear();
    519         if (scanDetails.size() == 0) {
    520             localLog("Empty connectivity scan result");
    521             return null;
    522         }
    523 
    524         WifiConfiguration currentNetwork =
    525                 mWifiConfigManager.getConfiguredNetwork(wifiInfo.getNetworkId());
    526 
    527         // Always get the current BSSID from WifiInfo in case that firmware initiated
    528         // roaming happened.
    529         String currentBssid = wifiInfo.getBSSID();
    530 
    531         // Shall we start network selection at all?
    532         if (!isNetworkSelectionNeeded(scanDetails, wifiInfo, connected, disconnected)) {
    533             return null;
    534         }
    535 
    536         // Update the registered network evaluators.
    537         for (NetworkEvaluator registeredEvaluator : mEvaluators) {
    538             if (registeredEvaluator != null) {
    539                 registeredEvaluator.update(scanDetails);
    540             }
    541         }
    542 
    543         // Filter out unwanted networks.
    544         mFilteredNetworks = filterScanResults(scanDetails, bssidBlacklist,
    545                 connected, currentBssid);
    546         if (mFilteredNetworks.size() == 0) {
    547             return null;
    548         }
    549 
    550         // Go through the registered network evaluators from the highest priority
    551         // one to the lowest till a network is selected.
    552         WifiConfiguration selectedNetwork = null;
    553         for (NetworkEvaluator registeredEvaluator : mEvaluators) {
    554             if (registeredEvaluator != null) {
    555                 localLog("About to run " + registeredEvaluator.getName() + " :");
    556                 selectedNetwork = registeredEvaluator.evaluateNetworks(
    557                         new ArrayList<>(mFilteredNetworks), currentNetwork, currentBssid, connected,
    558                         untrustedNetworkAllowed, mConnectableNetworks);
    559                 if (selectedNetwork != null) {
    560                     localLog(registeredEvaluator.getName() + " selects "
    561                             + WifiNetworkSelector.toNetworkString(selectedNetwork) + " : "
    562                             + selectedNetwork.getNetworkSelectionStatus().getCandidate().BSSID);
    563                     break;
    564                 }
    565             }
    566         }
    567 
    568         if (selectedNetwork != null) {
    569             selectedNetwork = overrideCandidateWithUserConnectChoice(selectedNetwork);
    570             mLastNetworkSelectionTimeStamp = mClock.getElapsedSinceBootMillis();
    571         }
    572 
    573         return selectedNetwork;
    574     }
    575 
    576     /**
    577      * Register a network evaluator
    578      *
    579      * @param evaluator the network evaluator to be registered
    580      * @param priority a value between 0 and (SCORER_MIN_PRIORITY-1)
    581      *
    582      * @return true if the evaluator is successfully registered with QNS;
    583      *         false if failed to register the evaluator
    584      */
    585     public boolean registerNetworkEvaluator(NetworkEvaluator evaluator, int priority) {
    586         if (priority < 0 || priority >= EVALUATOR_MIN_PRIORITY) {
    587             localLog("Invalid network evaluator priority: " + priority);
    588             return false;
    589         }
    590 
    591         if (mEvaluators[priority] != null) {
    592             localLog("Priority " + priority + " is already registered by "
    593                     + mEvaluators[priority].getName());
    594             return false;
    595         }
    596 
    597         mEvaluators[priority] = evaluator;
    598         return true;
    599     }
    600 
    601     WifiNetworkSelector(Context context, ScoringParams scoringParams,
    602             WifiConfigManager configManager, Clock clock,
    603             LocalLog localLog) {
    604         mWifiConfigManager = configManager;
    605         mClock = clock;
    606         mScoringParams = scoringParams;
    607         mLocalLog = localLog;
    608 
    609         mEnableAutoJoinWhenAssociated = context.getResources().getBoolean(
    610                 R.bool.config_wifi_framework_enable_associated_network_selection);
    611         mStayOnNetworkMinimumTxRate = context.getResources().getInteger(
    612                 R.integer.config_wifi_framework_min_tx_rate_for_staying_on_network);
    613         mStayOnNetworkMinimumRxRate = context.getResources().getInteger(
    614                 R.integer.config_wifi_framework_min_rx_rate_for_staying_on_network);
    615     }
    616 }
    617