summaryrefslogtreecommitdiffstats
path: root/service/java/com/android/server/wifi/BubbleFunScorer.java
blob: 497c22dce58e57249666d8a14dfcd22e156046c2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/*
 * 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<Candidate> 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;
    }

}