WifiScoreReport.java revision 6867feee7608fa47f9f7eb7042459ba55e7d0d21
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    // TODO: switch to WifiScoreReport if it doesn't break any tools
34    private static final String TAG = "WifiStateMachine";
35
36    // TODO: This score was hardcorded to 56.  Need to understand why after finishing code refactor
37    private static final int STARTING_SCORE = 56;
38
39    // TODO: Understand why these values are used
40    private static final int MAX_BAD_LINKSPEED_COUNT = 6;
41    private static final int SCAN_CACHE_VISIBILITY_MS = 12000;
42    private static final int HOME_VISIBLE_NETWORK_MAX_COUNT = 6;
43    private static final int SCAN_CACHE_COUNT_PENALTY = 2;
44    private static final int AGGRESSIVE_HANDOVER_PENALTY = 6;
45    private static final int MIN_SUCCESS_COUNT = 5;
46    private static final int MAX_SUCCESS_COUNT_OF_STUCK_LINK = 3;
47    private static final int MAX_STUCK_LINK_COUNT = 5;
48    private static final int MIN_NUM_TICKS_AT_STATE = 1000;
49    private static final int USER_DISCONNECT_PENALTY = 5;
50    private static final int MAX_BAD_RSSI_COUNT = 7;
51    private static final int BAD_RSSI_COUNT_PENALTY = 2;
52    private static final int MAX_LOW_RSSI_COUNT = 1;
53    private static final double MIN_TX_RATE_FOR_WORKING_LINK = 0.3;
54    private static final int MIN_SUSTAINED_LINK_STUCK_COUNT = 1;
55    private static final int LINK_STUCK_PENALTY = 2;
56    private static final int BAD_LINKSPEED_PENALTY = 4;
57    private static final int GOOD_LINKSPEED_BONUS = 4;
58
59    // Device configs.
60    private final int mThresholdMinimumRssi5;
61    private final int mThresholdQualifiedRssi5;
62    private final int mThresholdSaturatedRssi5;
63    private final int mThresholdMinimumRssi24;
64    private final int mThresholdQualifiedRssi24;
65    private final int mThresholdSaturatedRssi24;
66    private final int mBadLinkSpeed24;
67    private final int mBadLinkSpeed5;
68    private final int mGoodLinkSpeed24;
69    private final int mGoodLinkSpeed5;
70    private final boolean mEnableWifiCellularHandoverUserTriggeredAdjustment;
71
72    private final WifiConfigManager mWifiConfigManager;
73    private boolean mVerboseLoggingEnabled = false;
74
75    // Cache of the last score report.
76    private String mReport;
77    private int mBadLinkspeedcount = 0;
78    private boolean mReportValid = false;
79
80    WifiScoreReport(Context context, WifiConfigManager wifiConfigManager) {
81        // Fetch all the device configs.
82        mThresholdMinimumRssi5 = context.getResources().getInteger(
83                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz);
84        mThresholdQualifiedRssi5 = context.getResources().getInteger(
85                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz);
86        mThresholdSaturatedRssi5 = context.getResources().getInteger(
87                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz);
88        mThresholdMinimumRssi24 = context.getResources().getInteger(
89                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz);
90        mThresholdQualifiedRssi24 = context.getResources().getInteger(
91                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz);
92        mThresholdSaturatedRssi24 = context.getResources().getInteger(
93                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz);
94        mBadLinkSpeed24 = context.getResources().getInteger(
95                R.integer.config_wifi_framework_wifi_score_bad_link_speed_24);
96        mBadLinkSpeed5 = context.getResources().getInteger(
97                R.integer.config_wifi_framework_wifi_score_bad_link_speed_5);
98        mGoodLinkSpeed24 = context.getResources().getInteger(
99                R.integer.config_wifi_framework_wifi_score_good_link_speed_24);
100        mGoodLinkSpeed5 = context.getResources().getInteger(
101                R.integer.config_wifi_framework_wifi_score_good_link_speed_5);
102        mEnableWifiCellularHandoverUserTriggeredAdjustment = context.getResources().getBoolean(
103                R.bool.config_wifi_framework_cellular_handover_enable_user_triggered_adjustment);
104
105        mWifiConfigManager = wifiConfigManager;
106    }
107
108    /**
109     * Method returning the String representation of the last score report.
110     *
111     *  @return String score report
112     */
113    public String getLastReport() {
114        return mReport;
115    }
116
117    /**
118     * Method returning the bad link speed count at the time of the last score report.
119     *
120     *  @return int bad linkspeed count
121     */
122    public int getLastBadLinkspeedcount() {
123        return mBadLinkspeedcount;
124    }
125
126    /**
127     * Reset the last calculated score.
128     */
129    public void reset() {
130        mBadLinkspeedcount = 0;
131        mReport = "";
132        mReportValid = false;
133    }
134
135    /**
136     * Checks if the last report data is valid or not. This will be cleared when {@link #reset()} is
137     * invoked.
138     *
139     * @return true if valid, false otherwise.
140     */
141    public boolean isLastReportValid() {
142        return mReportValid;
143    }
144
145    /**
146     * Enable/Disable verbose logging in score report generation.
147     */
148    public void enableVerboseLogging(boolean enable) {
149        mVerboseLoggingEnabled = enable;
150    }
151
152    /**
153     * Calculate wifi network score based on updated link layer stats and send the score to provided
154     * network agent.
155     *
156     * If the score has changed from the previous value, update the WifiNetworkAgent.
157     * @param wifiInfo WifiInfo instance pointing to the currently connected network.
158     * @param networkAgent NetworkAgent to be notified of new score.
159     * @param aggressiveHandover int current aggressiveHandover setting.
160     */
161    public void calculateAndReportScore(
162            WifiInfo wifiInfo, NetworkAgent networkAgent, int aggressiveHandover) {
163        StringBuilder sb = new StringBuilder();
164
165        int score = STARTING_SCORE;
166        boolean isBadLinkspeed = (wifiInfo.is24GHz()
167                && wifiInfo.getLinkSpeed() < mBadLinkSpeed24)
168                || (wifiInfo.is5GHz() && wifiInfo.getLinkSpeed()
169                < mBadLinkSpeed5);
170        boolean isGoodLinkspeed = (wifiInfo.is24GHz()
171                && wifiInfo.getLinkSpeed() >= mGoodLinkSpeed24)
172                || (wifiInfo.is5GHz() && wifiInfo.getLinkSpeed()
173                >= mGoodLinkSpeed5);
174
175        int badLinkspeedcount = 0;
176        if (mReportValid) {
177            badLinkspeedcount = mBadLinkspeedcount;
178        }
179
180        if (isBadLinkspeed) {
181            if (badLinkspeedcount < MAX_BAD_LINKSPEED_COUNT) {
182                badLinkspeedcount++;
183            }
184        } else {
185            if (badLinkspeedcount > 0) {
186                badLinkspeedcount--;
187            }
188        }
189
190        if (isBadLinkspeed) sb.append(" bl(").append(badLinkspeedcount).append(")");
191        if (isGoodLinkspeed) sb.append(" gl");
192
193        WifiConfiguration currentConfiguration =
194                mWifiConfigManager.getWifiConfiguration(wifiInfo.getNetworkId());
195        ScanDetailCache scanDetailCache =
196                mWifiConfigManager.getScanDetailCache(currentConfiguration);
197        /**
198         * We want to make sure that we use the 24GHz RSSI thresholds if
199         * there are 2.4GHz scan results otherwise we end up lowering the score based on 5GHz values
200         * which may cause a switch to LTE before roaming has a chance to try 2.4GHz
201         * We also might unblacklist the configuation based on 2.4GHz
202         * thresholds but joining 5GHz anyhow, and failing over to 2.4GHz because 5GHz is not good
203         */
204        boolean use24Thresholds = false;
205        boolean homeNetworkBoost = false;
206        if (currentConfiguration != null && scanDetailCache != null) {
207            currentConfiguration.setVisibility(
208                    scanDetailCache.getVisibility(SCAN_CACHE_VISIBILITY_MS));
209            if (currentConfiguration.visibility != null) {
210                if (currentConfiguration.visibility.rssi24 != WifiConfiguration.INVALID_RSSI
211                        && currentConfiguration.visibility.rssi24
212                        >= (currentConfiguration.visibility.rssi5 - SCAN_CACHE_COUNT_PENALTY)) {
213                    use24Thresholds = true;
214                }
215            }
216            if (scanDetailCache.size() <= HOME_VISIBLE_NETWORK_MAX_COUNT
217                    && currentConfiguration.allowedKeyManagement.cardinality() == 1
218                    && currentConfiguration.allowedKeyManagement
219                            .get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
220                // A PSK network with less than 6 known BSSIDs
221                // This is most likely a home network and thus we want to stick to wifi more
222                homeNetworkBoost = true;
223            }
224        }
225        if (homeNetworkBoost) sb.append(" hn");
226        if (use24Thresholds) sb.append(" u24");
227
228        int rssi = wifiInfo.getRssi() - AGGRESSIVE_HANDOVER_PENALTY * aggressiveHandover
229                + (homeNetworkBoost ? WifiConfiguration.HOME_NETWORK_RSSI_BOOST : 0);
230        sb.append(String.format(" rssi=%d ag=%d", rssi, aggressiveHandover));
231
232        boolean is24GHz = use24Thresholds || wifiInfo.is24GHz();
233
234        boolean isBadRSSI = (is24GHz && rssi < mThresholdMinimumRssi24)
235                || (!is24GHz && rssi < mThresholdMinimumRssi5);
236        boolean isLowRSSI = (is24GHz && rssi < mThresholdQualifiedRssi24)
237                || (!is24GHz
238                        && wifiInfo.getRssi() < mThresholdMinimumRssi5);
239        boolean isHighRSSI = (is24GHz && rssi >= mThresholdSaturatedRssi24)
240                || (!is24GHz
241                        && wifiInfo.getRssi() >= mThresholdSaturatedRssi5);
242
243        if (isBadRSSI) sb.append(" br");
244        if (isLowRSSI) sb.append(" lr");
245        if (isHighRSSI) sb.append(" hr");
246
247        int penalizedDueToUserTriggeredDisconnect = 0;        // Not a user triggered disconnect
248        if (currentConfiguration != null
249                && (wifiInfo.txSuccessRate > MIN_SUCCESS_COUNT
250                        || wifiInfo.rxSuccessRate > MIN_SUCCESS_COUNT)) {
251            if (isBadRSSI) {
252                currentConfiguration.numTicksAtBadRSSI++;
253                if (currentConfiguration.numTicksAtBadRSSI > MIN_NUM_TICKS_AT_STATE) {
254                    // We remained associated for a compound amount of time while passing
255                    // traffic, hence loose the corresponding user triggered disabled stats
256                    if (currentConfiguration.numUserTriggeredWifiDisableBadRSSI > 0) {
257                        currentConfiguration.numUserTriggeredWifiDisableBadRSSI--;
258                    }
259                    if (currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0) {
260                        currentConfiguration.numUserTriggeredWifiDisableLowRSSI--;
261                    }
262                    if (currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) {
263                        currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI--;
264                    }
265                    currentConfiguration.numTicksAtBadRSSI = 0;
266                }
267                if (mEnableWifiCellularHandoverUserTriggeredAdjustment
268                        && (currentConfiguration.numUserTriggeredWifiDisableBadRSSI > 0
269                                || currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0
270                                || currentConfiguration
271                                        .numUserTriggeredWifiDisableNotHighRSSI > 0)) {
272                    score = score - USER_DISCONNECT_PENALTY;
273                    penalizedDueToUserTriggeredDisconnect = 1;
274                    sb.append(" p1");
275                }
276            } else if (isLowRSSI) {
277                currentConfiguration.numTicksAtLowRSSI++;
278                if (currentConfiguration.numTicksAtLowRSSI > MIN_NUM_TICKS_AT_STATE) {
279                    // We remained associated for a compound amount of time while passing
280                    // traffic, hence loose the corresponding user triggered disabled stats
281                    if (currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0) {
282                        currentConfiguration.numUserTriggeredWifiDisableLowRSSI--;
283                    }
284                    if (currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) {
285                        currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI--;
286                    }
287                    currentConfiguration.numTicksAtLowRSSI = 0;
288                }
289                if (mEnableWifiCellularHandoverUserTriggeredAdjustment
290                        && (currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0
291                                || currentConfiguration
292                                        .numUserTriggeredWifiDisableNotHighRSSI > 0)) {
293                    score = score - USER_DISCONNECT_PENALTY;
294                    penalizedDueToUserTriggeredDisconnect = 2;
295                    sb.append(" p2");
296                }
297            } else if (!isHighRSSI) {
298                currentConfiguration.numTicksAtNotHighRSSI++;
299                if (currentConfiguration.numTicksAtNotHighRSSI > MIN_NUM_TICKS_AT_STATE) {
300                    // We remained associated for a compound amount of time while passing
301                    // traffic, hence loose the corresponding user triggered disabled stats
302                    if (currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) {
303                        currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI--;
304                    }
305                    currentConfiguration.numTicksAtNotHighRSSI = 0;
306                }
307                if (mEnableWifiCellularHandoverUserTriggeredAdjustment
308                        && currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) {
309                    score = score - USER_DISCONNECT_PENALTY;
310                    penalizedDueToUserTriggeredDisconnect = 3;
311                    sb.append(" p3");
312                }
313            }
314            sb.append(String.format(" ticks %d,%d,%d", currentConfiguration.numTicksAtBadRSSI,
315                    currentConfiguration.numTicksAtLowRSSI,
316                    currentConfiguration.numTicksAtNotHighRSSI));
317        }
318
319        if (mVerboseLoggingEnabled) {
320            String rssiStatus = "";
321            if (isBadRSSI) {
322                rssiStatus += " badRSSI ";
323            } else if (isHighRSSI) {
324                rssiStatus += " highRSSI ";
325            } else if (isLowRSSI) {
326                rssiStatus += " lowRSSI ";
327            }
328            if (isBadLinkspeed) rssiStatus += " lowSpeed ";
329            Log.d(TAG, "calculateWifiScore freq=" + Integer.toString(wifiInfo.getFrequency())
330                    + " speed=" + Integer.toString(wifiInfo.getLinkSpeed())
331                    + " score=" + Integer.toString(wifiInfo.score)
332                    + rssiStatus
333                    + " -> txbadrate=" + String.format("%.2f", wifiInfo.txBadRate)
334                    + " txgoodrate=" + String.format("%.2f", wifiInfo.txSuccessRate)
335                    + " txretriesrate=" + String.format("%.2f", wifiInfo.txRetriesRate)
336                    + " rxrate=" + String.format("%.2f", wifiInfo.rxSuccessRate)
337                    + " userTriggerdPenalty" + penalizedDueToUserTriggeredDisconnect);
338        }
339
340        if ((wifiInfo.txBadRate >= 1)
341                && (wifiInfo.txSuccessRate < MAX_SUCCESS_COUNT_OF_STUCK_LINK)
342                && (isBadRSSI || isLowRSSI)) {
343            // Link is stuck
344            if (wifiInfo.linkStuckCount < MAX_STUCK_LINK_COUNT) {
345                wifiInfo.linkStuckCount += 1;
346            }
347            sb.append(String.format(" ls+=%d", wifiInfo.linkStuckCount));
348            if (mVerboseLoggingEnabled) {
349                Log.d(TAG, " bad link -> stuck count ="
350                        + Integer.toString(wifiInfo.linkStuckCount));
351            }
352        } else if (wifiInfo.txBadRate < MIN_TX_RATE_FOR_WORKING_LINK) {
353            if (wifiInfo.linkStuckCount > 0) {
354                wifiInfo.linkStuckCount -= 1;
355            }
356            sb.append(String.format(" ls-=%d", wifiInfo.linkStuckCount));
357            if (mVerboseLoggingEnabled) {
358                Log.d(TAG, " good link -> stuck count ="
359                        + Integer.toString(wifiInfo.linkStuckCount));
360            }
361        }
362
363        sb.append(String.format(" [%d", score));
364
365        if (wifiInfo.linkStuckCount > MIN_SUSTAINED_LINK_STUCK_COUNT) {
366            // Once link gets stuck for more than 3 seconds, start reducing the score
367            score = score - LINK_STUCK_PENALTY * (wifiInfo.linkStuckCount - 1);
368        }
369        sb.append(String.format(",%d", score));
370
371        if (isBadLinkspeed) {
372            score -= BAD_LINKSPEED_PENALTY;
373            if (mVerboseLoggingEnabled) {
374                Log.d(TAG, " isBadLinkspeed   ---> count=" + mBadLinkspeedcount
375                        + " score=" + Integer.toString(score));
376            }
377        } else if ((isGoodLinkspeed) && (wifiInfo.txSuccessRate > 5)) {
378            score += GOOD_LINKSPEED_BONUS; // So as bad rssi alone dont kill us
379        }
380        sb.append(String.format(",%d", score));
381
382        if (isBadRSSI) {
383            if (wifiInfo.badRssiCount < MAX_BAD_RSSI_COUNT) {
384                wifiInfo.badRssiCount += 1;
385            }
386        } else if (isLowRSSI) {
387            wifiInfo.lowRssiCount = MAX_LOW_RSSI_COUNT; // Dont increment the lowRssi count above 1
388            if (wifiInfo.badRssiCount > 0) {
389                // Decrement bad Rssi count
390                wifiInfo.badRssiCount -= 1;
391            }
392        } else {
393            wifiInfo.badRssiCount = 0;
394            wifiInfo.lowRssiCount = 0;
395        }
396
397        score -= wifiInfo.badRssiCount * BAD_RSSI_COUNT_PENALTY + wifiInfo.lowRssiCount;
398        sb.append(String.format(",%d", score));
399
400        if (mVerboseLoggingEnabled) {
401            Log.d(TAG, " badRSSI count" + Integer.toString(wifiInfo.badRssiCount)
402                    + " lowRSSI count" + Integer.toString(wifiInfo.lowRssiCount)
403                    + " --> score " + Integer.toString(score));
404        }
405
406        if (isHighRSSI) {
407            score += 5;
408            if (mVerboseLoggingEnabled) Log.d(TAG, " isHighRSSI       ---> score=" + score);
409        }
410        sb.append(String.format(",%d]", score));
411
412        sb.append(String.format(" brc=%d lrc=%d", wifiInfo.badRssiCount, wifiInfo.lowRssiCount));
413
414        //sanitize boundaries
415        if (score > NetworkAgent.WIFI_BASE_SCORE) {
416            score = NetworkAgent.WIFI_BASE_SCORE;
417        }
418        if (score < 0) {
419            score = 0;
420        }
421
422        //report score
423        if (score != wifiInfo.score) {
424            if (mVerboseLoggingEnabled) {
425                Log.d(TAG, "calculateWifiScore() report new score " + Integer.toString(score));
426            }
427            wifiInfo.score = score;
428            if (networkAgent != null) {
429                networkAgent.sendNetworkScore(score);
430            }
431        }
432
433        mBadLinkspeedcount = badLinkspeedcount;
434        mReport = sb.toString();
435        mReportValid = true;
436    }
437}
438