WifiScoreReport.java revision 6867feee7608fa47f9f7eb7042459ba55e7d0d21
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 StringBuilder sb = new StringBuilder(); 164 165 int score = STARTING_SCORE; 166 boolean isBadLinkspeed = (wifiInfo.is24GHz() 167 && wifiInfo.getLinkSpeed() < mBadLinkSpeed24) 168 || (wifiInfo.is5GHz() && wifiInfo.getLinkSpeed() 169 < mBadLinkSpeed5); 170 boolean isGoodLinkspeed = (wifiInfo.is24GHz() 171 && wifiInfo.getLinkSpeed() >= mGoodLinkSpeed24) 172 || (wifiInfo.is5GHz() && wifiInfo.getLinkSpeed() 173 >= mGoodLinkSpeed5); 174 175 int badLinkspeedcount = 0; 176 if (mReportValid) { 177 badLinkspeedcount = mBadLinkspeedcount; 178 } 179 180 if (isBadLinkspeed) { 181 if (badLinkspeedcount < MAX_BAD_LINKSPEED_COUNT) { 182 badLinkspeedcount++; 183 } 184 } else { 185 if (badLinkspeedcount > 0) { 186 badLinkspeedcount--; 187 } 188 } 189 190 if (isBadLinkspeed) sb.append(" bl(").append(badLinkspeedcount).append(")"); 191 if (isGoodLinkspeed) sb.append(" gl"); 192 193 WifiConfiguration currentConfiguration = 194 mWifiConfigManager.getWifiConfiguration(wifiInfo.getNetworkId()); 195 ScanDetailCache scanDetailCache = 196 mWifiConfigManager.getScanDetailCache(currentConfiguration); 197 /** 198 * We want to make sure that we use the 24GHz RSSI thresholds if 199 * there are 2.4GHz scan results otherwise we end up lowering the score based on 5GHz values 200 * which may cause a switch to LTE before roaming has a chance to try 2.4GHz 201 * We also might unblacklist the configuation based on 2.4GHz 202 * thresholds but joining 5GHz anyhow, and failing over to 2.4GHz because 5GHz is not good 203 */ 204 boolean use24Thresholds = false; 205 boolean homeNetworkBoost = false; 206 if (currentConfiguration != null && scanDetailCache != null) { 207 currentConfiguration.setVisibility( 208 scanDetailCache.getVisibility(SCAN_CACHE_VISIBILITY_MS)); 209 if (currentConfiguration.visibility != null) { 210 if (currentConfiguration.visibility.rssi24 != WifiConfiguration.INVALID_RSSI 211 && currentConfiguration.visibility.rssi24 212 >= (currentConfiguration.visibility.rssi5 - SCAN_CACHE_COUNT_PENALTY)) { 213 use24Thresholds = true; 214 } 215 } 216 if (scanDetailCache.size() <= HOME_VISIBLE_NETWORK_MAX_COUNT 217 && currentConfiguration.allowedKeyManagement.cardinality() == 1 218 && currentConfiguration.allowedKeyManagement 219 .get(WifiConfiguration.KeyMgmt.WPA_PSK)) { 220 // A PSK network with less than 6 known BSSIDs 221 // This is most likely a home network and thus we want to stick to wifi more 222 homeNetworkBoost = true; 223 } 224 } 225 if (homeNetworkBoost) sb.append(" hn"); 226 if (use24Thresholds) sb.append(" u24"); 227 228 int rssi = wifiInfo.getRssi() - AGGRESSIVE_HANDOVER_PENALTY * aggressiveHandover 229 + (homeNetworkBoost ? WifiConfiguration.HOME_NETWORK_RSSI_BOOST : 0); 230 sb.append(String.format(" rssi=%d ag=%d", rssi, aggressiveHandover)); 231 232 boolean is24GHz = use24Thresholds || wifiInfo.is24GHz(); 233 234 boolean isBadRSSI = (is24GHz && rssi < mThresholdMinimumRssi24) 235 || (!is24GHz && rssi < mThresholdMinimumRssi5); 236 boolean isLowRSSI = (is24GHz && rssi < mThresholdQualifiedRssi24) 237 || (!is24GHz 238 && wifiInfo.getRssi() < mThresholdMinimumRssi5); 239 boolean isHighRSSI = (is24GHz && rssi >= mThresholdSaturatedRssi24) 240 || (!is24GHz 241 && wifiInfo.getRssi() >= mThresholdSaturatedRssi5); 242 243 if (isBadRSSI) sb.append(" br"); 244 if (isLowRSSI) sb.append(" lr"); 245 if (isHighRSSI) sb.append(" hr"); 246 247 int penalizedDueToUserTriggeredDisconnect = 0; // Not a user triggered disconnect 248 if (currentConfiguration != null 249 && (wifiInfo.txSuccessRate > MIN_SUCCESS_COUNT 250 || wifiInfo.rxSuccessRate > MIN_SUCCESS_COUNT)) { 251 if (isBadRSSI) { 252 currentConfiguration.numTicksAtBadRSSI++; 253 if (currentConfiguration.numTicksAtBadRSSI > MIN_NUM_TICKS_AT_STATE) { 254 // We remained associated for a compound amount of time while passing 255 // traffic, hence loose the corresponding user triggered disabled stats 256 if (currentConfiguration.numUserTriggeredWifiDisableBadRSSI > 0) { 257 currentConfiguration.numUserTriggeredWifiDisableBadRSSI--; 258 } 259 if (currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0) { 260 currentConfiguration.numUserTriggeredWifiDisableLowRSSI--; 261 } 262 if (currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) { 263 currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI--; 264 } 265 currentConfiguration.numTicksAtBadRSSI = 0; 266 } 267 if (mEnableWifiCellularHandoverUserTriggeredAdjustment 268 && (currentConfiguration.numUserTriggeredWifiDisableBadRSSI > 0 269 || currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0 270 || currentConfiguration 271 .numUserTriggeredWifiDisableNotHighRSSI > 0)) { 272 score = score - USER_DISCONNECT_PENALTY; 273 penalizedDueToUserTriggeredDisconnect = 1; 274 sb.append(" p1"); 275 } 276 } else if (isLowRSSI) { 277 currentConfiguration.numTicksAtLowRSSI++; 278 if (currentConfiguration.numTicksAtLowRSSI > MIN_NUM_TICKS_AT_STATE) { 279 // We remained associated for a compound amount of time while passing 280 // traffic, hence loose the corresponding user triggered disabled stats 281 if (currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0) { 282 currentConfiguration.numUserTriggeredWifiDisableLowRSSI--; 283 } 284 if (currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) { 285 currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI--; 286 } 287 currentConfiguration.numTicksAtLowRSSI = 0; 288 } 289 if (mEnableWifiCellularHandoverUserTriggeredAdjustment 290 && (currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0 291 || currentConfiguration 292 .numUserTriggeredWifiDisableNotHighRSSI > 0)) { 293 score = score - USER_DISCONNECT_PENALTY; 294 penalizedDueToUserTriggeredDisconnect = 2; 295 sb.append(" p2"); 296 } 297 } else if (!isHighRSSI) { 298 currentConfiguration.numTicksAtNotHighRSSI++; 299 if (currentConfiguration.numTicksAtNotHighRSSI > MIN_NUM_TICKS_AT_STATE) { 300 // We remained associated for a compound amount of time while passing 301 // traffic, hence loose the corresponding user triggered disabled stats 302 if (currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) { 303 currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI--; 304 } 305 currentConfiguration.numTicksAtNotHighRSSI = 0; 306 } 307 if (mEnableWifiCellularHandoverUserTriggeredAdjustment 308 && currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) { 309 score = score - USER_DISCONNECT_PENALTY; 310 penalizedDueToUserTriggeredDisconnect = 3; 311 sb.append(" p3"); 312 } 313 } 314 sb.append(String.format(" ticks %d,%d,%d", currentConfiguration.numTicksAtBadRSSI, 315 currentConfiguration.numTicksAtLowRSSI, 316 currentConfiguration.numTicksAtNotHighRSSI)); 317 } 318 319 if (mVerboseLoggingEnabled) { 320 String rssiStatus = ""; 321 if (isBadRSSI) { 322 rssiStatus += " badRSSI "; 323 } else if (isHighRSSI) { 324 rssiStatus += " highRSSI "; 325 } else if (isLowRSSI) { 326 rssiStatus += " lowRSSI "; 327 } 328 if (isBadLinkspeed) rssiStatus += " lowSpeed "; 329 Log.d(TAG, "calculateWifiScore freq=" + Integer.toString(wifiInfo.getFrequency()) 330 + " speed=" + Integer.toString(wifiInfo.getLinkSpeed()) 331 + " score=" + Integer.toString(wifiInfo.score) 332 + rssiStatus 333 + " -> txbadrate=" + String.format("%.2f", wifiInfo.txBadRate) 334 + " txgoodrate=" + String.format("%.2f", wifiInfo.txSuccessRate) 335 + " txretriesrate=" + String.format("%.2f", wifiInfo.txRetriesRate) 336 + " rxrate=" + String.format("%.2f", wifiInfo.rxSuccessRate) 337 + " userTriggerdPenalty" + penalizedDueToUserTriggeredDisconnect); 338 } 339 340 if ((wifiInfo.txBadRate >= 1) 341 && (wifiInfo.txSuccessRate < MAX_SUCCESS_COUNT_OF_STUCK_LINK) 342 && (isBadRSSI || isLowRSSI)) { 343 // Link is stuck 344 if (wifiInfo.linkStuckCount < MAX_STUCK_LINK_COUNT) { 345 wifiInfo.linkStuckCount += 1; 346 } 347 sb.append(String.format(" ls+=%d", wifiInfo.linkStuckCount)); 348 if (mVerboseLoggingEnabled) { 349 Log.d(TAG, " bad link -> stuck count =" 350 + Integer.toString(wifiInfo.linkStuckCount)); 351 } 352 } else if (wifiInfo.txBadRate < MIN_TX_RATE_FOR_WORKING_LINK) { 353 if (wifiInfo.linkStuckCount > 0) { 354 wifiInfo.linkStuckCount -= 1; 355 } 356 sb.append(String.format(" ls-=%d", wifiInfo.linkStuckCount)); 357 if (mVerboseLoggingEnabled) { 358 Log.d(TAG, " good link -> stuck count =" 359 + Integer.toString(wifiInfo.linkStuckCount)); 360 } 361 } 362 363 sb.append(String.format(" [%d", score)); 364 365 if (wifiInfo.linkStuckCount > MIN_SUSTAINED_LINK_STUCK_COUNT) { 366 // Once link gets stuck for more than 3 seconds, start reducing the score 367 score = score - LINK_STUCK_PENALTY * (wifiInfo.linkStuckCount - 1); 368 } 369 sb.append(String.format(",%d", score)); 370 371 if (isBadLinkspeed) { 372 score -= BAD_LINKSPEED_PENALTY; 373 if (mVerboseLoggingEnabled) { 374 Log.d(TAG, " isBadLinkspeed ---> count=" + mBadLinkspeedcount 375 + " score=" + Integer.toString(score)); 376 } 377 } else if ((isGoodLinkspeed) && (wifiInfo.txSuccessRate > 5)) { 378 score += GOOD_LINKSPEED_BONUS; // So as bad rssi alone dont kill us 379 } 380 sb.append(String.format(",%d", score)); 381 382 if (isBadRSSI) { 383 if (wifiInfo.badRssiCount < MAX_BAD_RSSI_COUNT) { 384 wifiInfo.badRssiCount += 1; 385 } 386 } else if (isLowRSSI) { 387 wifiInfo.lowRssiCount = MAX_LOW_RSSI_COUNT; // Dont increment the lowRssi count above 1 388 if (wifiInfo.badRssiCount > 0) { 389 // Decrement bad Rssi count 390 wifiInfo.badRssiCount -= 1; 391 } 392 } else { 393 wifiInfo.badRssiCount = 0; 394 wifiInfo.lowRssiCount = 0; 395 } 396 397 score -= wifiInfo.badRssiCount * BAD_RSSI_COUNT_PENALTY + wifiInfo.lowRssiCount; 398 sb.append(String.format(",%d", score)); 399 400 if (mVerboseLoggingEnabled) { 401 Log.d(TAG, " badRSSI count" + Integer.toString(wifiInfo.badRssiCount) 402 + " lowRSSI count" + Integer.toString(wifiInfo.lowRssiCount) 403 + " --> score " + Integer.toString(score)); 404 } 405 406 if (isHighRSSI) { 407 score += 5; 408 if (mVerboseLoggingEnabled) Log.d(TAG, " isHighRSSI ---> score=" + score); 409 } 410 sb.append(String.format(",%d]", score)); 411 412 sb.append(String.format(" brc=%d lrc=%d", wifiInfo.badRssiCount, wifiInfo.lowRssiCount)); 413 414 //sanitize boundaries 415 if (score > NetworkAgent.WIFI_BASE_SCORE) { 416 score = NetworkAgent.WIFI_BASE_SCORE; 417 } 418 if (score < 0) { 419 score = 0; 420 } 421 422 //report score 423 if (score != wifiInfo.score) { 424 if (mVerboseLoggingEnabled) { 425 Log.d(TAG, "calculateWifiScore() report new score " + Integer.toString(score)); 426 } 427 wifiInfo.score = score; 428 if (networkAgent != null) { 429 networkAgent.sendNetworkScore(score); 430 } 431 } 432 433 mBadLinkspeedcount = badLinkspeedcount; 434 mReport = sb.toString(); 435 mReportValid = true; 436 } 437} 438