1 /*
2  * Copyright 2019 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 static com.android.server.wifi.WifiNetworkSelector.NetworkNominator.NOMINATOR_ID_SCORED;
20 
21 import android.annotation.NonNull;
22 import android.util.Log;
23 
24 import com.android.server.wifi.WifiCandidates.Candidate;
25 import com.android.server.wifi.WifiCandidates.ScoredCandidate;
26 
27 import java.util.Collection;
28 
29 /**
30  * A candidate scorer that combines RSSI base score and network throughput score.
31  */
32 final class ThroughputScorer implements WifiCandidates.CandidateScorer {
33     private static final String TAG = "ThroughputScorer";
34     private static final boolean DBG = false;
35     /**
36      * This should match WifiNetworkSelector.experimentIdFromIdentifier(getIdentifier())
37      * when using the default ScoringParams.
38      */
39     public static final int THROUGHPUT_SCORER_DEFAULT_EXPID = 42330058;
40 
41     /**
42      * Base score that is large enough to override all of the other categories.
43      * This is applied to the last-select network for a limited duration.
44      */
45     public static final int TOP_TIER_BASE_SCORE = 1_000_000;
46 
47     private final ScoringParams mScoringParams;
48 
49     // config_wifi_framework_RSSI_SCORE_OFFSET
50     public static final int RSSI_SCORE_OFFSET = 85;
51 
52     // config_wifi_framework_RSSI_SCORE_SLOPE
53     public static final int RSSI_SCORE_SLOPE_IS_4 = 4;
54 
55     /**
56      * Sample scoring buckets (assumes default overlay bucket sizes for metered, saved, etc):
57      * 0 -> 500: OEM private
58      * 500 -> 1000: OEM paid
59      * 1000 -> 1500: untrusted 3rd party
60      * 1500 -> 2000: untrusted carrier
61      * 2000 -> 2500: metered suggestions
62      * 2500 -> 3000: metered saved
63      * 3000 -> 3500: unmetered suggestions
64      * 3500 -> 4000: unmetered saved
65      */
66     public static final int TRUSTED_AWARD = 1000;
67     public static final int HALF_TRUSTED_AWARD = 1000 / 2;
68     public static final int NOT_OEM_PAID_AWARD = 500;
69     public static final int NOT_OEM_PRIVATE_AWARD = 500;
70 
71     private static final boolean USE_USER_CONNECT_CHOICE = true;
72 
ThroughputScorer(ScoringParams scoringParams)73     ThroughputScorer(ScoringParams scoringParams) {
74         mScoringParams = scoringParams;
75     }
76 
77     @Override
getIdentifier()78     public String getIdentifier() {
79         return "ThroughputScorer";
80     }
81 
82     /**
83      * Calculates an individual candidate's score.
84      */
scoreCandidate(Candidate candidate, boolean currentNetworkHasInternet)85     private ScoredCandidate scoreCandidate(Candidate candidate, boolean currentNetworkHasInternet) {
86         int rssiSaturationThreshold = mScoringParams.getSufficientRssi(candidate.getFrequency());
87         int rssi = Math.min(candidate.getScanRssi(), rssiSaturationThreshold);
88         int rssiBaseScore = (rssi + RSSI_SCORE_OFFSET) * RSSI_SCORE_SLOPE_IS_4;
89 
90         int throughputBonusScore = calculateThroughputBonusScore(candidate);
91 
92         int rssiAndThroughputScore = rssiBaseScore + throughputBonusScore;
93 
94         boolean unExpectedNoInternet = candidate.hasNoInternetAccess()
95                 && !candidate.isNoInternetAccessExpected();
96         int currentNetworkBonusMin = mScoringParams.getCurrentNetworkBonusMin();
97         int currentNetworkBonus = Math.max(currentNetworkBonusMin, rssiAndThroughputScore
98                 * mScoringParams.getCurrentNetworkBonusPercent() / 100);
99         int currentNetworkBoost = (candidate.isCurrentNetwork() && !unExpectedNoInternet)
100                 ? currentNetworkBonus : 0;
101 
102         int securityAward = candidate.isOpenNetwork()
103                 ? 0
104                 : mScoringParams.getSecureNetworkBonus();
105 
106         int unmeteredAward = candidate.isMetered()
107                 ? 0
108                 : mScoringParams.getUnmeteredNetworkBonus();
109 
110         int savedNetworkAward = candidate.isEphemeral() ? 0 : mScoringParams.getSavedNetworkBonus();
111 
112         int trustedAward = TRUSTED_AWARD;
113         if (!candidate.isTrusted()) {
114             savedNetworkAward = 0; // Saved networks are not untrusted, but clear anyway
115             unmeteredAward = 0; // Ignore metered for untrusted networks
116             if (candidate.isCarrierOrPrivileged()) {
117                 trustedAward = HALF_TRUSTED_AWARD;
118             } else if (candidate.getNominatorId() == NOMINATOR_ID_SCORED) {
119                 Log.e(TAG, "ScoredNetworkNominator is not carrier or privileged!");
120                 trustedAward = 0;
121             } else {
122                 trustedAward = 0;
123             }
124         }
125 
126         int notOemPaidAward = NOT_OEM_PAID_AWARD;
127         if (candidate.isOemPaid()) {
128             savedNetworkAward = 0; // Saved networks are not oem paid, but clear anyway
129             unmeteredAward = 0; // Ignore metered for oem paid networks
130             trustedAward = 0; // Ignore untrusted for oem paid networks.
131             notOemPaidAward = 0;
132         }
133 
134         int notOemPrivateAward = NOT_OEM_PRIVATE_AWARD;
135         if (candidate.isOemPrivate()) {
136             savedNetworkAward = 0; // Saved networks are not oem paid, but clear anyway
137             unmeteredAward = 0; // Ignore metered for oem paid networks
138             trustedAward = 0; // Ignore untrusted for oem paid networks.
139             notOemPaidAward = 0;
140             notOemPrivateAward = 0;
141         }
142 
143         int score = rssiBaseScore + throughputBonusScore
144                 + currentNetworkBoost + securityAward + unmeteredAward + savedNetworkAward
145                 + trustedAward + notOemPaidAward + notOemPrivateAward;
146 
147         // do not select a network that has no internet when the current network has internet.
148         if (currentNetworkHasInternet && !candidate.isCurrentNetwork() && unExpectedNoInternet) {
149             score = 0;
150         }
151 
152         if (candidate.getLastSelectionWeight() > 0.0) {
153             // Put a recently-selected network in a tier above everything else,
154             // but include rssi and throughput contributions for BSSID selection.
155             score = TOP_TIER_BASE_SCORE + rssiBaseScore + throughputBonusScore;
156         }
157 
158         if (DBG) {
159             Log.d(TAG, " rssiScore: " + rssiBaseScore
160                     + " throughputScore: " + throughputBonusScore
161                     + " currentNetworkBoost: " + currentNetworkBoost
162                     + " securityAward: " + securityAward
163                     + " unmeteredAward: " + unmeteredAward
164                     + " savedNetworkAward: " + savedNetworkAward
165                     + " trustedAward: " + trustedAward
166                     + " notOemPaidAward: " + notOemPaidAward
167                     + " notOemPrivateAward: " + notOemPrivateAward
168                     + " final score: " + score);
169         }
170 
171         // The old method breaks ties on the basis of RSSI, which we can
172         // emulate easily since our score does not need to be an integer.
173         double tieBreaker = candidate.getScanRssi() / 1000.0;
174         return new ScoredCandidate(score + tieBreaker, 10,
175                 USE_USER_CONNECT_CHOICE, candidate);
176     }
177 
calculateThroughputBonusScore(Candidate candidate)178     private int calculateThroughputBonusScore(Candidate candidate) {
179         int throughputScoreRaw = candidate.getPredictedThroughputMbps()
180                 * mScoringParams.getThroughputBonusNumerator()
181                 / mScoringParams.getThroughputBonusDenominator();
182         return Math.min(throughputScoreRaw, mScoringParams.getThroughputBonusLimit());
183     }
184 
doesAnyCurrentNetworksHaveInternet(@onNull Collection<Candidate> candidates)185     private boolean doesAnyCurrentNetworksHaveInternet(@NonNull Collection<Candidate> candidates) {
186         for (Candidate candidate : candidates) {
187             if (candidate.isCurrentNetwork() && !candidate.hasNoInternetAccess()) {
188                 return true;
189             }
190         }
191         return false;
192     }
193 
194     @Override
scoreCandidates(@onNull Collection<Candidate> candidates)195     public ScoredCandidate scoreCandidates(@NonNull Collection<Candidate> candidates) {
196         ScoredCandidate choice = ScoredCandidate.NONE;
197         boolean currentNetworkHasInternet = doesAnyCurrentNetworksHaveInternet(candidates);
198         for (Candidate candidate : candidates) {
199             ScoredCandidate scoredCandidate = scoreCandidate(candidate, currentNetworkHasInternet);
200             if (scoredCandidate.value > choice.value) {
201                 choice = scoredCandidate;
202             }
203         }
204         // Here we just return the highest scored candidate; we could
205         // compute a new score, if desired.
206         return choice;
207     }
208 
209 }
210