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