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