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 WifiScoreReport(String report, int badLinkspeedcount)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 */ getReport()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 */ getBadLinkspeedcount()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 */ calculateScore(WifiInfo wifiInfo, WifiConfiguration currentConfiguration, WifiConfigManager wifiConfigManager, NetworkAgent networkAgent, WifiScoreReport lastReport, int aggressiveHandover)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