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