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.content.Context;
     20 import android.net.wifi.ScanResult;
     21 import android.net.wifi.WifiConfiguration;
     22 import android.util.LocalLog;
     23 import android.util.Pair;
     24 
     25 import com.android.internal.R;
     26 
     27 import java.util.ArrayList;
     28 import java.util.Arrays;
     29 import java.util.List;
     30 
     31 /**
     32  * This class is the WifiNetworkSelector.NetworkEvaluator implementation for
     33  * saved networks.
     34  */
     35 public class SavedNetworkEvaluator implements WifiNetworkSelector.NetworkEvaluator {
     36     private static final String NAME = "SavedNetworkEvaluator";
     37     private final WifiConfigManager mWifiConfigManager;
     38     private final Clock mClock;
     39     private final LocalLog mLocalLog;
     40     private final WifiConnectivityHelper mConnectivityHelper;
     41     private final int mRssiScoreSlope;
     42     private final int mRssiScoreOffset;
     43     private final int mSameBssidAward;
     44     private final int mSameNetworkAward;
     45     private final int mBand5GHzAward;
     46     private final int mLastSelectionAward;
     47     private final int mSecurityAward;
     48     private final int mThresholdSaturatedRssi24;
     49     private final int mThresholdSaturatedRssi5;
     50 
     51     SavedNetworkEvaluator(final Context context, WifiConfigManager configManager, Clock clock,
     52             LocalLog localLog, WifiConnectivityHelper connectivityHelper) {
     53         mWifiConfigManager = configManager;
     54         mClock = clock;
     55         mLocalLog = localLog;
     56         mConnectivityHelper = connectivityHelper;
     57 
     58         mRssiScoreSlope = context.getResources().getInteger(
     59                 R.integer.config_wifi_framework_RSSI_SCORE_SLOPE);
     60         mRssiScoreOffset = context.getResources().getInteger(
     61                 R.integer.config_wifi_framework_RSSI_SCORE_OFFSET);
     62         mSameBssidAward = context.getResources().getInteger(
     63                 R.integer.config_wifi_framework_SAME_BSSID_AWARD);
     64         mSameNetworkAward = context.getResources().getInteger(
     65                 R.integer.config_wifi_framework_current_network_boost);
     66         mLastSelectionAward = context.getResources().getInteger(
     67                 R.integer.config_wifi_framework_LAST_SELECTION_AWARD);
     68         mSecurityAward = context.getResources().getInteger(
     69                 R.integer.config_wifi_framework_SECURITY_AWARD);
     70         mBand5GHzAward = context.getResources().getInteger(
     71                 R.integer.config_wifi_framework_5GHz_preference_boost_factor);
     72         mThresholdSaturatedRssi24 = context.getResources().getInteger(
     73                 R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz);
     74         mThresholdSaturatedRssi5 = context.getResources().getInteger(
     75                 R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz);
     76     }
     77 
     78     private void localLog(String log) {
     79         mLocalLog.log(log);
     80     }
     81 
     82     /**
     83      * Get the evaluator name.
     84      */
     85     public String getName() {
     86         return NAME;
     87     }
     88 
     89     /**
     90      * Update all the saved networks' selection status
     91      */
     92     private void updateSavedNetworkSelectionStatus() {
     93         List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks();
     94         if (savedNetworks.size() == 0) {
     95             localLog("No saved networks.");
     96             return;
     97         }
     98 
     99         StringBuffer sbuf = new StringBuffer();
    100         for (WifiConfiguration network : savedNetworks) {
    101             /**
    102              * Ignore Passpoint networks. Passpoint networks are also considered as "saved"
    103              * network, but without being persisted to the storage. They are managed
    104              * by {@link PasspointNetworkEvaluator}.
    105              */
    106             if (network.isPasspoint()) {
    107                 continue;
    108             }
    109 
    110             // If a configuration is temporarily disabled, re-enable it before trying
    111             // to connect to it.
    112             mWifiConfigManager.tryEnableNetwork(network.networkId);
    113 
    114             //TODO(b/30928589): Enable "permanently" disabled networks if we are in DISCONNECTED
    115             // state.
    116 
    117             // Clear the cached candidate, score and seen.
    118             mWifiConfigManager.clearNetworkCandidateScanResult(network.networkId);
    119 
    120             // Log disabled network.
    121             WifiConfiguration.NetworkSelectionStatus status = network.getNetworkSelectionStatus();
    122             if (!status.isNetworkEnabled()) {
    123                 sbuf.append("  ").append(WifiNetworkSelector.toNetworkString(network)).append(" ");
    124                 for (int index = WifiConfiguration.NetworkSelectionStatus
    125                             .NETWORK_SELECTION_DISABLED_STARTING_INDEX;
    126                         index < WifiConfiguration.NetworkSelectionStatus
    127                             .NETWORK_SELECTION_DISABLED_MAX;
    128                         index++) {
    129                     int count = status.getDisableReasonCounter(index);
    130                     // Here we log the reason as long as its count is greater than zero. The
    131                     // network may not be disabled because of this particular reason. Logging
    132                     // this information anyway to help understand what happened to the network.
    133                     if (count > 0) {
    134                         sbuf.append("reason=")
    135                                 .append(WifiConfiguration.NetworkSelectionStatus
    136                                         .getNetworkDisableReasonString(index))
    137                                 .append(", count=").append(count).append("; ");
    138                     }
    139                 }
    140                 sbuf.append("\n");
    141             }
    142         }
    143 
    144         if (sbuf.length() > 0) {
    145             localLog("Disabled saved networks:");
    146             localLog(sbuf.toString());
    147         }
    148     }
    149 
    150     /**
    151      * Update the evaluator.
    152      */
    153     public void update(List<ScanDetail> scanDetails) {
    154         updateSavedNetworkSelectionStatus();
    155     }
    156 
    157     private int calculateBssidScore(ScanResult scanResult, WifiConfiguration network,
    158                         WifiConfiguration currentNetwork, String currentBssid,
    159                         StringBuffer sbuf) {
    160         int score = 0;
    161         boolean is5GHz = scanResult.is5GHz();
    162 
    163         sbuf.append("[ ").append(scanResult.SSID).append(" ").append(scanResult.BSSID)
    164                 .append(" RSSI:").append(scanResult.level).append(" ] ");
    165         // Calculate the RSSI score.
    166         int rssiSaturationThreshold = is5GHz ? mThresholdSaturatedRssi5 : mThresholdSaturatedRssi24;
    167         int rssi = scanResult.level < rssiSaturationThreshold ? scanResult.level
    168                 : rssiSaturationThreshold;
    169         score += (rssi + mRssiScoreOffset) * mRssiScoreSlope;
    170         sbuf.append(" RSSI score: ").append(score).append(",");
    171 
    172         // 5GHz band bonus.
    173         if (is5GHz) {
    174             score += mBand5GHzAward;
    175             sbuf.append(" 5GHz bonus: ").append(mBand5GHzAward).append(",");
    176         }
    177 
    178         // Last user selection award.
    179         int lastUserSelectedNetworkId = mWifiConfigManager.getLastSelectedNetwork();
    180         if (lastUserSelectedNetworkId != WifiConfiguration.INVALID_NETWORK_ID
    181                 && lastUserSelectedNetworkId == network.networkId) {
    182             long timeDifference = mClock.getElapsedSinceBootMillis()
    183                     - mWifiConfigManager.getLastSelectedTimeStamp();
    184             if (timeDifference > 0) {
    185                 int bonus = mLastSelectionAward - (int) (timeDifference / 1000 / 60);
    186                 score += bonus > 0 ? bonus : 0;
    187                 sbuf.append(" User selection ").append(timeDifference / 1000 / 60)
    188                         .append(" minutes ago, bonus: ").append(bonus).append(",");
    189             }
    190         }
    191 
    192         // Same network award.
    193         if (currentNetwork != null
    194                 && (network.networkId == currentNetwork.networkId
    195                 //TODO(b/36788683): re-enable linked configuration check
    196                 /* || network.isLinked(currentNetwork) */)) {
    197             score += mSameNetworkAward;
    198             sbuf.append(" Same network bonus: ").append(mSameNetworkAward).append(",");
    199 
    200             // When firmware roaming is supported, equivalent BSSIDs (the ones under the
    201             // same network as the currently connected one) get the same BSSID award.
    202             if (mConnectivityHelper.isFirmwareRoamingSupported()
    203                     && currentBssid != null && !currentBssid.equals(scanResult.BSSID)) {
    204                 score += mSameBssidAward;
    205                 sbuf.append(" Equivalent BSSID bonus: ").append(mSameBssidAward).append(",");
    206             }
    207         }
    208 
    209         // Same BSSID award.
    210         if (currentBssid != null && currentBssid.equals(scanResult.BSSID)) {
    211             score += mSameBssidAward;
    212             sbuf.append(" Same BSSID bonus: ").append(mSameBssidAward).append(",");
    213         }
    214 
    215         // Security award.
    216         if (!WifiConfigurationUtil.isConfigForOpenNetwork(network)) {
    217             score += mSecurityAward;
    218             sbuf.append(" Secure network bonus: ").append(mSecurityAward).append(",");
    219         }
    220 
    221         sbuf.append(" ## Total score: ").append(score).append("\n");
    222 
    223         return score;
    224     }
    225 
    226     /**
    227      * Evaluate all the networks from the scan results and return
    228      * the WifiConfiguration of the network chosen for connection.
    229      *
    230      * @return configuration of the chosen network;
    231      *         null if no network in this category is available.
    232      */
    233     public WifiConfiguration evaluateNetworks(List<ScanDetail> scanDetails,
    234                     WifiConfiguration currentNetwork, String currentBssid, boolean connected,
    235                     boolean untrustedNetworkAllowed,
    236                     List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks) {
    237         int highestScore = Integer.MIN_VALUE;
    238         ScanResult scanResultCandidate = null;
    239         WifiConfiguration candidate = null;
    240         StringBuffer scoreHistory = new StringBuffer();
    241 
    242         for (ScanDetail scanDetail : scanDetails) {
    243             ScanResult scanResult = scanDetail.getScanResult();
    244             int highestScoreOfScanResult = Integer.MIN_VALUE;
    245             int candidateIdOfScanResult = WifiConfiguration.INVALID_NETWORK_ID;
    246 
    247             // One ScanResult can be associated with more than one networks, hence we calculate all
    248             // the scores and use the highest one as the ScanResult's score.
    249             List<WifiConfiguration> associatedConfigurations = null;
    250             WifiConfiguration associatedConfiguration =
    251                     mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail);
    252 
    253             if (associatedConfiguration == null) {
    254                 continue;
    255             } else {
    256                 associatedConfigurations =
    257                     new ArrayList<>(Arrays.asList(associatedConfiguration));
    258             }
    259 
    260             for (WifiConfiguration network : associatedConfigurations) {
    261                 /**
    262                  * Ignore Passpoint and Ephemeral networks. They are configured networks,
    263                  * but without being persisted to the storage. They are evaluated by
    264                  * {@link PasspointNetworkEvaluator} and {@link ScoredNetworkEvaluator}
    265                  * respectively.
    266                  */
    267                 if (network.isPasspoint() || network.isEphemeral()) {
    268                     continue;
    269                 }
    270 
    271                 WifiConfiguration.NetworkSelectionStatus status =
    272                         network.getNetworkSelectionStatus();
    273                 status.setSeenInLastQualifiedNetworkSelection(true);
    274 
    275                 if (!status.isNetworkEnabled()) {
    276                     continue;
    277                 } else if (network.BSSID != null &&  !network.BSSID.equals("any")
    278                         && !network.BSSID.equals(scanResult.BSSID)) {
    279                     // App has specified the only BSSID to connect for this
    280                     // configuration. So only the matching ScanResult can be a candidate.
    281                     localLog("Network " + WifiNetworkSelector.toNetworkString(network)
    282                             + " has specified BSSID " + network.BSSID + ". Skip "
    283                             + scanResult.BSSID);
    284                     continue;
    285                 }
    286 
    287                 int score = calculateBssidScore(scanResult, network, currentNetwork, currentBssid,
    288                         scoreHistory);
    289 
    290                 // Set candidate ScanResult for all saved networks to ensure that users can
    291                 // override network selection. See WifiNetworkSelector#setUserConnectChoice.
    292                 // TODO(b/36067705): consider alternative designs to push filtering/selecting of
    293                 // user connect choice networks to RecommendedNetworkEvaluator.
    294                 if (score > status.getCandidateScore() || (score == status.getCandidateScore()
    295                         && status.getCandidate() != null
    296                         && scanResult.level > status.getCandidate().level)) {
    297                     mWifiConfigManager.setNetworkCandidateScanResult(
    298                             network.networkId, scanResult, score);
    299                 }
    300 
    301                 // If the network is marked to use external scores, or is an open network with
    302                 // curate saved open networks enabled, do not consider it for network selection.
    303                 if (network.useExternalScores) {
    304                     localLog("Network " + WifiNetworkSelector.toNetworkString(network)
    305                             + " has external score.");
    306                     continue;
    307                 }
    308 
    309                 if (score > highestScoreOfScanResult) {
    310                     highestScoreOfScanResult = score;
    311                     candidateIdOfScanResult = network.networkId;
    312                 }
    313             }
    314 
    315             if (connectableNetworks != null) {
    316                 connectableNetworks.add(Pair.create(scanDetail,
    317                         mWifiConfigManager.getConfiguredNetwork(candidateIdOfScanResult)));
    318             }
    319 
    320             if (highestScoreOfScanResult > highestScore
    321                     || (highestScoreOfScanResult == highestScore
    322                     && scanResultCandidate != null
    323                     && scanResult.level > scanResultCandidate.level)) {
    324                 highestScore = highestScoreOfScanResult;
    325                 scanResultCandidate = scanResult;
    326                 mWifiConfigManager.setNetworkCandidateScanResult(
    327                         candidateIdOfScanResult, scanResultCandidate, highestScore);
    328                 // Reload the network config with the updated info.
    329                 candidate = mWifiConfigManager.getConfiguredNetwork(candidateIdOfScanResult);
    330             }
    331         }
    332 
    333         if (scoreHistory.length() > 0) {
    334             localLog("\n" + scoreHistory.toString());
    335         }
    336 
    337         if (scanResultCandidate == null) {
    338             localLog("did not see any good candidates.");
    339         }
    340         return candidate;
    341     }
    342 }
    343