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.net.NetworkAgent;
     20 import android.net.wifi.WifiConfiguration;
     21 import android.net.wifi.WifiInfo;
     22 import android.util.Log;
     23 
     24 
     25 /**
     26 * Calculate scores for connected wifi networks.
     27 */
     28 public class WifiScoreReport {
     29     // TODO: switch to WifiScoreReport if it doesn't break any tools
     30     private static final String TAG = "WifiStateMachine";
     31 
     32     // TODO: This score was hardcorded to 56.  Need to understand why after finishing code refactor
     33     private static final int STARTING_SCORE = 56;
     34 
     35     // TODO: Understand why these values are used
     36     private static final int MAX_BAD_LINKSPEED_COUNT = 6;
     37     private static final int SCAN_CACHE_VISIBILITY_MS = 12000;
     38     private static final int HOME_VISIBLE_NETWORK_MAX_COUNT = 6;
     39     private static final int SCAN_CACHE_COUNT_PENALTY = 2;
     40     private static final int AGGRESSIVE_HANDOVER_PENALTY = 6;
     41     private static final int MIN_SUCCESS_COUNT = 5;
     42     private static final int MAX_SUCCESS_COUNT_OF_STUCK_LINK = 3;
     43     private static final int MAX_STUCK_LINK_COUNT = 5;
     44     private static final int MIN_NUM_TICKS_AT_STATE = 1000;
     45     private static final int USER_DISCONNECT_PENALTY = 5;
     46     private static final int MAX_BAD_RSSI_COUNT = 7;
     47     private static final int BAD_RSSI_COUNT_PENALTY = 2;
     48     private static final int MAX_LOW_RSSI_COUNT = 1;
     49     private static final double MIN_TX_RATE_FOR_WORKING_LINK = 0.3;
     50     private static final int MIN_SUSTAINED_LINK_STUCK_COUNT = 1;
     51     private static final int LINK_STUCK_PENALTY = 2;
     52     private static final int BAD_LINKSPEED_PENALTY = 4;
     53     private static final int GOOD_LINKSPEED_BONUS = 4;
     54 
     55 
     56     private String mReport;
     57     private int mBadLinkspeedcount;
     58 
     59     WifiScoreReport(String report, int badLinkspeedcount) {
     60         mReport = report;
     61         mBadLinkspeedcount = badLinkspeedcount;
     62     }
     63 
     64     /**
     65      *  Method returning the String representation of the score report.
     66      *
     67      *  @return String score report
     68      */
     69     public String getReport() {
     70         return mReport;
     71     }
     72 
     73     /**
     74      *  Method returning the bad link speed count at the time of the current score report.
     75      *
     76      *  @return int bad linkspeed count
     77      */
     78     public int getBadLinkspeedcount() {
     79         return mBadLinkspeedcount;
     80     }
     81 
     82     /**
     83      * Calculate wifi network score based on updated link layer stats and return a new
     84      * WifiScoreReport object.
     85      *
     86      * If the score has changed from the previous value, update the WifiNetworkAgent.
     87      * @param wifiInfo WifiInfo information about current network connection
     88      * @param currentConfiguration WifiConfiguration current wifi config
     89      * @param wifiConfigManager WifiConfigManager Object holding current config state
     90      * @param networkAgent NetworkAgent to be notified of new score
     91      * @param lastReport String most recent score report
     92      * @param aggressiveHandover int current aggressiveHandover setting
     93      * @return WifiScoreReport Wifi Score report
     94      */
     95     public static WifiScoreReport calculateScore(WifiInfo wifiInfo,
     96                                                  WifiConfiguration currentConfiguration,
     97                                                  WifiConfigManager wifiConfigManager,
     98                                                  NetworkAgent networkAgent,
     99                                                  WifiScoreReport lastReport,
    100                                                  int aggressiveHandover) {
    101         boolean debugLogging = false;
    102         if (wifiConfigManager.mEnableVerboseLogging.get() > 0) {
    103             debugLogging = true;
    104         }
    105 
    106         StringBuilder sb = new StringBuilder();
    107 
    108         int score = STARTING_SCORE;
    109         boolean isBadLinkspeed = (wifiInfo.is24GHz()
    110                 && wifiInfo.getLinkSpeed() < wifiConfigManager.mBadLinkSpeed24)
    111                 || (wifiInfo.is5GHz() && wifiInfo.getLinkSpeed()
    112                 < wifiConfigManager.mBadLinkSpeed5);
    113         boolean isGoodLinkspeed = (wifiInfo.is24GHz()
    114                 && wifiInfo.getLinkSpeed() >= wifiConfigManager.mGoodLinkSpeed24)
    115                 || (wifiInfo.is5GHz() && wifiInfo.getLinkSpeed()
    116                 >= wifiConfigManager.mGoodLinkSpeed5);
    117 
    118         int badLinkspeedcount = 0;
    119         if (lastReport != null) {
    120             badLinkspeedcount = lastReport.getBadLinkspeedcount();
    121         }
    122 
    123         if (isBadLinkspeed) {
    124             if (badLinkspeedcount < MAX_BAD_LINKSPEED_COUNT) {
    125                 badLinkspeedcount++;
    126             }
    127         } else {
    128             if (badLinkspeedcount > 0) {
    129                 badLinkspeedcount--;
    130             }
    131         }
    132 
    133         if (isBadLinkspeed) sb.append(" bl(").append(badLinkspeedcount).append(")");
    134         if (isGoodLinkspeed) sb.append(" gl");
    135 
    136         /**
    137          * We want to make sure that we use the 24GHz RSSI thresholds if
    138          * there are 2.4GHz scan results
    139          * otherwise we end up lowering the score based on 5GHz values
    140          * which may cause a switch to LTE before roaming has a chance to try 2.4GHz
    141          * We also might unblacklist the configuation based on 2.4GHz
    142          * thresholds but joining 5GHz anyhow, and failing over to 2.4GHz because 5GHz is not good
    143          */
    144         boolean use24Thresholds = false;
    145         boolean homeNetworkBoost = false;
    146         ScanDetailCache scanDetailCache =
    147                 wifiConfigManager.getScanDetailCache(currentConfiguration);
    148         if (currentConfiguration != null && scanDetailCache != null) {
    149             currentConfiguration.setVisibility(
    150                     scanDetailCache.getVisibility(SCAN_CACHE_VISIBILITY_MS));
    151             if (currentConfiguration.visibility != null) {
    152                 if (currentConfiguration.visibility.rssi24 != WifiConfiguration.INVALID_RSSI
    153                         && currentConfiguration.visibility.rssi24
    154                         >= (currentConfiguration.visibility.rssi5 - SCAN_CACHE_COUNT_PENALTY)) {
    155                     use24Thresholds = true;
    156                 }
    157             }
    158             if (scanDetailCache.size() <= HOME_VISIBLE_NETWORK_MAX_COUNT
    159                     && currentConfiguration.allowedKeyManagement.cardinality() == 1
    160                     && currentConfiguration.allowedKeyManagement
    161                             .get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
    162                 // A PSK network with less than 6 known BSSIDs
    163                 // This is most likely a home network and thus we want to stick to wifi more
    164                 homeNetworkBoost = true;
    165             }
    166         }
    167         if (homeNetworkBoost) sb.append(" hn");
    168         if (use24Thresholds) sb.append(" u24");
    169 
    170         int rssi = wifiInfo.getRssi() - AGGRESSIVE_HANDOVER_PENALTY * aggressiveHandover
    171                 + (homeNetworkBoost ? WifiConfiguration.HOME_NETWORK_RSSI_BOOST : 0);
    172         sb.append(String.format(" rssi=%d ag=%d", rssi, aggressiveHandover));
    173 
    174         boolean is24GHz = use24Thresholds || wifiInfo.is24GHz();
    175 
    176         boolean isBadRSSI = (is24GHz && rssi < wifiConfigManager.mThresholdMinimumRssi24.get())
    177                 || (!is24GHz && rssi < wifiConfigManager.mThresholdMinimumRssi5.get());
    178         boolean isLowRSSI = (is24GHz && rssi < wifiConfigManager.mThresholdQualifiedRssi24.get())
    179                 || (!is24GHz
    180                         && wifiInfo.getRssi() < wifiConfigManager.mThresholdMinimumRssi5.get());
    181         boolean isHighRSSI = (is24GHz && rssi >= wifiConfigManager.mThresholdSaturatedRssi24.get())
    182                 || (!is24GHz
    183                         && wifiInfo.getRssi() >= wifiConfigManager.mThresholdSaturatedRssi5.get());
    184 
    185         if (isBadRSSI) sb.append(" br");
    186         if (isLowRSSI) sb.append(" lr");
    187         if (isHighRSSI) sb.append(" hr");
    188 
    189         int penalizedDueToUserTriggeredDisconnect = 0;        // Not a user triggered disconnect
    190         if (currentConfiguration != null
    191                 && (wifiInfo.txSuccessRate > MIN_SUCCESS_COUNT
    192                         || wifiInfo.rxSuccessRate > MIN_SUCCESS_COUNT)) {
    193             if (isBadRSSI) {
    194                 currentConfiguration.numTicksAtBadRSSI++;
    195                 if (currentConfiguration.numTicksAtBadRSSI > MIN_NUM_TICKS_AT_STATE) {
    196                     // We remained associated for a compound amount of time while passing
    197                     // traffic, hence loose the corresponding user triggered disabled stats
    198                     if (currentConfiguration.numUserTriggeredWifiDisableBadRSSI > 0) {
    199                         currentConfiguration.numUserTriggeredWifiDisableBadRSSI--;
    200                     }
    201                     if (currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0) {
    202                         currentConfiguration.numUserTriggeredWifiDisableLowRSSI--;
    203                     }
    204                     if (currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) {
    205                         currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI--;
    206                     }
    207                     currentConfiguration.numTicksAtBadRSSI = 0;
    208                 }
    209                 if (wifiConfigManager.mEnableWifiCellularHandoverUserTriggeredAdjustment
    210                         && (currentConfiguration.numUserTriggeredWifiDisableBadRSSI > 0
    211                                 || currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0
    212                                 || currentConfiguration
    213                                         .numUserTriggeredWifiDisableNotHighRSSI > 0)) {
    214                     score = score - USER_DISCONNECT_PENALTY;
    215                     penalizedDueToUserTriggeredDisconnect = 1;
    216                     sb.append(" p1");
    217                 }
    218             } else if (isLowRSSI) {
    219                 currentConfiguration.numTicksAtLowRSSI++;
    220                 if (currentConfiguration.numTicksAtLowRSSI > MIN_NUM_TICKS_AT_STATE) {
    221                     // We remained associated for a compound amount of time while passing
    222                     // traffic, hence loose the corresponding user triggered disabled stats
    223                     if (currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0) {
    224                         currentConfiguration.numUserTriggeredWifiDisableLowRSSI--;
    225                     }
    226                     if (currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) {
    227                         currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI--;
    228                     }
    229                     currentConfiguration.numTicksAtLowRSSI = 0;
    230                 }
    231                 if (wifiConfigManager.mEnableWifiCellularHandoverUserTriggeredAdjustment
    232                         && (currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0
    233                                 || currentConfiguration
    234                                         .numUserTriggeredWifiDisableNotHighRSSI > 0)) {
    235                     score = score - USER_DISCONNECT_PENALTY;
    236                     penalizedDueToUserTriggeredDisconnect = 2;
    237                     sb.append(" p2");
    238                 }
    239             } else if (!isHighRSSI) {
    240                 currentConfiguration.numTicksAtNotHighRSSI++;
    241                 if (currentConfiguration.numTicksAtNotHighRSSI > MIN_NUM_TICKS_AT_STATE) {
    242                     // We remained associated for a compound amount of time while passing
    243                     // traffic, hence loose the corresponding user triggered disabled stats
    244                     if (currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) {
    245                         currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI--;
    246                     }
    247                     currentConfiguration.numTicksAtNotHighRSSI = 0;
    248                 }
    249                 if (wifiConfigManager.mEnableWifiCellularHandoverUserTriggeredAdjustment
    250                         && currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) {
    251                     score = score - USER_DISCONNECT_PENALTY;
    252                     penalizedDueToUserTriggeredDisconnect = 3;
    253                     sb.append(" p3");
    254                 }
    255             }
    256             sb.append(String.format(" ticks %d,%d,%d", currentConfiguration.numTicksAtBadRSSI,
    257                     currentConfiguration.numTicksAtLowRSSI,
    258                     currentConfiguration.numTicksAtNotHighRSSI));
    259         }
    260 
    261         if (debugLogging) {
    262             String rssiStatus = "";
    263             if (isBadRSSI) {
    264                 rssiStatus += " badRSSI ";
    265             } else if (isHighRSSI) {
    266                 rssiStatus += " highRSSI ";
    267             } else if (isLowRSSI) {
    268                 rssiStatus += " lowRSSI ";
    269             }
    270             if (isBadLinkspeed) rssiStatus += " lowSpeed ";
    271             Log.d(TAG, "calculateWifiScore freq=" + Integer.toString(wifiInfo.getFrequency())
    272                     + " speed=" + Integer.toString(wifiInfo.getLinkSpeed())
    273                     + " score=" + Integer.toString(wifiInfo.score)
    274                     + rssiStatus
    275                     + " -> txbadrate=" + String.format("%.2f", wifiInfo.txBadRate)
    276                     + " txgoodrate=" + String.format("%.2f", wifiInfo.txSuccessRate)
    277                     + " txretriesrate=" + String.format("%.2f", wifiInfo.txRetriesRate)
    278                     + " rxrate=" + String.format("%.2f", wifiInfo.rxSuccessRate)
    279                     + " userTriggerdPenalty" + penalizedDueToUserTriggeredDisconnect);
    280         }
    281 
    282         if ((wifiInfo.txBadRate >= 1) && (wifiInfo.txSuccessRate < MAX_SUCCESS_COUNT_OF_STUCK_LINK)
    283                 && (isBadRSSI || isLowRSSI)) {
    284             // Link is stuck
    285             if (wifiInfo.linkStuckCount < MAX_STUCK_LINK_COUNT) {
    286                 wifiInfo.linkStuckCount += 1;
    287             }
    288             sb.append(String.format(" ls+=%d", wifiInfo.linkStuckCount));
    289             if (debugLogging) {
    290                 Log.d(TAG, " bad link -> stuck count ="
    291                         + Integer.toString(wifiInfo.linkStuckCount));
    292             }
    293         } else if (wifiInfo.txBadRate < MIN_TX_RATE_FOR_WORKING_LINK) {
    294             if (wifiInfo.linkStuckCount > 0) {
    295                 wifiInfo.linkStuckCount -= 1;
    296             }
    297             sb.append(String.format(" ls-=%d", wifiInfo.linkStuckCount));
    298             if (debugLogging) {
    299                 Log.d(TAG, " good link -> stuck count ="
    300                         + Integer.toString(wifiInfo.linkStuckCount));
    301             }
    302         }
    303 
    304         sb.append(String.format(" [%d", score));
    305 
    306         if (wifiInfo.linkStuckCount > MIN_SUSTAINED_LINK_STUCK_COUNT) {
    307             // Once link gets stuck for more than 3 seconds, start reducing the score
    308             score = score - LINK_STUCK_PENALTY * (wifiInfo.linkStuckCount - 1);
    309         }
    310         sb.append(String.format(",%d", score));
    311 
    312         if (isBadLinkspeed) {
    313             score -= BAD_LINKSPEED_PENALTY;
    314             if (debugLogging) {
    315                 Log.d(TAG, " isBadLinkspeed   ---> count=" + badLinkspeedcount
    316                         + " score=" + Integer.toString(score));
    317             }
    318         } else if ((isGoodLinkspeed) && (wifiInfo.txSuccessRate > 5)) {
    319             score += GOOD_LINKSPEED_BONUS; // So as bad rssi alone dont kill us
    320         }
    321         sb.append(String.format(",%d", score));
    322 
    323         if (isBadRSSI) {
    324             if (wifiInfo.badRssiCount < MAX_BAD_RSSI_COUNT) {
    325                 wifiInfo.badRssiCount += 1;
    326             }
    327         } else if (isLowRSSI) {
    328             wifiInfo.lowRssiCount = MAX_LOW_RSSI_COUNT; // Dont increment the lowRssi count above 1
    329             if (wifiInfo.badRssiCount > 0) {
    330                 // Decrement bad Rssi count
    331                 wifiInfo.badRssiCount -= 1;
    332             }
    333         } else {
    334             wifiInfo.badRssiCount = 0;
    335             wifiInfo.lowRssiCount = 0;
    336         }
    337 
    338         score -= wifiInfo.badRssiCount * BAD_RSSI_COUNT_PENALTY + wifiInfo.lowRssiCount;
    339         sb.append(String.format(",%d", score));
    340 
    341         if (debugLogging) {
    342             Log.d(TAG, " badRSSI count" + Integer.toString(wifiInfo.badRssiCount)
    343                     + " lowRSSI count" + Integer.toString(wifiInfo.lowRssiCount)
    344                     + " --> score " + Integer.toString(score));
    345         }
    346 
    347         if (isHighRSSI) {
    348             score += 5;
    349             if (debugLogging) Log.d(TAG, " isHighRSSI       ---> score=" + Integer.toString(score));
    350         }
    351         sb.append(String.format(",%d]", score));
    352 
    353         sb.append(String.format(" brc=%d lrc=%d", wifiInfo.badRssiCount, wifiInfo.lowRssiCount));
    354 
    355         //sanitize boundaries
    356         if (score > NetworkAgent.WIFI_BASE_SCORE) {
    357             score = NetworkAgent.WIFI_BASE_SCORE;
    358         }
    359         if (score < 0) {
    360             score = 0;
    361         }
    362 
    363         //report score
    364         if (score != wifiInfo.score) {
    365             if (debugLogging) {
    366                 Log.d(TAG, "calculateWifiScore() report new score " + Integer.toString(score));
    367             }
    368             wifiInfo.score = score;
    369             if (networkAgent != null) {
    370                 networkAgent.sendNetworkScore(score);
    371             }
    372         }
    373         return new WifiScoreReport(sb.toString(), badLinkspeedcount);
    374     }
    375 }
    376