blob: 50e28bf289aecacf90bd9347580faef8135ee414 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.wifi;
import android.net.NetworkAgent;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.util.Log;
/**
* Calculate scores for connected wifi networks.
*/
public class WifiScoreReport {
// TODO: switch to WifiScoreReport if it doesn't break any tools
private static final String TAG = "WifiStateMachine";
// TODO: This score was hardcorded to 56. Need to understand why after finishing code refactor
private static final int STARTING_SCORE = 56;
// TODO: Understand why these values are used
private static final int MAX_BAD_LINKSPEED_COUNT = 6;
private static final int SCAN_CACHE_VISIBILITY_MS = 12000;
private static final int HOME_VISIBLE_NETWORK_MAX_COUNT = 6;
private static final int SCAN_CACHE_COUNT_PENALTY = 2;
private static final int AGGRESSIVE_HANDOVER_PENALTY = 6;
private static final int MIN_SUCCESS_COUNT = 5;
private static final int MAX_SUCCESS_COUNT_OF_STUCK_LINK = 3;
private static final int MAX_STUCK_LINK_COUNT = 5;
private static final int MIN_NUM_TICKS_AT_STATE = 1000;
private static final int USER_DISCONNECT_PENALTY = 5;
private static final int MAX_BAD_RSSI_COUNT = 7;
private static final int BAD_RSSI_COUNT_PENALTY = 2;
private static final int MAX_LOW_RSSI_COUNT = 1;
private static final double MIN_TX_RATE_FOR_WORKING_LINK = 0.3;
private static final int MIN_SUSTAINED_LINK_STUCK_COUNT = 1;
private static final int LINK_STUCK_PENALTY = 2;
private static final int BAD_LINKSPEED_PENALTY = 4;
private static final int GOOD_LINKSPEED_BONUS = 4;
private String mReport;
private int mBadLinkspeedcount;
WifiScoreReport(String report, int badLinkspeedcount) {
mReport = report;
mBadLinkspeedcount = badLinkspeedcount;
}
/**
* Method returning the String representation of the score report.
*
* @return String score report
*/
public String getReport() {
return mReport;
}
/**
* Method returning the bad link speed count at the time of the current score report.
*
* @return int bad linkspeed count
*/
public int getBadLinkspeedcount() {
return mBadLinkspeedcount;
}
/**
* Calculate wifi network score based on updated link layer stats and return a new
* WifiScoreReport object.
*
* If the score has changed from the previous value, update the WifiNetworkAgent.
* @param wifiInfo WifiInfo information about current network connection
* @param currentConfiguration WifiConfiguration current wifi config
* @param wifiConfigManager WifiConfigManager Object holding current config state
* @param networkAgent NetworkAgent to be notified of new score
* @param lastReport String most recent score report
* @param aggressiveHandover int current aggressiveHandover setting
* @return WifiScoreReport Wifi Score report
*/
public static WifiScoreReport calculateScore(WifiInfo wifiInfo,
WifiConfiguration currentConfiguration,
WifiConfigManager wifiConfigManager,
NetworkAgent networkAgent,
WifiScoreReport lastReport,
int aggressiveHandover) {
boolean debugLogging = false;
if (wifiConfigManager.mEnableVerboseLogging.get() > 0) {
debugLogging = true;
}
StringBuilder sb = new StringBuilder();
int score = STARTING_SCORE;
boolean isBadLinkspeed = (wifiInfo.is24GHz()
&& wifiInfo.getLinkSpeed() < wifiConfigManager.mBadLinkSpeed24)
|| (wifiInfo.is5GHz() && wifiInfo.getLinkSpeed()
< wifiConfigManager.mBadLinkSpeed5);
boolean isGoodLinkspeed = (wifiInfo.is24GHz()
&& wifiInfo.getLinkSpeed() >= wifiConfigManager.mGoodLinkSpeed24)
|| (wifiInfo.is5GHz() && wifiInfo.getLinkSpeed()
>= wifiConfigManager.mGoodLinkSpeed5);
int badLinkspeedcount = 0;
if (lastReport != null) {
badLinkspeedcount = lastReport.getBadLinkspeedcount();
}
if (isBadLinkspeed) {
if (badLinkspeedcount < MAX_BAD_LINKSPEED_COUNT) {
badLinkspeedcount++;
}
} else {
if (badLinkspeedcount > 0) {
badLinkspeedcount--;
}
}
if (isBadLinkspeed) sb.append(" bl(").append(badLinkspeedcount).append(")");
if (isGoodLinkspeed) sb.append(" gl");
/**
* We want to make sure that we use the 24GHz RSSI thresholds if
* there are 2.4GHz scan results
* otherwise we end up lowering the score based on 5GHz values
* which may cause a switch to LTE before roaming has a chance to try 2.4GHz
* We also might unblacklist the configuation based on 2.4GHz
* thresholds but joining 5GHz anyhow, and failing over to 2.4GHz because 5GHz is not good
*/
boolean use24Thresholds = false;
boolean homeNetworkBoost = false;
ScanDetailCache scanDetailCache =
wifiConfigManager.getScanDetailCache(currentConfiguration);
if (currentConfiguration != null && scanDetailCache != null) {
currentConfiguration.setVisibility(
scanDetailCache.getVisibility(SCAN_CACHE_VISIBILITY_MS));
if (currentConfiguration.visibility != null) {
if (currentConfiguration.visibility.rssi24 != WifiConfiguration.INVALID_RSSI
&& currentConfiguration.visibility.rssi24
>= (currentConfiguration.visibility.rssi5 - SCAN_CACHE_COUNT_PENALTY)) {
use24Thresholds = true;
}
}
if (scanDetailCache.size() <= HOME_VISIBLE_NETWORK_MAX_COUNT
&& currentConfiguration.allowedKeyManagement.cardinality() == 1
&& currentConfiguration.allowedKeyManagement
.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
// A PSK network with less than 6 known BSSIDs
// This is most likely a home network and thus we want to stick to wifi more
homeNetworkBoost = true;
}
}
if (homeNetworkBoost) sb.append(" hn");
if (use24Thresholds) sb.append(" u24");
int rssi = wifiInfo.getRssi() - AGGRESSIVE_HANDOVER_PENALTY * aggressiveHandover
+ (homeNetworkBoost ? WifiConfiguration.HOME_NETWORK_RSSI_BOOST : 0);
sb.append(String.format(" rssi=%d ag=%d", rssi, aggressiveHandover));
boolean is24GHz = use24Thresholds || wifiInfo.is24GHz();
boolean isBadRSSI = (is24GHz && rssi < wifiConfigManager.mThresholdMinimumRssi24.get())
|| (!is24GHz && rssi < wifiConfigManager.mThresholdMinimumRssi5.get());
boolean isLowRSSI = (is24GHz && rssi < wifiConfigManager.mThresholdQualifiedRssi24.get())
|| (!is24GHz
&& wifiInfo.getRssi() < wifiConfigManager.mThresholdMinimumRssi5.get());
boolean isHighRSSI = (is24GHz && rssi >= wifiConfigManager.mThresholdSaturatedRssi24.get())
|| (!is24GHz
&& wifiInfo.getRssi() >= wifiConfigManager.mThresholdSaturatedRssi5.get());
if (isBadRSSI) sb.append(" br");
if (isLowRSSI) sb.append(" lr");
if (isHighRSSI) sb.append(" hr");
int penalizedDueToUserTriggeredDisconnect = 0; // Not a user triggered disconnect
if (currentConfiguration != null
&& (wifiInfo.txSuccessRate > MIN_SUCCESS_COUNT
|| wifiInfo.rxSuccessRate > MIN_SUCCESS_COUNT)) {
if (isBadRSSI) {
currentConfiguration.numTicksAtBadRSSI++;
if (currentConfiguration.numTicksAtBadRSSI > MIN_NUM_TICKS_AT_STATE) {
// We remained associated for a compound amount of time while passing
// traffic, hence loose the corresponding user triggered disabled stats
if (currentConfiguration.numUserTriggeredWifiDisableBadRSSI > 0) {
currentConfiguration.numUserTriggeredWifiDisableBadRSSI--;
}
if (currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0) {
currentConfiguration.numUserTriggeredWifiDisableLowRSSI--;
}
if (currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) {
currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI--;
}
currentConfiguration.numTicksAtBadRSSI = 0;
}
if (wifiConfigManager.mEnableWifiCellularHandoverUserTriggeredAdjustment
&& (currentConfiguration.numUserTriggeredWifiDisableBadRSSI > 0
|| currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0
|| currentConfiguration
.numUserTriggeredWifiDisableNotHighRSSI > 0)) {
score = score - USER_DISCONNECT_PENALTY;
penalizedDueToUserTriggeredDisconnect = 1;
sb.append(" p1");
}
} else if (isLowRSSI) {
currentConfiguration.numTicksAtLowRSSI++;
if (currentConfiguration.numTicksAtLowRSSI > MIN_NUM_TICKS_AT_STATE) {
// We remained associated for a compound amount of time while passing
// traffic, hence loose the corresponding user triggered disabled stats
if (currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0) {
currentConfiguration.numUserTriggeredWifiDisableLowRSSI--;
}
if (currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) {
currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI--;
}
currentConfiguration.numTicksAtLowRSSI = 0;
}
if (wifiConfigManager.mEnableWifiCellularHandoverUserTriggeredAdjustment
&& (currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0
|| currentConfiguration
.numUserTriggeredWifiDisableNotHighRSSI > 0)) {
score = score - USER_DISCONNECT_PENALTY;
penalizedDueToUserTriggeredDisconnect = 2;
sb.append(" p2");
}
} else if (!isHighRSSI) {
currentConfiguration.numTicksAtNotHighRSSI++;
if (currentConfiguration.numTicksAtNotHighRSSI > MIN_NUM_TICKS_AT_STATE) {
// We remained associated for a compound amount of time while passing
// traffic, hence loose the corresponding user triggered disabled stats
if (currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) {
currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI--;
}
currentConfiguration.numTicksAtNotHighRSSI = 0;
}
if (wifiConfigManager.mEnableWifiCellularHandoverUserTriggeredAdjustment
&& currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) {
score = score - USER_DISCONNECT_PENALTY;
penalizedDueToUserTriggeredDisconnect = 3;
sb.append(" p3");
}
}
sb.append(String.format(" ticks %d,%d,%d", currentConfiguration.numTicksAtBadRSSI,
currentConfiguration.numTicksAtLowRSSI,
currentConfiguration.numTicksAtNotHighRSSI));
}
if (debugLogging) {
String rssiStatus = "";
if (isBadRSSI) {
rssiStatus += " badRSSI ";
} else if (isHighRSSI) {
rssiStatus += " highRSSI ";
} else if (isLowRSSI) {
rssiStatus += " lowRSSI ";
}
if (isBadLinkspeed) rssiStatus += " lowSpeed ";
Log.d(TAG, "calculateWifiScore freq=" + Integer.toString(wifiInfo.getFrequency())
+ " speed=" + Integer.toString(wifiInfo.getLinkSpeed())
+ " score=" + Integer.toString(wifiInfo.score)
+ rssiStatus
+ " -> txbadrate=" + String.format("%.2f", wifiInfo.txBadRate)
+ " txgoodrate=" + String.format("%.2f", wifiInfo.txSuccessRate)
+ " txretriesrate=" + String.format("%.2f", wifiInfo.txRetriesRate)
+ " rxrate=" + String.format("%.2f", wifiInfo.rxSuccessRate)
+ " userTriggerdPenalty" + penalizedDueToUserTriggeredDisconnect);
}
if ((wifiInfo.txBadRate >= 1) && (wifiInfo.txSuccessRate < MAX_SUCCESS_COUNT_OF_STUCK_LINK)
&& (isBadRSSI || isLowRSSI)) {
// Link is stuck
if (wifiInfo.linkStuckCount < MAX_STUCK_LINK_COUNT) {
wifiInfo.linkStuckCount += 1;
}
sb.append(String.format(" ls+=%d", wifiInfo.linkStuckCount));
if (debugLogging) {
Log.d(TAG, " bad link -> stuck count ="
+ Integer.toString(wifiInfo.linkStuckCount));
}
} else if (wifiInfo.txBadRate < MIN_TX_RATE_FOR_WORKING_LINK) {
if (wifiInfo.linkStuckCount > 0) {
wifiInfo.linkStuckCount -= 1;
}
sb.append(String.format(" ls-=%d", wifiInfo.linkStuckCount));
if (debugLogging) {
Log.d(TAG, " good link -> stuck count ="
+ Integer.toString(wifiInfo.linkStuckCount));
}
}
sb.append(String.format(" [%d", score));
if (wifiInfo.linkStuckCount > MIN_SUSTAINED_LINK_STUCK_COUNT) {
// Once link gets stuck for more than 3 seconds, start reducing the score
score = score - LINK_STUCK_PENALTY * (wifiInfo.linkStuckCount - 1);
}
sb.append(String.format(",%d", score));
if (isBadLinkspeed) {
score -= BAD_LINKSPEED_PENALTY;
if (debugLogging) {
Log.d(TAG, " isBadLinkspeed ---> count=" + badLinkspeedcount
+ " score=" + Integer.toString(score));
}
} else if ((isGoodLinkspeed) && (wifiInfo.txSuccessRate > 5)) {
score += GOOD_LINKSPEED_BONUS; // So as bad rssi alone dont kill us
}
sb.append(String.format(",%d", score));
if (isBadRSSI) {
if (wifiInfo.badRssiCount < MAX_BAD_RSSI_COUNT) {
wifiInfo.badRssiCount += 1;
}
} else if (isLowRSSI) {
wifiInfo.lowRssiCount = MAX_LOW_RSSI_COUNT; // Dont increment the lowRssi count above 1
if (wifiInfo.badRssiCount > 0) {
// Decrement bad Rssi count
wifiInfo.badRssiCount -= 1;
}
} else {
wifiInfo.badRssiCount = 0;
wifiInfo.lowRssiCount = 0;
}
score -= wifiInfo.badRssiCount * BAD_RSSI_COUNT_PENALTY + wifiInfo.lowRssiCount;
sb.append(String.format(",%d", score));
if (debugLogging) {
Log.d(TAG, " badRSSI count" + Integer.toString(wifiInfo.badRssiCount)
+ " lowRSSI count" + Integer.toString(wifiInfo.lowRssiCount)
+ " --> score " + Integer.toString(score));
}
if (isHighRSSI) {
score += 5;
if (debugLogging) Log.d(TAG, " isHighRSSI ---> score=" + Integer.toString(score));
}
sb.append(String.format(",%d]", score));
sb.append(String.format(" brc=%d lrc=%d", wifiInfo.badRssiCount, wifiInfo.lowRssiCount));
//sanitize boundaries
if (score > NetworkAgent.WIFI_BASE_SCORE) {
score = NetworkAgent.WIFI_BASE_SCORE;
}
if (score < 0) {
score = 0;
}
//report score
if (score != wifiInfo.score) {
if (debugLogging) {
Log.d(TAG, "calculateWifiScore() report new score " + Integer.toString(score));
}
wifiInfo.score = score;
if (networkAgent != null) {
networkAgent.sendNetworkScore(score);
}
}
return new WifiScoreReport(sb.toString(), badLinkspeedcount);
}
}