| /* |
| * 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); |
| } |
| } |