• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 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 
17 package com.android.server.wifi;
18 
19 import android.annotation.NonNull;
20 import android.content.Context;
21 import android.net.wifi.ScanResult;
22 import android.net.wifi.WifiInfo;
23 import android.text.TextUtils;
24 import android.util.Log;
25 
26 import com.android.internal.annotations.VisibleForTesting;
27 import com.android.server.wifi.util.KeyValueListParser;
28 import com.android.wifi.resources.R;
29 
30 /**
31  * Holds parameters used for scoring networks.
32  *
33  * Doing this in one place means that there's a better chance of consistency between
34  * connected score and network selection.
35  *
36  */
37 public class ScoringParams {
38     private final Context mContext;
39 
40     private static final String TAG = "WifiScoringParams";
41     private static final int EXIT = 0;
42     private static final int ENTRY = 1;
43     private static final int SUFFICIENT = 2;
44     private static final int GOOD = 3;
45 
46     private static final int ACTIVE_TRAFFIC = 1;
47     private static final int HIGH_TRAFFIC = 2;
48     /**
49      * Parameter values are stored in a separate container so that a new collection of values can
50      * be checked for consistency before activating them.
51      */
52     private class Values {
53         /** RSSI thresholds for 2.4 GHz band (dBm) */
54         public static final String KEY_RSSI2 = "rssi2";
55         public final int[] rssi2 = {-83, -80, -73, -60};
56 
57         /** RSSI thresholds for 5 GHz band (dBm) */
58         public static final String KEY_RSSI5 = "rssi5";
59         public final int[] rssi5 = {-80, -77, -70, -57};
60 
61         /** RSSI thresholds for 6 GHz band (dBm) */
62         public static final String KEY_RSSI6 = "rssi6";
63         public final int[] rssi6 = {-80, -77, -70, -57};
64 
65        /** Guidelines based on packet rates (packets/sec) */
66         public static final String KEY_PPS = "pps";
67         public final int[] pps = {0, 1, 100};
68 
69         /** Number of seconds for RSSI forecast */
70         public static final String KEY_HORIZON = "horizon";
71         public static final int MIN_HORIZON = -9;
72         public static final int MAX_HORIZON = 60;
73         public int horizon = 15;
74 
75         /** Number 0-10 influencing requests for network unreachability detection */
76         public static final String KEY_NUD = "nud";
77         public static final int MIN_NUD = 0;
78         public static final int MAX_NUD = 10;
79         public int nud = 8;
80 
81         /** Experiment identifier */
82         public static final String KEY_EXPID = "expid";
83         public static final int MIN_EXPID = 0;
84         public static final int MAX_EXPID = Integer.MAX_VALUE;
85         public int expid = 0;
86 
87         /** CandidateScorer parameters */
88         public int throughputBonusNumerator = 120;
89         public int throughputBonusDenominator = 433;
90         public int throughputBonusNumeratorAfter800Mbps = 1;
91         public int throughputBonusDenominatorAfter800Mbps = 16;
92         public boolean enable6GhzBeaconRssiBoost = true;
93         public int throughputBonusLimit = 320;
94         public int savedNetworkBonus = 500;
95         public int unmeteredNetworkBonus = 1000;
96         public int currentNetworkBonusMin = 16;
97         public int currentNetworkBonusPercent = 20;
98         public int secureNetworkBonus = 40;
99         public int band6GhzBonus = 0;
100         public int scoringBucketStepSize = 500;
101         public int lastSelectionMinutes = 480;
102         public int estimateRssiErrorMargin = 5;
103         public static final int MIN_MINUTES = 1;
104         public static final int MAX_MINUTES = Integer.MAX_VALUE / (60 * 1000);
105 
Values()106         Values() {
107         }
108 
Values(Values source)109         Values(Values source) {
110             for (int i = 0; i < rssi2.length; i++) {
111                 rssi2[i] = source.rssi2[i];
112             }
113             for (int i = 0; i < rssi5.length; i++) {
114                 rssi5[i] = source.rssi5[i];
115             }
116             for (int i = 0; i < rssi6.length; i++) {
117                 rssi6[i] = source.rssi6[i];
118             }
119             for (int i = 0; i < pps.length; i++) {
120                 pps[i] = source.pps[i];
121             }
122             horizon = source.horizon;
123             nud = source.nud;
124             expid = source.expid;
125         }
126 
validate()127         public void validate() throws IllegalArgumentException {
128             validateRssiArray(rssi2);
129             validateRssiArray(rssi5);
130             validateRssiArray(rssi6);
131             validateOrderedNonNegativeArray(pps);
132             validateRange(horizon, MIN_HORIZON, MAX_HORIZON);
133             validateRange(nud, MIN_NUD, MAX_NUD);
134             validateRange(expid, MIN_EXPID, MAX_EXPID);
135             validateRange(lastSelectionMinutes, MIN_MINUTES, MAX_MINUTES);
136         }
137 
validateRssiArray(int[] rssi)138         private void validateRssiArray(int[] rssi) throws IllegalArgumentException {
139             int low = WifiInfo.MIN_RSSI;
140             int high = Math.min(WifiInfo.MAX_RSSI, -1); // Stricter than Wifiinfo
141             for (int i = 0; i < rssi.length; i++) {
142                 validateRange(rssi[i], low, high);
143                 low = rssi[i];
144             }
145         }
146 
validateRange(int k, int low, int high)147         private void validateRange(int k, int low, int high) throws IllegalArgumentException {
148             if (k < low || k > high) {
149                 throw new IllegalArgumentException();
150             }
151         }
152 
validateOrderedNonNegativeArray(int[] a)153         private void validateOrderedNonNegativeArray(int[] a) throws IllegalArgumentException {
154             int low = 0;
155             for (int i = 0; i < a.length; i++) {
156                 if (a[i] < low) {
157                     throw new IllegalArgumentException();
158                 }
159                 low = a[i];
160             }
161         }
162 
parseString(String kvList)163         public void parseString(String kvList) throws IllegalArgumentException {
164             KeyValueListParser parser = new KeyValueListParser(',');
165             parser.setString(kvList);
166             if (parser.size() != ("" + kvList).split(",").length) {
167                 throw new IllegalArgumentException("dup keys");
168             }
169             updateIntArray(rssi2, parser, KEY_RSSI2);
170             updateIntArray(rssi5, parser, KEY_RSSI5);
171             updateIntArray(rssi6, parser, KEY_RSSI6);
172             updateIntArray(pps, parser, KEY_PPS);
173             horizon = updateInt(parser, KEY_HORIZON, horizon);
174             nud = updateInt(parser, KEY_NUD, nud);
175             expid = updateInt(parser, KEY_EXPID, expid);
176         }
177 
updateInt(KeyValueListParser parser, String key, int defaultValue)178         private int updateInt(KeyValueListParser parser, String key, int defaultValue)
179                 throws IllegalArgumentException {
180             String value = parser.getString(key, null);
181             if (value == null) return defaultValue;
182             try {
183                 return Integer.parseInt(value);
184             } catch (NumberFormatException e) {
185                 throw new IllegalArgumentException();
186             }
187         }
188 
updateIntArray(final int[] dest, KeyValueListParser parser, String key)189         private void updateIntArray(final int[] dest, KeyValueListParser parser, String key)
190                 throws IllegalArgumentException {
191             if (parser.getString(key, null) == null) return;
192             int[] ints = parser.getIntArray(key, null);
193             if (ints == null) throw new IllegalArgumentException();
194             if (ints.length != dest.length) throw new IllegalArgumentException();
195             for (int i = 0; i < dest.length; i++) {
196                 dest[i] = ints[i];
197             }
198         }
199 
200         @Override
toString()201         public String toString() {
202             StringBuilder sb = new StringBuilder();
203             appendKey(sb, KEY_RSSI2);
204             appendInts(sb, rssi2);
205             appendKey(sb, KEY_RSSI5);
206             appendInts(sb, rssi5);
207             appendKey(sb, KEY_RSSI6);
208             appendInts(sb, rssi6);
209             appendKey(sb, KEY_PPS);
210             appendInts(sb, pps);
211             appendKey(sb, KEY_HORIZON);
212             sb.append(horizon);
213             appendKey(sb, KEY_NUD);
214             sb.append(nud);
215             appendKey(sb, KEY_EXPID);
216             sb.append(expid);
217             return sb.toString();
218         }
219 
appendKey(StringBuilder sb, String key)220         private void appendKey(StringBuilder sb, String key) {
221             if (sb.length() != 0) sb.append(",");
222             sb.append(key).append("=");
223         }
224 
appendInts(StringBuilder sb, final int[] a)225         private void appendInts(StringBuilder sb, final int[] a) {
226             final int n = a.length;
227             for (int i = 0; i < n; i++) {
228                 if (i > 0) sb.append(":");
229                 sb.append(a[i]);
230             }
231         }
232     }
233 
234     @NonNull private Values mVal = null;
235 
236     @VisibleForTesting
ScoringParams()237     public ScoringParams() {
238         mContext = null;
239         mVal = new Values();
240     }
241 
ScoringParams(Context context)242     public ScoringParams(Context context) {
243         mContext = context;
244     }
245 
loadResources(Context context)246     private void loadResources(Context context) {
247         if (mVal != null) return;
248         mVal = new Values();
249         mVal.rssi2[EXIT] = context.getResources().getInteger(
250                 R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz);
251         mVal.rssi2[ENTRY] = context.getResources().getInteger(
252                 R.integer.config_wifi_framework_wifi_score_entry_rssi_threshold_24GHz);
253         mVal.rssi2[SUFFICIENT] = context.getResources().getInteger(
254                 R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz);
255         mVal.rssi2[GOOD] = context.getResources().getInteger(
256                 R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz);
257         mVal.rssi5[EXIT] = context.getResources().getInteger(
258                 R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz);
259         mVal.rssi5[ENTRY] = context.getResources().getInteger(
260                 R.integer.config_wifi_framework_wifi_score_entry_rssi_threshold_5GHz);
261         mVal.rssi5[SUFFICIENT] = context.getResources().getInteger(
262                 R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz);
263         mVal.rssi5[GOOD] = context.getResources().getInteger(
264                 R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz);
265         mVal.rssi6[EXIT] = context.getResources().getInteger(
266                 R.integer.config_wifiFrameworkScoreBadRssiThreshold6ghz);
267         mVal.rssi6[ENTRY] = context.getResources().getInteger(
268                 R.integer.config_wifiFrameworkScoreEntryRssiThreshold6ghz);
269         mVal.rssi6[SUFFICIENT] = context.getResources().getInteger(
270                 R.integer.config_wifiFrameworkScoreLowRssiThreshold6ghz);
271         mVal.rssi6[GOOD] = context.getResources().getInteger(
272                 R.integer.config_wifiFrameworkScoreGoodRssiThreshold6ghz);
273         mVal.throughputBonusNumerator = context.getResources().getInteger(
274                 R.integer.config_wifiFrameworkThroughputBonusNumerator);
275         mVal.throughputBonusDenominator = context.getResources().getInteger(
276                 R.integer.config_wifiFrameworkThroughputBonusDenominator);
277         mVal.throughputBonusNumeratorAfter800Mbps = context.getResources().getInteger(
278                 R.integer.config_wifiFrameworkThroughputBonusNumeratorAfter800Mbps);
279         mVal.throughputBonusDenominatorAfter800Mbps = context.getResources().getInteger(
280                 R.integer.config_wifiFrameworkThroughputBonusDenominatorAfter800Mbps);
281         mVal.enable6GhzBeaconRssiBoost = context.getResources().getBoolean(
282                 R.bool.config_wifiEnable6GhzBeaconRssiBoost);
283         mVal.throughputBonusLimit = context.getResources().getInteger(
284                 R.integer.config_wifiFrameworkThroughputBonusLimit);
285         mVal.savedNetworkBonus = context.getResources().getInteger(
286                 R.integer.config_wifiFrameworkSavedNetworkBonus);
287         mVal.unmeteredNetworkBonus = context.getResources().getInteger(
288                 R.integer.config_wifiFrameworkUnmeteredNetworkBonus);
289         mVal.currentNetworkBonusMin = context.getResources().getInteger(
290                 R.integer.config_wifiFrameworkCurrentNetworkBonusMin);
291         mVal.currentNetworkBonusPercent = context.getResources().getInteger(
292             R.integer.config_wifiFrameworkCurrentNetworkBonusPercent);
293         mVal.secureNetworkBonus = context.getResources().getInteger(
294                 R.integer.config_wifiFrameworkSecureNetworkBonus);
295         mVal.band6GhzBonus = context.getResources().getInteger(R.integer.config_wifiBand6GhzBonus);
296         mVal.scoringBucketStepSize = context.getResources().getInteger(
297                 R.integer.config_wifiScoringBucketStepSize);
298         mVal.lastSelectionMinutes = context.getResources().getInteger(
299                 R.integer.config_wifiFrameworkLastSelectionMinutes);
300         mVal.estimateRssiErrorMargin = context.getResources().getInteger(
301                 R.integer.config_wifiEstimateRssiErrorMarginDb);
302         mVal.pps[ACTIVE_TRAFFIC] = context.getResources().getInteger(
303                 R.integer.config_wifiFrameworkMinPacketPerSecondActiveTraffic);
304         mVal.pps[HIGH_TRAFFIC] = context.getResources().getInteger(
305                 R.integer.config_wifiFrameworkMinPacketPerSecondHighTraffic);
306         try {
307             mVal.validate();
308         } catch (IllegalArgumentException e) {
309             Log.wtf(TAG, "Inconsistent config_wifi_framework_ resources: " + this, e);
310         }
311     }
312 
313     private static final String COMMA_KEY_VAL_STAR = "^(,[A-Za-z_][A-Za-z0-9_]*=[0-9.:+-]+)*$";
314 
315     /**
316      * Updates the parameters from the given parameter string.
317      * If any errors are detected, no change is made.
318      * @param kvList is a comma-separated key=value list.
319      * @return true for success
320      */
321     @VisibleForTesting
update(String kvList)322     public boolean update(String kvList) {
323         if (TextUtils.isEmpty(kvList)) {
324             return true;
325         }
326         if (!("," + kvList).matches(COMMA_KEY_VAL_STAR)) {
327             return false;
328         }
329         loadResources(mContext);
330         Values v = new Values(mVal);
331         try {
332             v.parseString(kvList);
333             v.validate();
334             mVal = v;
335             return true;
336         } catch (IllegalArgumentException e) {
337             return false;
338         }
339     }
340 
341     /**
342      * Sanitize a string to make it safe for printing.
343      * @param params is the untrusted string
344      * @return string with questionable characters replaced with question marks
345      */
sanitize(String params)346     public String sanitize(String params) {
347         if (params == null) return "";
348         String printable = params.replaceAll("[^A-Za-z_0-9=,:.+-]", "?");
349         if (printable.length() > 100) {
350             printable = printable.substring(0, 98) + "...";
351         }
352         return printable;
353     }
354 
355     /**
356      * Returns the RSSI value at which the connection is deemed to be unusable,
357      * in the absence of other indications.
358      */
getExitRssi(int frequencyMegaHertz)359     public int getExitRssi(int frequencyMegaHertz) {
360         return getRssiArray(frequencyMegaHertz)[EXIT];
361     }
362 
363     /**
364      * Returns the minimum scan RSSI for making a connection attempt.
365      */
getEntryRssi(int frequencyMegaHertz)366     public int getEntryRssi(int frequencyMegaHertz) {
367         return getRssiArray(frequencyMegaHertz)[ENTRY];
368     }
369 
370     /**
371      * Returns a connected RSSI value that indicates the connection is
372      * good enough that we needn't scan for alternatives.
373      */
getSufficientRssi(int frequencyMegaHertz)374     public int getSufficientRssi(int frequencyMegaHertz) {
375         return getRssiArray(frequencyMegaHertz)[SUFFICIENT];
376     }
377 
378     /**
379      * Returns a connected RSSI value that indicates a good connection.
380      */
getGoodRssi(int frequencyMegaHertz)381     public int getGoodRssi(int frequencyMegaHertz) {
382         return getRssiArray(frequencyMegaHertz)[GOOD];
383     }
384 
385     /**
386      * Returns the number of seconds to use for rssi forecast.
387      */
getHorizonSeconds()388     public int getHorizonSeconds() {
389         loadResources(mContext);
390         return mVal.horizon;
391     }
392 
393     /**
394      * Returns a packet rate that should be considered acceptable for staying on wifi,
395      * no matter how bad the RSSI gets (packets per second).
396      */
getYippeeSkippyPacketsPerSecond()397     public int getYippeeSkippyPacketsPerSecond() {
398         loadResources(mContext);
399         return mVal.pps[HIGH_TRAFFIC];
400     }
401 
402     /**
403      * Returns a packet rate that should be considered acceptable to skip scan or network selection
404      */
getActiveTrafficPacketsPerSecond()405     public int getActiveTrafficPacketsPerSecond() {
406         loadResources(mContext);
407         return mVal.pps[ACTIVE_TRAFFIC];
408     }
409 
410     /**
411      * Returns a number between 0 and 10 inclusive that indicates
412      * how aggressive to be about asking for IP configuration checks
413      * (also known as Network Unreachabilty Detection, or NUD).
414      *
415      * 0 - no nud checks requested by scorer (framework still checks after roam)
416      * 1 - check when score becomes very low
417      *     ...
418      * 10 - check when score first breaches threshold, and again as it gets worse
419      *
420      */
getNudKnob()421     public int getNudKnob() {
422         loadResources(mContext);
423         return mVal.nud;
424     }
425 
426     /**
427      * Returns the estimate rssi error margin to account minor differences in the environment
428      * and the device's orientation.
429      *
430      */
getEstimateRssiErrorMargin()431     public int getEstimateRssiErrorMargin() {
432         return mVal.estimateRssiErrorMargin;
433     }
434 
435     /**
436      */
getThroughputBonusNumerator()437     public int getThroughputBonusNumerator() {
438         return mVal.throughputBonusNumerator;
439     }
440 
441     /**
442      */
getThroughputBonusDenominator()443     public int getThroughputBonusDenominator() {
444         return mVal.throughputBonusDenominator;
445     }
446 
447     /**
448      * Getter for throughput numerator after 800Mbps.
449      */
getThroughputBonusNumeratorAfter800Mbps()450     public int getThroughputBonusNumeratorAfter800Mbps() {
451         return mVal.throughputBonusNumeratorAfter800Mbps;
452     }
453 
454     /**
455      * Getter for throughput denominator after 800Mbps.
456      */
getThroughputBonusDenominatorAfter800Mbps()457     public int getThroughputBonusDenominatorAfter800Mbps() {
458         return mVal.throughputBonusDenominatorAfter800Mbps;
459     }
460 
461     /**
462      * Feature flag for boosting 6Ghz RSSI based on channel width.
463      */
is6GhzBeaconRssiBoostEnabled()464     public boolean is6GhzBeaconRssiBoostEnabled() {
465         return mVal.enable6GhzBeaconRssiBoost;
466     }
467 
468     /*
469      * Returns the maximum bonus for the network selection candidate score
470      * for the contribution of the selected score.
471      */
getThroughputBonusLimit()472     public int getThroughputBonusLimit() {
473         return mVal.throughputBonusLimit;
474     }
475 
476     /*
477      * Returns the bonus for the network selection candidate score
478      * for a saved network (i.e., not a suggestion).
479      */
getSavedNetworkBonus()480     public int getSavedNetworkBonus() {
481         return mVal.savedNetworkBonus;
482     }
483 
484     /*
485      * Returns the bonus for the network selection candidate score
486      * for an unmetered network.
487      */
getUnmeteredNetworkBonus()488     public int getUnmeteredNetworkBonus() {
489         return mVal.unmeteredNetworkBonus;
490     }
491 
492     /*
493      * Returns the minimum bonus for the network selection candidate score
494      * for the currently connected network.
495      */
getCurrentNetworkBonusMin()496     public int getCurrentNetworkBonusMin() {
497         return mVal.currentNetworkBonusMin;
498     }
499 
500     /*
501      * Returns the percentage bonus for the network selection candidate score
502      * for the currently connected network. The percent value is applied to rssi score and
503      * throughput score;
504      */
getCurrentNetworkBonusPercent()505     public int getCurrentNetworkBonusPercent() {
506         return mVal.currentNetworkBonusPercent;
507     }
508 
509     /*
510      * Returns the bonus for the network selection candidate score
511      * for a secure network.
512      */
getSecureNetworkBonus()513     public int getSecureNetworkBonus() {
514         return mVal.secureNetworkBonus;
515     }
516 
517     /**
518      * Returns the bonus given if the network belongs to the 6Ghz band.
519      */
getBand6GhzBonus()520     public int getBand6GhzBonus() {
521         return mVal.band6GhzBonus;
522     }
523 
524     /**
525      * Returns the expected amount of score to reach the next tier during candidate scoring. This
526      * value should be configured according to the value of parameters that determine the
527      * scoring buckets such as {@code config_wifiFrameworkSavedNetworkBonus} and
528      * {@code config_wifiFrameworkUnmeteredNetworkBonus}.
529      */
getScoringBucketStepSize()530     public int getScoringBucketStepSize() {
531         return mVal.scoringBucketStepSize;
532     }
533 
534     /*
535      * Returns the duration in minutes for a recently selected network
536      * to be strongly favored.
537      */
getLastSelectionMinutes()538     public int getLastSelectionMinutes() {
539         return mVal.lastSelectionMinutes;
540     }
541 
542     /**
543      * Returns the experiment identifier.
544      *
545      * This value may be used to tag a set of experimental settings.
546      */
getExperimentIdentifier()547     public int getExperimentIdentifier() {
548         loadResources(mContext);
549         return mVal.expid;
550     }
551 
getRssiArray(int frequency)552     private int[] getRssiArray(int frequency) {
553         loadResources(mContext);
554         if (ScanResult.is24GHz(frequency)) {
555             return mVal.rssi2;
556         } else if (ScanResult.is5GHz(frequency)) {
557             return mVal.rssi5;
558         } else if (ScanResult.is6GHz(frequency)) {
559             return mVal.rssi6;
560         }
561         // Invalid frequency use
562         Log.e(TAG, "Invalid frequency(" + frequency + "), using 5G as default rssi array");
563         return mVal.rssi5;
564     }
565 
566     @Override
toString()567     public String toString() {
568         loadResources(mContext);
569         return mVal.toString();
570     }
571 }
572