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.NetworkAgent;
     21 import android.net.wifi.WifiConfiguration;
     22 import android.net.wifi.WifiInfo;
     23 import android.util.Log;
     24 
     25 import com.android.internal.R;
     26 
     27 /**
     28  * Class used to calculate scores for connected wifi networks and report it to the associated
     29  * network agent.
     30 */
     31 public class WifiScoreReport {
     32     private static final String TAG = "WifiScoreReport";
     33 
     34     private static final int STARTING_SCORE = 56;
     35 
     36     private static final int SCAN_CACHE_VISIBILITY_MS = 12000;
     37     private static final int HOME_VISIBLE_NETWORK_MAX_COUNT = 6;
     38     private static final int SCAN_CACHE_COUNT_PENALTY = 2;
     39     private static final int AGGRESSIVE_HANDOVER_PENALTY = 6;
     40     private static final int MAX_SUCCESS_RATE_OF_STUCK_LINK = 3; // proportional to packets per sec
     41     private static final int MAX_STUCK_LINK_COUNT = 5;
     42     private static final int MAX_BAD_RSSI_COUNT = 7;
     43     private static final int BAD_RSSI_COUNT_PENALTY = 2;
     44     private static final int MAX_LOW_RSSI_COUNT = 1;
     45     private static final double MIN_TX_FAILURE_RATE_FOR_WORKING_LINK = 0.3;
     46     private static final int MIN_SUSTAINED_LINK_STUCK_COUNT = 1;
     47     private static final int LINK_STUCK_PENALTY = 2;
     48     private static final int BAD_LINKSPEED_PENALTY = 4;
     49     private static final int GOOD_LINKSPEED_BONUS = 4;
     50 
     51     // Device configs. The values are examples.
     52     private final int mThresholdMinimumRssi5;      // -82
     53     private final int mThresholdQualifiedRssi5;    // -70
     54     private final int mThresholdSaturatedRssi5;    // -57
     55     private final int mThresholdMinimumRssi24;     // -85
     56     private final int mThresholdQualifiedRssi24;   // -73
     57     private final int mThresholdSaturatedRssi24;   // -60
     58     private final int mBadLinkSpeed24;             //  6 Mbps
     59     private final int mBadLinkSpeed5;              // 12 Mbps
     60     private final int mGoodLinkSpeed24;            // 24 Mbps
     61     private final int mGoodLinkSpeed5;             // 36 Mbps
     62 
     63     private final WifiConfigManager mWifiConfigManager;
     64     private boolean mVerboseLoggingEnabled = false;
     65 
     66     // Cache of the last score report.
     67     private String mReport;
     68     private boolean mReportValid = false;
     69 
     70     // State set by updateScoringState
     71     private boolean mMultiBandScanResults;
     72     private boolean mIsHomeNetwork;
     73 
     74     WifiScoreReport(Context context, WifiConfigManager wifiConfigManager) {
     75         // Fetch all the device configs.
     76         mThresholdMinimumRssi5 = context.getResources().getInteger(
     77                 R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz);
     78         mThresholdQualifiedRssi5 = context.getResources().getInteger(
     79                 R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz);
     80         mThresholdSaturatedRssi5 = context.getResources().getInteger(
     81                 R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz);
     82         mThresholdMinimumRssi24 = context.getResources().getInteger(
     83                 R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz);
     84         mThresholdQualifiedRssi24 = context.getResources().getInteger(
     85                 R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz);
     86         mThresholdSaturatedRssi24 = context.getResources().getInteger(
     87                 R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz);
     88         mBadLinkSpeed24 = context.getResources().getInteger(
     89                 R.integer.config_wifi_framework_wifi_score_bad_link_speed_24);
     90         mBadLinkSpeed5 = context.getResources().getInteger(
     91                 R.integer.config_wifi_framework_wifi_score_bad_link_speed_5);
     92         mGoodLinkSpeed24 = context.getResources().getInteger(
     93                 R.integer.config_wifi_framework_wifi_score_good_link_speed_24);
     94         mGoodLinkSpeed5 = context.getResources().getInteger(
     95                 R.integer.config_wifi_framework_wifi_score_good_link_speed_5);
     96 
     97         mWifiConfigManager = wifiConfigManager;
     98     }
     99 
    100     /**
    101      * Method returning the String representation of the last score report.
    102      *
    103      *  @return String score report
    104      */
    105     public String getLastReport() {
    106         return mReport;
    107     }
    108 
    109     /**
    110      * Reset the last calculated score.
    111      */
    112     public void reset() {
    113         mReport = "";
    114         mReportValid = false;
    115     }
    116 
    117     /**
    118      * Checks if the last report data is valid or not. This will be cleared when {@link #reset()} is
    119      * invoked.
    120      *
    121      * @return true if valid, false otherwise.
    122      */
    123     public boolean isLastReportValid() {
    124         return mReportValid;
    125     }
    126 
    127     /**
    128      * Enable/Disable verbose logging in score report generation.
    129      */
    130     public void enableVerboseLogging(boolean enable) {
    131         mVerboseLoggingEnabled = enable;
    132     }
    133 
    134     /**
    135      * Calculate wifi network score based on updated link layer stats and send the score to
    136      * the provided network agent.
    137      *
    138      * If the score has changed from the previous value, update the WifiNetworkAgent.
    139      *
    140      * Called periodically (POLL_RSSI_INTERVAL_MSECS) about every 3 seconds.
    141      *
    142      * @param wifiInfo WifiInfo instance pointing to the currently connected network.
    143      * @param networkAgent NetworkAgent to be notified of new score.
    144      * @param aggressiveHandover int current aggressiveHandover setting.
    145      * @param wifiMetrics for reporting our scores.
    146      */
    147     public void calculateAndReportScore(WifiInfo wifiInfo, NetworkAgent networkAgent,
    148                                         int aggressiveHandover, WifiMetrics wifiMetrics) {
    149         int score;
    150 
    151         updateScoringState(wifiInfo, aggressiveHandover);
    152         score = calculateScore(wifiInfo, aggressiveHandover);
    153 
    154         //sanitize boundaries
    155         if (score > NetworkAgent.WIFI_BASE_SCORE) {
    156             score = NetworkAgent.WIFI_BASE_SCORE;
    157         }
    158         if (score < 0) {
    159             score = 0;
    160         }
    161 
    162         //report score
    163         if (score != wifiInfo.score) {
    164             if (mVerboseLoggingEnabled) {
    165                 Log.d(TAG, " report new wifi score " + score);
    166             }
    167             wifiInfo.score = score;
    168             if (networkAgent != null) {
    169                 networkAgent.sendNetworkScore(score);
    170             }
    171         }
    172 
    173         mReport = String.format(" score=%d", score);
    174         mReportValid = true;
    175         wifiMetrics.incrementWifiScoreCount(score);
    176     }
    177 
    178     /**
    179      * Updates the state.
    180      */
    181     private void updateScoringState(WifiInfo wifiInfo, int aggressiveHandover) {
    182         mMultiBandScanResults = multiBandScanResults(wifiInfo);
    183         mIsHomeNetwork = isHomeNetwork(wifiInfo);
    184 
    185         int rssiThreshBad = mThresholdMinimumRssi24;
    186         int rssiThreshLow = mThresholdQualifiedRssi24;
    187 
    188         if (wifiInfo.is5GHz() && !mMultiBandScanResults) {
    189             rssiThreshBad = mThresholdMinimumRssi5;
    190             rssiThreshLow = mThresholdQualifiedRssi5;
    191         }
    192 
    193         int rssi =  wifiInfo.getRssi();
    194         if (aggressiveHandover != 0) {
    195             rssi -= AGGRESSIVE_HANDOVER_PENALTY * aggressiveHandover;
    196         }
    197         if (mIsHomeNetwork) {
    198             rssi += WifiConfiguration.HOME_NETWORK_RSSI_BOOST;
    199         }
    200 
    201         if ((wifiInfo.txBadRate >= 1)
    202                 && (wifiInfo.txSuccessRate < MAX_SUCCESS_RATE_OF_STUCK_LINK)
    203                 && rssi < rssiThreshLow) {
    204             // Link is stuck
    205             if (wifiInfo.linkStuckCount < MAX_STUCK_LINK_COUNT) {
    206                 wifiInfo.linkStuckCount += 1;
    207             }
    208         } else if (wifiInfo.txBadRate < MIN_TX_FAILURE_RATE_FOR_WORKING_LINK) {
    209             if (wifiInfo.linkStuckCount > 0) {
    210                 wifiInfo.linkStuckCount -= 1;
    211             }
    212         }
    213 
    214         if (rssi < rssiThreshBad) {
    215             if (wifiInfo.badRssiCount < MAX_BAD_RSSI_COUNT) {
    216                 wifiInfo.badRssiCount += 1;
    217             }
    218         } else if (rssi < rssiThreshLow) {
    219             wifiInfo.lowRssiCount = MAX_LOW_RSSI_COUNT; // Dont increment the lowRssi count above 1
    220             if (wifiInfo.badRssiCount > 0) {
    221                 // Decrement bad Rssi count
    222                 wifiInfo.badRssiCount -= 1;
    223             }
    224         } else {
    225             wifiInfo.badRssiCount = 0;
    226             wifiInfo.lowRssiCount = 0;
    227         }
    228 
    229     }
    230 
    231     /**
    232      * Calculates the score, without all the cruft.
    233      */
    234     private int calculateScore(WifiInfo wifiInfo, int aggressiveHandover) {
    235         int score = STARTING_SCORE;
    236 
    237         int rssiThreshSaturated = mThresholdSaturatedRssi24;
    238         int linkspeedThreshBad = mBadLinkSpeed24;
    239         int linkspeedThreshGood = mGoodLinkSpeed24;
    240 
    241         if (wifiInfo.is5GHz()) {
    242             if (!mMultiBandScanResults) {
    243                 rssiThreshSaturated = mThresholdSaturatedRssi5;
    244             }
    245             linkspeedThreshBad = mBadLinkSpeed5;
    246             linkspeedThreshGood = mGoodLinkSpeed5;
    247         }
    248 
    249         int rssi =  wifiInfo.getRssi();
    250         if (aggressiveHandover != 0) {
    251             rssi -= AGGRESSIVE_HANDOVER_PENALTY * aggressiveHandover;
    252         }
    253         if (mIsHomeNetwork) {
    254             rssi += WifiConfiguration.HOME_NETWORK_RSSI_BOOST;
    255         }
    256 
    257         int linkSpeed = wifiInfo.getLinkSpeed();
    258 
    259         if (wifiInfo.linkStuckCount > MIN_SUSTAINED_LINK_STUCK_COUNT) {
    260             // Once link gets stuck for more than 3 seconds, start reducing the score
    261             score = score - LINK_STUCK_PENALTY * (wifiInfo.linkStuckCount - 1);
    262         }
    263 
    264         if (linkSpeed < linkspeedThreshBad) {
    265             score -= BAD_LINKSPEED_PENALTY;
    266         } else if ((linkSpeed >= linkspeedThreshGood) && (wifiInfo.txSuccessRate > 5)) {
    267             score += GOOD_LINKSPEED_BONUS; // So as bad rssi alone doesn't kill us
    268         }
    269 
    270         score -= wifiInfo.badRssiCount * BAD_RSSI_COUNT_PENALTY + wifiInfo.lowRssiCount;
    271 
    272         if (rssi >= rssiThreshSaturated) score += 5;
    273 
    274         if (score > NetworkAgent.WIFI_BASE_SCORE) score = NetworkAgent.WIFI_BASE_SCORE;
    275         if (score < 0) score = 0;
    276 
    277         return score;
    278     }
    279 
    280     /**
    281      * Determines if we can see both 2.4GHz and 5GHz for current config
    282      */
    283     private boolean multiBandScanResults(WifiInfo wifiInfo) {
    284         WifiConfiguration currentConfiguration =
    285                 mWifiConfigManager.getConfiguredNetwork(wifiInfo.getNetworkId());
    286         if (currentConfiguration == null) return false;
    287         ScanDetailCache scanDetailCache =
    288                 mWifiConfigManager.getScanDetailCacheForNetwork(wifiInfo.getNetworkId());
    289         if (scanDetailCache == null) return false;
    290         // Nasty that we change state here...
    291         currentConfiguration.setVisibility(scanDetailCache.getVisibility(SCAN_CACHE_VISIBILITY_MS));
    292         if (currentConfiguration.visibility == null) return false;
    293         if (currentConfiguration.visibility.rssi24 == WifiConfiguration.INVALID_RSSI) return false;
    294         if (currentConfiguration.visibility.rssi5 == WifiConfiguration.INVALID_RSSI) return false;
    295         // N.B. this does not do exactly what is claimed!
    296         if (currentConfiguration.visibility.rssi24
    297                 >= currentConfiguration.visibility.rssi5 - SCAN_CACHE_COUNT_PENALTY) {
    298             return true;
    299         }
    300         return false;
    301     }
    302 
    303     /**
    304      * Decides whether the current network is a "home" network
    305      */
    306     private boolean isHomeNetwork(WifiInfo wifiInfo) {
    307         WifiConfiguration currentConfiguration =
    308                 mWifiConfigManager.getConfiguredNetwork(wifiInfo.getNetworkId());
    309         if (currentConfiguration == null) return false;
    310         // This seems like it will only return true for really old routers!
    311         if (currentConfiguration.allowedKeyManagement.cardinality() != 1) return false;
    312         if (!currentConfiguration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
    313             return false;
    314         }
    315         ScanDetailCache scanDetailCache =
    316                 mWifiConfigManager.getScanDetailCacheForNetwork(wifiInfo.getNetworkId());
    317         if (scanDetailCache == null) return false;
    318         if (scanDetailCache.size() <= HOME_VISIBLE_NETWORK_MAX_COUNT) {
    319             return true;
    320         }
    321         return false;
    322     }
    323 }
    324