WifiScoreReport.java revision 4569ebc2277f35b9bd1baa98194f963388e0c4ca
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 // TODO: switch to WifiScoreReport if it doesn't break any tools 34 private static final String TAG = "WifiStateMachine"; 35 36 // TODO: This score was hardcorded to 56. Need to understand why after finishing code refactor 37 private static final int STARTING_SCORE = 56; 38 39 // TODO: Understand why these values are used 40 private static final int MAX_BAD_LINKSPEED_COUNT = 6; 41 private static final int SCAN_CACHE_VISIBILITY_MS = 12000; 42 private static final int HOME_VISIBLE_NETWORK_MAX_COUNT = 6; 43 private static final int SCAN_CACHE_COUNT_PENALTY = 2; 44 private static final int AGGRESSIVE_HANDOVER_PENALTY = 6; 45 private static final int MIN_SUCCESS_COUNT = 5; 46 private static final int MAX_SUCCESS_COUNT_OF_STUCK_LINK = 3; 47 private static final int MAX_STUCK_LINK_COUNT = 5; 48 private static final int MIN_NUM_TICKS_AT_STATE = 1000; 49 private static final int USER_DISCONNECT_PENALTY = 5; 50 private static final int MAX_BAD_RSSI_COUNT = 7; 51 private static final int BAD_RSSI_COUNT_PENALTY = 2; 52 private static final int MAX_LOW_RSSI_COUNT = 1; 53 private static final double MIN_TX_RATE_FOR_WORKING_LINK = 0.3; 54 private static final int MIN_SUSTAINED_LINK_STUCK_COUNT = 1; 55 private static final int LINK_STUCK_PENALTY = 2; 56 private static final int BAD_LINKSPEED_PENALTY = 4; 57 private static final int GOOD_LINKSPEED_BONUS = 4; 58 59 // Device configs. 60 private final int mThresholdMinimumRssi5; 61 private final int mThresholdQualifiedRssi5; 62 private final int mThresholdSaturatedRssi5; 63 private final int mThresholdMinimumRssi24; 64 private final int mThresholdQualifiedRssi24; 65 private final int mThresholdSaturatedRssi24; 66 private final int mBadLinkSpeed24; 67 private final int mBadLinkSpeed5; 68 private final int mGoodLinkSpeed24; 69 private final int mGoodLinkSpeed5; 70 private final boolean mEnableWifiCellularHandoverUserTriggeredAdjustment; 71 72 private final WifiConfigManager mWifiConfigManager; 73 private boolean mVerboseLoggingEnabled = false; 74 75 // Cache of the last score report. 76 private String mReport; 77 private int mBadLinkspeedcount = 0; 78 private boolean mReportValid = false; 79 80 WifiScoreReport(Context context, WifiConfigManager wifiConfigManager) { 81 // Fetch all the device configs. 82 mThresholdMinimumRssi5 = context.getResources().getInteger( 83 R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz); 84 mThresholdQualifiedRssi5 = context.getResources().getInteger( 85 R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz); 86 mThresholdSaturatedRssi5 = context.getResources().getInteger( 87 R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz); 88 mThresholdMinimumRssi24 = context.getResources().getInteger( 89 R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz); 90 mThresholdQualifiedRssi24 = context.getResources().getInteger( 91 R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz); 92 mThresholdSaturatedRssi24 = context.getResources().getInteger( 93 R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz); 94 mBadLinkSpeed24 = context.getResources().getInteger( 95 R.integer.config_wifi_framework_wifi_score_bad_link_speed_24); 96 mBadLinkSpeed5 = context.getResources().getInteger( 97 R.integer.config_wifi_framework_wifi_score_bad_link_speed_5); 98 mGoodLinkSpeed24 = context.getResources().getInteger( 99 R.integer.config_wifi_framework_wifi_score_good_link_speed_24); 100 mGoodLinkSpeed5 = context.getResources().getInteger( 101 R.integer.config_wifi_framework_wifi_score_good_link_speed_5); 102 mEnableWifiCellularHandoverUserTriggeredAdjustment = context.getResources().getBoolean( 103 R.bool.config_wifi_framework_cellular_handover_enable_user_triggered_adjustment); 104 105 mWifiConfigManager = wifiConfigManager; 106 } 107 108 /** 109 * Method returning the String representation of the last score report. 110 * 111 * @return String score report 112 */ 113 public String getLastReport() { 114 return mReport; 115 } 116 117 /** 118 * Method returning the bad link speed count at the time of the last score report. 119 * 120 * @return int bad linkspeed count 121 */ 122 public int getLastBadLinkspeedcount() { 123 return mBadLinkspeedcount; 124 } 125 126 /** 127 * Reset the last calculated score. 128 */ 129 public void reset() { 130 mBadLinkspeedcount = 0; 131 mReport = ""; 132 mReportValid = false; 133 } 134 135 /** 136 * Checks if the last report data is valid or not. This will be cleared when {@link #reset()} is 137 * invoked. 138 * 139 * @return true if valid, false otherwise. 140 */ 141 public boolean isLastReportValid() { 142 return mReportValid; 143 } 144 145 /** 146 * Enable/Disable verbose logging in score report generation. 147 */ 148 public void enableVerboseLogging(boolean enable) { 149 mVerboseLoggingEnabled = enable; 150 } 151 152 /** 153 * Calculate wifi network score based on updated link layer stats and send the score to provided 154 * network agent. 155 * 156 * If the score has changed from the previous value, update the WifiNetworkAgent. 157 * @param wifiInfo WifiInfo instance pointing to the currently connected network. 158 * @param networkAgent NetworkAgent to be notified of new score. 159 * @param aggressiveHandover int current aggressiveHandover setting. 160 */ 161 public void calculateAndReportScore( 162 WifiInfo wifiInfo, NetworkAgent networkAgent, int aggressiveHandover, 163 WifiMetrics wifiMetrics) { 164 StringBuilder sb = new StringBuilder(); 165 166 int score = STARTING_SCORE; 167 boolean isBadLinkspeed = (wifiInfo.is24GHz() 168 && wifiInfo.getLinkSpeed() < mBadLinkSpeed24) 169 || (wifiInfo.is5GHz() && wifiInfo.getLinkSpeed() 170 < mBadLinkSpeed5); 171 boolean isGoodLinkspeed = (wifiInfo.is24GHz() 172 && wifiInfo.getLinkSpeed() >= mGoodLinkSpeed24) 173 || (wifiInfo.is5GHz() && wifiInfo.getLinkSpeed() 174 >= mGoodLinkSpeed5); 175 176 int badLinkspeedcount = 0; 177 if (mReportValid) { 178 badLinkspeedcount = mBadLinkspeedcount; 179 } 180 181 if (isBadLinkspeed) { 182 if (badLinkspeedcount < MAX_BAD_LINKSPEED_COUNT) { 183 badLinkspeedcount++; 184 } 185 } else { 186 if (badLinkspeedcount > 0) { 187 badLinkspeedcount--; 188 } 189 } 190 191 if (isBadLinkspeed) sb.append(" bl(").append(badLinkspeedcount).append(")"); 192 if (isGoodLinkspeed) sb.append(" gl"); 193 194 WifiConfiguration currentConfiguration = 195 mWifiConfigManager.getWifiConfiguration(wifiInfo.getNetworkId()); 196 ScanDetailCache scanDetailCache = 197 mWifiConfigManager.getScanDetailCache(currentConfiguration); 198 /** 199 * We want to make sure that we use the 24GHz RSSI thresholds if 200 * there are 2.4GHz scan results otherwise we end up lowering the score based on 5GHz values 201 * which may cause a switch to LTE before roaming has a chance to try 2.4GHz 202 * We also might unblacklist the configuation based on 2.4GHz 203 * thresholds but joining 5GHz anyhow, and failing over to 2.4GHz because 5GHz is not good 204 */ 205 boolean use24Thresholds = false; 206 boolean homeNetworkBoost = false; 207 if (currentConfiguration != null && scanDetailCache != null) { 208 currentConfiguration.setVisibility( 209 scanDetailCache.getVisibility(SCAN_CACHE_VISIBILITY_MS)); 210 if (currentConfiguration.visibility != null) { 211 if (currentConfiguration.visibility.rssi24 != WifiConfiguration.INVALID_RSSI 212 && currentConfiguration.visibility.rssi24 213 >= (currentConfiguration.visibility.rssi5 - SCAN_CACHE_COUNT_PENALTY)) { 214 use24Thresholds = true; 215 } 216 } 217 if (scanDetailCache.size() <= HOME_VISIBLE_NETWORK_MAX_COUNT 218 && currentConfiguration.allowedKeyManagement.cardinality() == 1 219 && currentConfiguration.allowedKeyManagement 220 .get(WifiConfiguration.KeyMgmt.WPA_PSK)) { 221 // A PSK network with less than 6 known BSSIDs 222 // This is most likely a home network and thus we want to stick to wifi more 223 homeNetworkBoost = true; 224 } 225 } 226 if (homeNetworkBoost) sb.append(" hn"); 227 if (use24Thresholds) sb.append(" u24"); 228 229 int rssi = wifiInfo.getRssi() - AGGRESSIVE_HANDOVER_PENALTY * aggressiveHandover 230 + (homeNetworkBoost ? WifiConfiguration.HOME_NETWORK_RSSI_BOOST : 0); 231 sb.append(String.format(" rssi=%d ag=%d", rssi, aggressiveHandover)); 232 233 boolean is24GHz = use24Thresholds || wifiInfo.is24GHz(); 234 235 boolean isBadRSSI = (is24GHz && rssi < mThresholdMinimumRssi24) 236 || (!is24GHz && rssi < mThresholdMinimumRssi5); 237 boolean isLowRSSI = (is24GHz && rssi < mThresholdQualifiedRssi24) 238 || (!is24GHz 239 && wifiInfo.getRssi() < mThresholdMinimumRssi5); 240 boolean isHighRSSI = (is24GHz && rssi >= mThresholdSaturatedRssi24) 241 || (!is24GHz 242 && wifiInfo.getRssi() >= mThresholdSaturatedRssi5); 243 244 if (isBadRSSI) sb.append(" br"); 245 if (isLowRSSI) sb.append(" lr"); 246 if (isHighRSSI) sb.append(" hr"); 247 248 int penalizedDueToUserTriggeredDisconnect = 0; // Not a user triggered disconnect 249 if (currentConfiguration != null 250 && (wifiInfo.txSuccessRate > MIN_SUCCESS_COUNT 251 || wifiInfo.rxSuccessRate > MIN_SUCCESS_COUNT)) { 252 if (isBadRSSI) { 253 currentConfiguration.numTicksAtBadRSSI++; 254 if (currentConfiguration.numTicksAtBadRSSI > MIN_NUM_TICKS_AT_STATE) { 255 // We remained associated for a compound amount of time while passing 256 // traffic, hence loose the corresponding user triggered disabled stats 257 if (currentConfiguration.numUserTriggeredWifiDisableBadRSSI > 0) { 258 currentConfiguration.numUserTriggeredWifiDisableBadRSSI--; 259 } 260 if (currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0) { 261 currentConfiguration.numUserTriggeredWifiDisableLowRSSI--; 262 } 263 if (currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) { 264 currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI--; 265 } 266 currentConfiguration.numTicksAtBadRSSI = 0; 267 } 268 if (mEnableWifiCellularHandoverUserTriggeredAdjustment 269 && (currentConfiguration.numUserTriggeredWifiDisableBadRSSI > 0 270 || currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0 271 || currentConfiguration 272 .numUserTriggeredWifiDisableNotHighRSSI > 0)) { 273 score = score - USER_DISCONNECT_PENALTY; 274 penalizedDueToUserTriggeredDisconnect = 1; 275 sb.append(" p1"); 276 } 277 } else if (isLowRSSI) { 278 currentConfiguration.numTicksAtLowRSSI++; 279 if (currentConfiguration.numTicksAtLowRSSI > MIN_NUM_TICKS_AT_STATE) { 280 // We remained associated for a compound amount of time while passing 281 // traffic, hence loose the corresponding user triggered disabled stats 282 if (currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0) { 283 currentConfiguration.numUserTriggeredWifiDisableLowRSSI--; 284 } 285 if (currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) { 286 currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI--; 287 } 288 currentConfiguration.numTicksAtLowRSSI = 0; 289 } 290 if (mEnableWifiCellularHandoverUserTriggeredAdjustment 291 && (currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0 292 || currentConfiguration 293 .numUserTriggeredWifiDisableNotHighRSSI > 0)) { 294 score = score - USER_DISCONNECT_PENALTY; 295 penalizedDueToUserTriggeredDisconnect = 2; 296 sb.append(" p2"); 297 } 298 } else if (!isHighRSSI) { 299 currentConfiguration.numTicksAtNotHighRSSI++; 300 if (currentConfiguration.numTicksAtNotHighRSSI > MIN_NUM_TICKS_AT_STATE) { 301 // We remained associated for a compound amount of time while passing 302 // traffic, hence loose the corresponding user triggered disabled stats 303 if (currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) { 304 currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI--; 305 } 306 currentConfiguration.numTicksAtNotHighRSSI = 0; 307 } 308 if (mEnableWifiCellularHandoverUserTriggeredAdjustment 309 && currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) { 310 score = score - USER_DISCONNECT_PENALTY; 311 penalizedDueToUserTriggeredDisconnect = 3; 312 sb.append(" p3"); 313 } 314 } 315 sb.append(String.format(" ticks %d,%d,%d", currentConfiguration.numTicksAtBadRSSI, 316 currentConfiguration.numTicksAtLowRSSI, 317 currentConfiguration.numTicksAtNotHighRSSI)); 318 } 319 320 if (mVerboseLoggingEnabled) { 321 String rssiStatus = ""; 322 if (isBadRSSI) { 323 rssiStatus += " badRSSI "; 324 } else if (isHighRSSI) { 325 rssiStatus += " highRSSI "; 326 } else if (isLowRSSI) { 327 rssiStatus += " lowRSSI "; 328 } 329 if (isBadLinkspeed) rssiStatus += " lowSpeed "; 330 Log.d(TAG, "calculateWifiScore freq=" + Integer.toString(wifiInfo.getFrequency()) 331 + " speed=" + Integer.toString(wifiInfo.getLinkSpeed()) 332 + " score=" + Integer.toString(wifiInfo.score) 333 + rssiStatus 334 + " -> txbadrate=" + String.format("%.2f", wifiInfo.txBadRate) 335 + " txgoodrate=" + String.format("%.2f", wifiInfo.txSuccessRate) 336 + " txretriesrate=" + String.format("%.2f", wifiInfo.txRetriesRate) 337 + " rxrate=" + String.format("%.2f", wifiInfo.rxSuccessRate) 338 + " userTriggerdPenalty" + penalizedDueToUserTriggeredDisconnect); 339 } 340 341 if ((wifiInfo.txBadRate >= 1) 342 && (wifiInfo.txSuccessRate < MAX_SUCCESS_COUNT_OF_STUCK_LINK) 343 && (isBadRSSI || isLowRSSI)) { 344 // Link is stuck 345 if (wifiInfo.linkStuckCount < MAX_STUCK_LINK_COUNT) { 346 wifiInfo.linkStuckCount += 1; 347 } 348 sb.append(String.format(" ls+=%d", wifiInfo.linkStuckCount)); 349 if (mVerboseLoggingEnabled) { 350 Log.d(TAG, " bad link -> stuck count =" 351 + Integer.toString(wifiInfo.linkStuckCount)); 352 } 353 } else if (wifiInfo.txBadRate < MIN_TX_RATE_FOR_WORKING_LINK) { 354 if (wifiInfo.linkStuckCount > 0) { 355 wifiInfo.linkStuckCount -= 1; 356 } 357 sb.append(String.format(" ls-=%d", wifiInfo.linkStuckCount)); 358 if (mVerboseLoggingEnabled) { 359 Log.d(TAG, " good link -> stuck count =" 360 + Integer.toString(wifiInfo.linkStuckCount)); 361 } 362 } 363 364 sb.append(String.format(" [%d", score)); 365 366 if (wifiInfo.linkStuckCount > MIN_SUSTAINED_LINK_STUCK_COUNT) { 367 // Once link gets stuck for more than 3 seconds, start reducing the score 368 score = score - LINK_STUCK_PENALTY * (wifiInfo.linkStuckCount - 1); 369 } 370 sb.append(String.format(",%d", score)); 371 372 if (isBadLinkspeed) { 373 score -= BAD_LINKSPEED_PENALTY; 374 if (mVerboseLoggingEnabled) { 375 Log.d(TAG, " isBadLinkspeed ---> count=" + mBadLinkspeedcount 376 + " score=" + Integer.toString(score)); 377 } 378 } else if ((isGoodLinkspeed) && (wifiInfo.txSuccessRate > 5)) { 379 score += GOOD_LINKSPEED_BONUS; // So as bad rssi alone dont kill us 380 } 381 sb.append(String.format(",%d", score)); 382 383 if (isBadRSSI) { 384 if (wifiInfo.badRssiCount < MAX_BAD_RSSI_COUNT) { 385 wifiInfo.badRssiCount += 1; 386 } 387 } else if (isLowRSSI) { 388 wifiInfo.lowRssiCount = MAX_LOW_RSSI_COUNT; // Dont increment the lowRssi count above 1 389 if (wifiInfo.badRssiCount > 0) { 390 // Decrement bad Rssi count 391 wifiInfo.badRssiCount -= 1; 392 } 393 } else { 394 wifiInfo.badRssiCount = 0; 395 wifiInfo.lowRssiCount = 0; 396 } 397 398 score -= wifiInfo.badRssiCount * BAD_RSSI_COUNT_PENALTY + wifiInfo.lowRssiCount; 399 sb.append(String.format(",%d", score)); 400 401 if (mVerboseLoggingEnabled) { 402 Log.d(TAG, " badRSSI count" + Integer.toString(wifiInfo.badRssiCount) 403 + " lowRSSI count" + Integer.toString(wifiInfo.lowRssiCount) 404 + " --> score " + Integer.toString(score)); 405 } 406 407 if (isHighRSSI) { 408 score += 5; 409 if (mVerboseLoggingEnabled) Log.d(TAG, " isHighRSSI ---> score=" + score); 410 } 411 sb.append(String.format(",%d]", score)); 412 413 sb.append(String.format(" brc=%d lrc=%d", wifiInfo.badRssiCount, wifiInfo.lowRssiCount)); 414 415 //sanitize boundaries 416 if (score > NetworkAgent.WIFI_BASE_SCORE) { 417 score = NetworkAgent.WIFI_BASE_SCORE; 418 } 419 if (score < 0) { 420 score = 0; 421 } 422 423 //report score 424 if (score != wifiInfo.score) { 425 if (mVerboseLoggingEnabled) { 426 Log.d(TAG, "calculateWifiScore() report new score " + Integer.toString(score)); 427 } 428 wifiInfo.score = score; 429 if (networkAgent != null) { 430 networkAgent.sendNetworkScore(score); 431 } 432 } 433 mBadLinkspeedcount = badLinkspeedcount; 434 mReport = sb.toString(); 435 mReportValid = true; 436 wifiMetrics.incrementWifiScoreCount(score); 437 } 438} 439