WifiScoreReport.java revision d350a59494f8b54d46e253fffc48a49ac78fc069
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    private static final String TAG = "WifiScoreReport";
34
35    // TODO: This score was hardcorded to 56.  Need to understand why after finishing code refactor
36    private static final int STARTING_SCORE = 56;
37
38    // TODO: Understand why these values are used
39    private static final int MAX_BAD_LINKSPEED_COUNT = 6;
40    private static final int SCAN_CACHE_VISIBILITY_MS = 12000;
41    private static final int HOME_VISIBLE_NETWORK_MAX_COUNT = 6;
42    private static final int SCAN_CACHE_COUNT_PENALTY = 2;
43    private static final int AGGRESSIVE_HANDOVER_PENALTY = 6;
44    private static final int MIN_SUCCESS_COUNT = 5;
45    private static final int MAX_SUCCESS_COUNT_OF_STUCK_LINK = 3;
46    private static final int MAX_STUCK_LINK_COUNT = 5;
47    private static final int MIN_NUM_TICKS_AT_STATE = 1000;
48    private static final int USER_DISCONNECT_PENALTY = 5;
49    private static final int MAX_BAD_RSSI_COUNT = 7;
50    private static final int BAD_RSSI_COUNT_PENALTY = 2;
51    private static final int MAX_LOW_RSSI_COUNT = 1;
52    private static final double MIN_TX_RATE_FOR_WORKING_LINK = 0.3;
53    private static final int MIN_SUSTAINED_LINK_STUCK_COUNT = 1;
54    private static final int LINK_STUCK_PENALTY = 2;
55    private static final int BAD_LINKSPEED_PENALTY = 4;
56    private static final int GOOD_LINKSPEED_BONUS = 4;
57
58    // Device configs. The values are examples.
59    private final int mThresholdMinimumRssi5;      // -82
60    private final int mThresholdQualifiedRssi5;    // -70
61    private final int mThresholdSaturatedRssi5;    // -57
62    private final int mThresholdMinimumRssi24;     // -85
63    private final int mThresholdQualifiedRssi24;   // -73
64    private final int mThresholdSaturatedRssi24;   // -60
65    private final int mBadLinkSpeed24;             //  6 Mbps
66    private final int mBadLinkSpeed5;              // 12 Mbps
67    private final int mGoodLinkSpeed24;            // 24 Mbps
68    private final int mGoodLinkSpeed5;             // 36 Mbps
69    private final boolean mEnableWifiCellularHandoverUserTriggeredAdjustment; // true
70
71    private final WifiConfigManager mWifiConfigManager;
72    private boolean mVerboseLoggingEnabled = false;
73
74    // Cache of the last score report.
75    private String mReport;
76    private boolean mReportValid = false;
77
78    WifiScoreReport(Context context, WifiConfigManager wifiConfigManager) {
79        // Fetch all the device configs.
80        mThresholdMinimumRssi5 = context.getResources().getInteger(
81                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz);
82        mThresholdQualifiedRssi5 = context.getResources().getInteger(
83                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz);
84        mThresholdSaturatedRssi5 = context.getResources().getInteger(
85                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz);
86        mThresholdMinimumRssi24 = context.getResources().getInteger(
87                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz);
88        mThresholdQualifiedRssi24 = context.getResources().getInteger(
89                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz);
90        mThresholdSaturatedRssi24 = context.getResources().getInteger(
91                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz);
92        mBadLinkSpeed24 = context.getResources().getInteger(
93                R.integer.config_wifi_framework_wifi_score_bad_link_speed_24);
94        mBadLinkSpeed5 = context.getResources().getInteger(
95                R.integer.config_wifi_framework_wifi_score_bad_link_speed_5);
96        mGoodLinkSpeed24 = context.getResources().getInteger(
97                R.integer.config_wifi_framework_wifi_score_good_link_speed_24);
98        mGoodLinkSpeed5 = context.getResources().getInteger(
99                R.integer.config_wifi_framework_wifi_score_good_link_speed_5);
100        mEnableWifiCellularHandoverUserTriggeredAdjustment = context.getResources().getBoolean(
101                R.bool.config_wifi_framework_cellular_handover_enable_user_triggered_adjustment);
102
103        mWifiConfigManager = wifiConfigManager;
104    }
105
106    /**
107     * Method returning the String representation of the last score report.
108     *
109     *  @return String score report
110     */
111    public String getLastReport() {
112        return mReport;
113    }
114
115    /**
116     * Reset the last calculated score.
117     */
118    public void reset() {
119        mReport = "";
120        mReportValid = false;
121    }
122
123    /**
124     * Checks if the last report data is valid or not. This will be cleared when {@link #reset()} is
125     * invoked.
126     *
127     * @return true if valid, false otherwise.
128     */
129    public boolean isLastReportValid() {
130        return mReportValid;
131    }
132
133    /**
134     * Enable/Disable verbose logging in score report generation.
135     */
136    public void enableVerboseLogging(boolean enable) {
137        mVerboseLoggingEnabled = enable;
138    }
139
140    /**
141     * Calculate wifi network score based on updated link layer stats and send the score to
142     * the provided network agent.
143     *
144     * If the score has changed from the previous value, update the WifiNetworkAgent.
145     *
146     * Called periodically (POLL_RSSI_INTERVAL_MSECS) about every 3 seconds.
147     *
148     * @param wifiInfo WifiInfo instance pointing to the currently connected network.
149     * @param networkAgent NetworkAgent to be notified of new score.
150     * @param aggressiveHandover int current aggressiveHandover setting.
151     * @param wifiMetrics for reporting our scores.
152     */
153    public void calculateAndReportScore(
154            WifiInfo wifiInfo, NetworkAgent networkAgent, int aggressiveHandover,
155            WifiMetrics wifiMetrics) {
156        WifiInfo checkWifiInfo = new WifiInfo(wifiInfo);
157
158        int score = STARTING_SCORE;
159
160        updateScoringState(wifiInfo, aggressiveHandover);
161        score = calculateScore(wifiInfo, aggressiveHandover);
162
163        //sanitize boundaries
164        if (score > NetworkAgent.WIFI_BASE_SCORE) {
165            score = NetworkAgent.WIFI_BASE_SCORE;
166        }
167        if (score < 0) {
168            score = 0;
169        }
170
171        //report score
172        if (score != wifiInfo.score) {
173            if (mVerboseLoggingEnabled) {
174                Log.d(TAG, " report new wifi score " + score);
175            }
176            wifiInfo.score = score;
177            if (networkAgent != null) {
178                networkAgent.sendNetworkScore(score);
179            }
180        }
181
182        mReport = String.format(" score=%d", score);
183        mReportValid = true;
184        wifiMetrics.incrementWifiScoreCount(score);
185    }
186
187    /**
188     * Updates the state.
189     */
190    public void updateScoringState(WifiInfo wifiInfo, int aggressiveHandover) {
191        int rssiThreshBad = mThresholdMinimumRssi24;
192        int rssiThreshLow = mThresholdQualifiedRssi24;
193
194        if (wifiInfo.is5GHz()) {
195            if (!multiBandScanResults(wifiInfo)) {
196                rssiThreshBad = mThresholdMinimumRssi5;
197                rssiThreshLow = mThresholdQualifiedRssi5;
198            }
199        }
200
201        int rssi =  wifiInfo.getRssi();
202        if (aggressiveHandover != 0) {
203            rssi -= AGGRESSIVE_HANDOVER_PENALTY * aggressiveHandover;
204        }
205        if (isHomeNetwork(wifiInfo)) {
206            rssi += WifiConfiguration.HOME_NETWORK_RSSI_BOOST;
207        }
208
209        if ((wifiInfo.txBadRate >= 1)
210                && (wifiInfo.txSuccessRate < MAX_SUCCESS_COUNT_OF_STUCK_LINK)
211                && rssi < rssiThreshLow) {
212            // Link is stuck
213            if (wifiInfo.linkStuckCount < MAX_STUCK_LINK_COUNT) {
214                wifiInfo.linkStuckCount += 1;
215            }
216        } else if (wifiInfo.txBadRate < MIN_TX_RATE_FOR_WORKING_LINK) {
217            if (wifiInfo.linkStuckCount > 0) {
218                wifiInfo.linkStuckCount -= 1;
219            }
220        }
221
222        if (rssi < rssiThreshBad) {
223            if (wifiInfo.badRssiCount < MAX_BAD_RSSI_COUNT) {
224                wifiInfo.badRssiCount += 1;
225            }
226        } else if (rssi < rssiThreshLow) {
227            wifiInfo.lowRssiCount = MAX_LOW_RSSI_COUNT; // Dont increment the lowRssi count above 1
228            if (wifiInfo.badRssiCount > 0) {
229                // Decrement bad Rssi count
230                wifiInfo.badRssiCount -= 1;
231            }
232        } else {
233            wifiInfo.badRssiCount = 0;
234            wifiInfo.lowRssiCount = 0;
235        }
236
237    }
238
239    /**
240     * Calculates the score, without all the cruft.
241     */
242    public int calculateScore(WifiInfo wifiInfo, int aggressiveHandover) {
243        int score = STARTING_SCORE;
244
245        int rssiThreshSaturated = mThresholdSaturatedRssi24;
246        int linkspeedThreshBad = mBadLinkSpeed24;
247        int linkspeedThreshGood = mGoodLinkSpeed24;
248
249        if (wifiInfo.is24GHz() != !(wifiInfo.is5GHz())) {
250            throw new AssertionError("What is happening here?");
251        }
252
253        if (wifiInfo.is5GHz()) {
254            if (!multiBandScanResults(wifiInfo)) {
255                rssiThreshSaturated = mThresholdSaturatedRssi5;
256            }
257            linkspeedThreshBad = mBadLinkSpeed5;
258            linkspeedThreshGood = mGoodLinkSpeed5;
259        }
260
261        int rssi =  wifiInfo.getRssi();
262        if (aggressiveHandover != 0) {
263            rssi -= AGGRESSIVE_HANDOVER_PENALTY * aggressiveHandover;
264        }
265        if (isHomeNetwork(wifiInfo)) {
266            rssi += WifiConfiguration.HOME_NETWORK_RSSI_BOOST;
267        }
268
269        int linkSpeed = wifiInfo.getLinkSpeed();
270
271        // Updates to wifiInfo.linkStuckCount skipped here
272
273        if (wifiInfo.linkStuckCount > MIN_SUSTAINED_LINK_STUCK_COUNT) {
274            // Once link gets stuck for more than 3 seconds, start reducing the score
275            score = score - LINK_STUCK_PENALTY * (wifiInfo.linkStuckCount - 1);
276        }
277
278        if (linkSpeed < linkspeedThreshBad) {
279            score -= BAD_LINKSPEED_PENALTY;
280        } else if ((linkSpeed >= linkspeedThreshGood) && (wifiInfo.txSuccessRate > 5)) {
281            score += GOOD_LINKSPEED_BONUS; // So as bad rssi alone doesn't kill us
282        }
283
284        // Updates to wifiInfo.badRssiCount skipped here
285
286        score -= wifiInfo.badRssiCount * BAD_RSSI_COUNT_PENALTY + wifiInfo.lowRssiCount;
287
288        if (rssi >= rssiThreshSaturated) score += 5;
289
290        if (score > NetworkAgent.WIFI_BASE_SCORE) score = NetworkAgent.WIFI_BASE_SCORE;
291        if (score < 0) score = 0;
292
293        return score;
294    }
295
296    /**
297     * Determines if we can see both 2.4GHz and 5GHz for current config
298     */
299    private boolean multiBandScanResults(WifiInfo wifiInfo) {
300        WifiConfiguration currentConfiguration =
301                mWifiConfigManager.getConfiguredNetwork(wifiInfo.getNetworkId());
302        if (currentConfiguration == null) return false;
303        ScanDetailCache scanDetailCache =
304                mWifiConfigManager.getScanDetailCacheForNetwork(wifiInfo.getNetworkId());
305        if (scanDetailCache == null) return false;
306        // TODO(b/36364366): Nasty that we change state here...
307        currentConfiguration.setVisibility(scanDetailCache.getVisibility(SCAN_CACHE_VISIBILITY_MS));
308        if (currentConfiguration.visibility == null) return false;
309        if (currentConfiguration.visibility.rssi24 == WifiConfiguration.INVALID_RSSI) return false;
310        // TODO: this does not do exactly what is claimed!
311        if (currentConfiguration.visibility.rssi24
312                >= currentConfiguration.visibility.rssi5 - SCAN_CACHE_COUNT_PENALTY) {
313            return true;
314        }
315        return false;
316    }
317
318    /**
319     * Decides whether the current network is a "home" network
320     */
321    private boolean isHomeNetwork(WifiInfo wifiInfo) {
322        WifiConfiguration currentConfiguration =
323                mWifiConfigManager.getConfiguredNetwork(wifiInfo.getNetworkId());
324        if (currentConfiguration == null) return false;
325        // TODO: This seems like it will only return true for really old routers!
326        if (currentConfiguration.allowedKeyManagement.cardinality() != 1) return false;
327        if (!currentConfiguration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
328            return false;
329        }
330        ScanDetailCache scanDetailCache =
331                mWifiConfigManager.getScanDetailCacheForNetwork(wifiInfo.getNetworkId());
332        if (scanDetailCache == null) return false;
333        if (scanDetailCache.size() <= HOME_VISIBLE_NETWORK_MAX_COUNT) {
334            return true;
335        }
336        return false;
337    }
338}
339