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