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