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 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 */ 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 */ 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 */ 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