WifiScoreReport.java revision 4569ebc2277f35b9bd1baa98194f963388e0c4ca
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            WifiMetrics wifiMetrics) {
164        StringBuilder sb = new StringBuilder();
165
166        int score = STARTING_SCORE;
167        boolean isBadLinkspeed = (wifiInfo.is24GHz()
168                && wifiInfo.getLinkSpeed() < mBadLinkSpeed24)
169                || (wifiInfo.is5GHz() && wifiInfo.getLinkSpeed()
170                < mBadLinkSpeed5);
171        boolean isGoodLinkspeed = (wifiInfo.is24GHz()
172                && wifiInfo.getLinkSpeed() >= mGoodLinkSpeed24)
173                || (wifiInfo.is5GHz() && wifiInfo.getLinkSpeed()
174                >= mGoodLinkSpeed5);
175
176        int badLinkspeedcount = 0;
177        if (mReportValid) {
178            badLinkspeedcount = mBadLinkspeedcount;
179        }
180
181        if (isBadLinkspeed) {
182            if (badLinkspeedcount < MAX_BAD_LINKSPEED_COUNT) {
183                badLinkspeedcount++;
184            }
185        } else {
186            if (badLinkspeedcount > 0) {
187                badLinkspeedcount--;
188            }
189        }
190
191        if (isBadLinkspeed) sb.append(" bl(").append(badLinkspeedcount).append(")");
192        if (isGoodLinkspeed) sb.append(" gl");
193
194        WifiConfiguration currentConfiguration =
195                mWifiConfigManager.getWifiConfiguration(wifiInfo.getNetworkId());
196        ScanDetailCache scanDetailCache =
197                mWifiConfigManager.getScanDetailCache(currentConfiguration);
198        /**
199         * We want to make sure that we use the 24GHz RSSI thresholds if
200         * there are 2.4GHz scan results otherwise we end up lowering the score based on 5GHz values
201         * which may cause a switch to LTE before roaming has a chance to try 2.4GHz
202         * We also might unblacklist the configuation based on 2.4GHz
203         * thresholds but joining 5GHz anyhow, and failing over to 2.4GHz because 5GHz is not good
204         */
205        boolean use24Thresholds = false;
206        boolean homeNetworkBoost = false;
207        if (currentConfiguration != null && scanDetailCache != null) {
208            currentConfiguration.setVisibility(
209                    scanDetailCache.getVisibility(SCAN_CACHE_VISIBILITY_MS));
210            if (currentConfiguration.visibility != null) {
211                if (currentConfiguration.visibility.rssi24 != WifiConfiguration.INVALID_RSSI
212                        && currentConfiguration.visibility.rssi24
213                        >= (currentConfiguration.visibility.rssi5 - SCAN_CACHE_COUNT_PENALTY)) {
214                    use24Thresholds = true;
215                }
216            }
217            if (scanDetailCache.size() <= HOME_VISIBLE_NETWORK_MAX_COUNT
218                    && currentConfiguration.allowedKeyManagement.cardinality() == 1
219                    && currentConfiguration.allowedKeyManagement
220                            .get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
221                // A PSK network with less than 6 known BSSIDs
222                // This is most likely a home network and thus we want to stick to wifi more
223                homeNetworkBoost = true;
224            }
225        }
226        if (homeNetworkBoost) sb.append(" hn");
227        if (use24Thresholds) sb.append(" u24");
228
229        int rssi = wifiInfo.getRssi() - AGGRESSIVE_HANDOVER_PENALTY * aggressiveHandover
230                + (homeNetworkBoost ? WifiConfiguration.HOME_NETWORK_RSSI_BOOST : 0);
231        sb.append(String.format(" rssi=%d ag=%d", rssi, aggressiveHandover));
232
233        boolean is24GHz = use24Thresholds || wifiInfo.is24GHz();
234
235        boolean isBadRSSI = (is24GHz && rssi < mThresholdMinimumRssi24)
236                || (!is24GHz && rssi < mThresholdMinimumRssi5);
237        boolean isLowRSSI = (is24GHz && rssi < mThresholdQualifiedRssi24)
238                || (!is24GHz
239                        && wifiInfo.getRssi() < mThresholdMinimumRssi5);
240        boolean isHighRSSI = (is24GHz && rssi >= mThresholdSaturatedRssi24)
241                || (!is24GHz
242                        && wifiInfo.getRssi() >= mThresholdSaturatedRssi5);
243
244        if (isBadRSSI) sb.append(" br");
245        if (isLowRSSI) sb.append(" lr");
246        if (isHighRSSI) sb.append(" hr");
247
248        int penalizedDueToUserTriggeredDisconnect = 0;        // Not a user triggered disconnect
249        if (currentConfiguration != null
250                && (wifiInfo.txSuccessRate > MIN_SUCCESS_COUNT
251                        || wifiInfo.rxSuccessRate > MIN_SUCCESS_COUNT)) {
252            if (isBadRSSI) {
253                currentConfiguration.numTicksAtBadRSSI++;
254                if (currentConfiguration.numTicksAtBadRSSI > MIN_NUM_TICKS_AT_STATE) {
255                    // We remained associated for a compound amount of time while passing
256                    // traffic, hence loose the corresponding user triggered disabled stats
257                    if (currentConfiguration.numUserTriggeredWifiDisableBadRSSI > 0) {
258                        currentConfiguration.numUserTriggeredWifiDisableBadRSSI--;
259                    }
260                    if (currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0) {
261                        currentConfiguration.numUserTriggeredWifiDisableLowRSSI--;
262                    }
263                    if (currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) {
264                        currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI--;
265                    }
266                    currentConfiguration.numTicksAtBadRSSI = 0;
267                }
268                if (mEnableWifiCellularHandoverUserTriggeredAdjustment
269                        && (currentConfiguration.numUserTriggeredWifiDisableBadRSSI > 0
270                                || currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0
271                                || currentConfiguration
272                                        .numUserTriggeredWifiDisableNotHighRSSI > 0)) {
273                    score = score - USER_DISCONNECT_PENALTY;
274                    penalizedDueToUserTriggeredDisconnect = 1;
275                    sb.append(" p1");
276                }
277            } else if (isLowRSSI) {
278                currentConfiguration.numTicksAtLowRSSI++;
279                if (currentConfiguration.numTicksAtLowRSSI > MIN_NUM_TICKS_AT_STATE) {
280                    // We remained associated for a compound amount of time while passing
281                    // traffic, hence loose the corresponding user triggered disabled stats
282                    if (currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0) {
283                        currentConfiguration.numUserTriggeredWifiDisableLowRSSI--;
284                    }
285                    if (currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) {
286                        currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI--;
287                    }
288                    currentConfiguration.numTicksAtLowRSSI = 0;
289                }
290                if (mEnableWifiCellularHandoverUserTriggeredAdjustment
291                        && (currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0
292                                || currentConfiguration
293                                        .numUserTriggeredWifiDisableNotHighRSSI > 0)) {
294                    score = score - USER_DISCONNECT_PENALTY;
295                    penalizedDueToUserTriggeredDisconnect = 2;
296                    sb.append(" p2");
297                }
298            } else if (!isHighRSSI) {
299                currentConfiguration.numTicksAtNotHighRSSI++;
300                if (currentConfiguration.numTicksAtNotHighRSSI > MIN_NUM_TICKS_AT_STATE) {
301                    // We remained associated for a compound amount of time while passing
302                    // traffic, hence loose the corresponding user triggered disabled stats
303                    if (currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) {
304                        currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI--;
305                    }
306                    currentConfiguration.numTicksAtNotHighRSSI = 0;
307                }
308                if (mEnableWifiCellularHandoverUserTriggeredAdjustment
309                        && currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) {
310                    score = score - USER_DISCONNECT_PENALTY;
311                    penalizedDueToUserTriggeredDisconnect = 3;
312                    sb.append(" p3");
313                }
314            }
315            sb.append(String.format(" ticks %d,%d,%d", currentConfiguration.numTicksAtBadRSSI,
316                    currentConfiguration.numTicksAtLowRSSI,
317                    currentConfiguration.numTicksAtNotHighRSSI));
318        }
319
320        if (mVerboseLoggingEnabled) {
321            String rssiStatus = "";
322            if (isBadRSSI) {
323                rssiStatus += " badRSSI ";
324            } else if (isHighRSSI) {
325                rssiStatus += " highRSSI ";
326            } else if (isLowRSSI) {
327                rssiStatus += " lowRSSI ";
328            }
329            if (isBadLinkspeed) rssiStatus += " lowSpeed ";
330            Log.d(TAG, "calculateWifiScore freq=" + Integer.toString(wifiInfo.getFrequency())
331                    + " speed=" + Integer.toString(wifiInfo.getLinkSpeed())
332                    + " score=" + Integer.toString(wifiInfo.score)
333                    + rssiStatus
334                    + " -> txbadrate=" + String.format("%.2f", wifiInfo.txBadRate)
335                    + " txgoodrate=" + String.format("%.2f", wifiInfo.txSuccessRate)
336                    + " txretriesrate=" + String.format("%.2f", wifiInfo.txRetriesRate)
337                    + " rxrate=" + String.format("%.2f", wifiInfo.rxSuccessRate)
338                    + " userTriggerdPenalty" + penalizedDueToUserTriggeredDisconnect);
339        }
340
341        if ((wifiInfo.txBadRate >= 1)
342                && (wifiInfo.txSuccessRate < MAX_SUCCESS_COUNT_OF_STUCK_LINK)
343                && (isBadRSSI || isLowRSSI)) {
344            // Link is stuck
345            if (wifiInfo.linkStuckCount < MAX_STUCK_LINK_COUNT) {
346                wifiInfo.linkStuckCount += 1;
347            }
348            sb.append(String.format(" ls+=%d", wifiInfo.linkStuckCount));
349            if (mVerboseLoggingEnabled) {
350                Log.d(TAG, " bad link -> stuck count ="
351                        + Integer.toString(wifiInfo.linkStuckCount));
352            }
353        } else if (wifiInfo.txBadRate < MIN_TX_RATE_FOR_WORKING_LINK) {
354            if (wifiInfo.linkStuckCount > 0) {
355                wifiInfo.linkStuckCount -= 1;
356            }
357            sb.append(String.format(" ls-=%d", wifiInfo.linkStuckCount));
358            if (mVerboseLoggingEnabled) {
359                Log.d(TAG, " good link -> stuck count ="
360                        + Integer.toString(wifiInfo.linkStuckCount));
361            }
362        }
363
364        sb.append(String.format(" [%d", score));
365
366        if (wifiInfo.linkStuckCount > MIN_SUSTAINED_LINK_STUCK_COUNT) {
367            // Once link gets stuck for more than 3 seconds, start reducing the score
368            score = score - LINK_STUCK_PENALTY * (wifiInfo.linkStuckCount - 1);
369        }
370        sb.append(String.format(",%d", score));
371
372        if (isBadLinkspeed) {
373            score -= BAD_LINKSPEED_PENALTY;
374            if (mVerboseLoggingEnabled) {
375                Log.d(TAG, " isBadLinkspeed   ---> count=" + mBadLinkspeedcount
376                        + " score=" + Integer.toString(score));
377            }
378        } else if ((isGoodLinkspeed) && (wifiInfo.txSuccessRate > 5)) {
379            score += GOOD_LINKSPEED_BONUS; // So as bad rssi alone dont kill us
380        }
381        sb.append(String.format(",%d", score));
382
383        if (isBadRSSI) {
384            if (wifiInfo.badRssiCount < MAX_BAD_RSSI_COUNT) {
385                wifiInfo.badRssiCount += 1;
386            }
387        } else if (isLowRSSI) {
388            wifiInfo.lowRssiCount = MAX_LOW_RSSI_COUNT; // Dont increment the lowRssi count above 1
389            if (wifiInfo.badRssiCount > 0) {
390                // Decrement bad Rssi count
391                wifiInfo.badRssiCount -= 1;
392            }
393        } else {
394            wifiInfo.badRssiCount = 0;
395            wifiInfo.lowRssiCount = 0;
396        }
397
398        score -= wifiInfo.badRssiCount * BAD_RSSI_COUNT_PENALTY + wifiInfo.lowRssiCount;
399        sb.append(String.format(",%d", score));
400
401        if (mVerboseLoggingEnabled) {
402            Log.d(TAG, " badRSSI count" + Integer.toString(wifiInfo.badRssiCount)
403                    + " lowRSSI count" + Integer.toString(wifiInfo.lowRssiCount)
404                    + " --> score " + Integer.toString(score));
405        }
406
407        if (isHighRSSI) {
408            score += 5;
409            if (mVerboseLoggingEnabled) Log.d(TAG, " isHighRSSI       ---> score=" + score);
410        }
411        sb.append(String.format(",%d]", score));
412
413        sb.append(String.format(" brc=%d lrc=%d", wifiInfo.badRssiCount, wifiInfo.lowRssiCount));
414
415        //sanitize boundaries
416        if (score > NetworkAgent.WIFI_BASE_SCORE) {
417            score = NetworkAgent.WIFI_BASE_SCORE;
418        }
419        if (score < 0) {
420            score = 0;
421        }
422
423        //report score
424        if (score != wifiInfo.score) {
425            if (mVerboseLoggingEnabled) {
426                Log.d(TAG, "calculateWifiScore() report new score " + Integer.toString(score));
427            }
428            wifiInfo.score = score;
429            if (networkAgent != null) {
430                networkAgent.sendNetworkScore(score);
431            }
432        }
433        mBadLinkspeedcount = badLinkspeedcount;
434        mReport = sb.toString();
435        mReportValid = true;
436        wifiMetrics.incrementWifiScoreCount(score);
437    }
438}
439