/* * Copyright 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wifi; import android.annotation.NonNull; import com.android.server.wifi.WifiCandidates.Candidate; import com.android.server.wifi.WifiCandidates.ScoredCandidate; import java.util.Collection; /** * A CandidateScorer that weights the RSSIs for more compactly-shaped * regions of selection around access points. */ final class BubbleFunScorer implements WifiCandidates.CandidateScorer { /** * This should match WifiNetworkSelector.experimentIdFromIdentifier(getIdentifier()) * when using the default ScoringParams. */ public static final int BUBBLE_FUN_SCORER_DEFAULT_EXPID = 42598151; private static final double SECURITY_AWARD = 80.0; private static final double CURRENT_NETWORK_BOOST = 80.0; private static final double LOW_BAND_FACTOR = 0.3; private static final double TYPICAL_SCAN_RSSI_STD = 4.0; private final ScoringParams mScoringParams; BubbleFunScorer(ScoringParams scoringParams) { mScoringParams = scoringParams; } @Override public String getIdentifier() { return "BubbleFunScorer_v1"; } /** * Calculates an individual candidate's score. * * Ideally, this is a pure function of the candidate, and side-effect free. */ private ScoredCandidate scoreCandidate(Candidate candidate) { final int rssi = candidate.getScanRssi(); final int rssiEntryThreshold = mScoringParams.getEntryRssi(candidate.getFrequency()); double score = shapeFunction(rssi) - shapeFunction(rssiEntryThreshold); // If we are below the entry threshold, make the score more negative if (score < 0.0) score *= 10.0; // A recently selected network gets a large boost score += candidate.getLastSelectionWeight() * CURRENT_NETWORK_BOOST; // Hysteresis to prefer staying on the current network. if (candidate.isCurrentNetwork()) { score += CURRENT_NETWORK_BOOST; } if (!candidate.isOpenNetwork()) { score += SECURITY_AWARD; } // The gain is approximately the derivative of shapeFunction at the given rssi // This is used to estimate the error double gain = shapeFunction(rssi + 0.5) - shapeFunction(rssi - 0.5); // Prefer 5GHz when both are strong, but at the fringes, 2.4 might be better // Typically the entry rssi is lower for the 2.4 band, which provides the fringe boost if (candidate.getFrequency() < ScoringParams.MINIMUM_5GHZ_BAND_FREQUENCY_IN_MEGAHERTZ) { score *= LOW_BAND_FACTOR; gain *= LOW_BAND_FACTOR; } return new ScoredCandidate(score, TYPICAL_SCAN_RSSI_STD * gain, candidate); } /** * Reshapes raw RSSI into a value that varies more usefully for scoring purposes. * * The most important aspect of this function is that it is monotone (has * positive slope). The offset and scale are not important, because the * calculation above uses differences that cancel out the offset, and * a rescaling here effects all the candidates' scores in the same way. * However, we choose to scale things for an overall range of about 100 for * useful values of RSSI. */ private static double unscaledShapeFunction(double rssi) { return -Math.exp(-rssi * BELS_PER_DECIBEL); } private static final double BELS_PER_DECIBEL = 0.1; private static final double RESCALE_FACTOR = 100.0 / ( unscaledShapeFunction(0.0) - unscaledShapeFunction(-85.0)); private static double shapeFunction(double rssi) { return unscaledShapeFunction(rssi) * RESCALE_FACTOR; } @Override public ScoredCandidate scoreCandidates(@NonNull Collection group) { ScoredCandidate choice = ScoredCandidate.NONE; for (Candidate candidate : group) { ScoredCandidate scoredCandidate = scoreCandidate(candidate); if (scoredCandidate.value > choice.value) { choice = scoredCandidate; } } // Here we just return the highest scored candidate; we could // compute a new score, if desired. return choice; } @Override public boolean userConnectChoiceOverrideWanted() { return true; } }