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 android.annotation.NonNull; 20 import android.net.wifi.ScanResult; 21 22 import com.android.server.wifi.WifiCandidates.Candidate; 23 import com.android.server.wifi.WifiCandidates.ScoredCandidate; 24 25 import java.util.Collection; 26 27 /** 28 * A CandidateScorer that weights the RSSIs for more compactly-shaped 29 * regions of selection around access points. 30 */ 31 final class BubbleFunScorer implements WifiCandidates.CandidateScorer { 32 33 /** 34 * This should match WifiNetworkSelector.experimentIdFromIdentifier(getIdentifier()) 35 * when using the default ScoringParams. 36 */ 37 public static final int BUBBLE_FUN_SCORER_DEFAULT_EXPID = 42598152; 38 39 private static final double SECURITY_AWARD = 44.0; 40 private static final double CURRENT_NETWORK_BOOST = 22.0; 41 private static final double LAST_SELECTION_BOOST = 250.0; 42 private static final double LOW_BAND_FACTOR = 0.25; 43 private static final double TYPICAL_SCAN_RSSI_STD = 4.0; 44 private static final boolean USE_USER_CONNECT_CHOICE = true; 45 46 private final ScoringParams mScoringParams; 47 BubbleFunScorer(ScoringParams scoringParams)48 BubbleFunScorer(ScoringParams scoringParams) { 49 mScoringParams = scoringParams; 50 } 51 52 @Override getIdentifier()53 public String getIdentifier() { 54 return "BubbleFunScorer_v2"; 55 } 56 57 /** 58 * Calculates an individual candidate's score. 59 * 60 * Ideally, this is a pure function of the candidate, and side-effect free. 61 */ scoreCandidate(Candidate candidate)62 private ScoredCandidate scoreCandidate(Candidate candidate) { 63 final int rssi = candidate.getScanRssi(); 64 final int rssiEntryThreshold = mScoringParams.getEntryRssi(candidate.getFrequency()); 65 66 double score = shapeFunction(rssi) - shapeFunction(rssiEntryThreshold); 67 68 // If we are below the entry threshold, make the score more negative 69 if (score < 0.0) score *= 2.0; 70 71 // The gain is approximately the derivative of shapeFunction at the given rssi 72 // This is used to estimate the error 73 double gain = shapeFunction(rssi + 0.5) 74 - shapeFunction(rssi - 0.5); 75 76 // Prefer 5GHz/6GHz when all are strong, but at the fringes, 2.4 might be better 77 // Typically the entry rssi is lower for the 2.4 band, which provides the fringe boost 78 if (ScanResult.is24GHz(candidate.getFrequency())) { 79 score *= LOW_BAND_FACTOR; 80 gain *= LOW_BAND_FACTOR; 81 } 82 83 // A recently selected network gets a large boost 84 score += candidate.getLastSelectionWeight() * LAST_SELECTION_BOOST; 85 86 // Hysteresis to prefer staying on the current network. 87 if (candidate.isCurrentNetwork()) { 88 score += CURRENT_NETWORK_BOOST; 89 } 90 91 if (!candidate.isOpenNetwork()) { 92 score += SECURITY_AWARD; 93 } 94 95 return new ScoredCandidate(score, TYPICAL_SCAN_RSSI_STD * gain, 96 USE_USER_CONNECT_CHOICE, candidate); 97 } 98 99 /** 100 * Reshapes raw RSSI into a value that varies more usefully for scoring purposes. 101 * 102 * The most important aspect of this function is that it is monotone (has 103 * positive slope). The offset and scale are not important, because the 104 * calculation above uses differences that cancel out the offset, and 105 * a rescaling here effects all the candidates' scores in the same way. 106 * However, we choose to scale things for an overall range of about 100 for 107 * useful values of RSSI. 108 */ unscaledShapeFunction(double rssi)109 private static double unscaledShapeFunction(double rssi) { 110 return -Math.exp(-rssi * BELS_PER_DECIBEL); 111 } 112 private static final double BELS_PER_DECIBEL = 0.1; 113 114 private static final double RESCALE_FACTOR = 100.0 / ( 115 unscaledShapeFunction(0.0) - unscaledShapeFunction(-85.0)); shapeFunction(double rssi)116 private static double shapeFunction(double rssi) { 117 return unscaledShapeFunction(rssi) * RESCALE_FACTOR; 118 } 119 120 @Override scoreCandidates(@onNull Collection<Candidate> candidates)121 public ScoredCandidate scoreCandidates(@NonNull Collection<Candidate> candidates) { 122 ScoredCandidate choice = ScoredCandidate.NONE; 123 for (Candidate candidate : candidates) { 124 ScoredCandidate scoredCandidate = scoreCandidate(candidate); 125 if (scoredCandidate.value > choice.value) { 126 choice = scoredCandidate; 127 } 128 } 129 // Here we just return the highest scored candidate; we could 130 // compute a new score, if desired. 131 return choice; 132 } 133 134 } 135