SavedNetworkEvaluator.java revision 03c23584f072aef576736044c1fa12ddcb2d882b
1/*
2 * Copyright (C) 2016 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
17package com.android.server.wifi;
18
19import android.content.Context;
20import android.net.wifi.ScanResult;
21import android.net.wifi.WifiConfiguration;
22import android.util.LocalLog;
23import android.util.Pair;
24
25import com.android.internal.R;
26import com.android.server.wifi.util.TelephonyUtil;
27
28import java.util.ArrayList;
29import java.util.Arrays;
30import java.util.List;
31
32/**
33 * This class is the WifiNetworkSelector.NetworkEvaluator implementation for
34 * saved networks.
35 */
36public class SavedNetworkEvaluator implements WifiNetworkSelector.NetworkEvaluator {
37    private static final String NAME = "SavedNetworkEvaluator";
38    private final WifiConfigManager mWifiConfigManager;
39    private final Clock mClock;
40    private final LocalLog mLocalLog;
41    private final WifiConnectivityHelper mConnectivityHelper;
42    private final int mRssiScoreSlope;
43    private final int mRssiScoreOffset;
44    private final int mSameBssidAward;
45    private final int mSameNetworkAward;
46    private final int mBand5GHzAward;
47    private final int mLastSelectionAward;
48    private final int mSecurityAward;
49    private final int mThresholdSaturatedRssi24;
50    private final int mThresholdSaturatedRssi5;
51
52    SavedNetworkEvaluator(final Context context, WifiConfigManager configManager, Clock clock,
53            LocalLog localLog, WifiConnectivityHelper connectivityHelper) {
54        mWifiConfigManager = configManager;
55        mClock = clock;
56        mLocalLog = localLog;
57        mConnectivityHelper = connectivityHelper;
58
59        mRssiScoreSlope = context.getResources().getInteger(
60                R.integer.config_wifi_framework_RSSI_SCORE_SLOPE);
61        mRssiScoreOffset = context.getResources().getInteger(
62                R.integer.config_wifi_framework_RSSI_SCORE_OFFSET);
63        mSameBssidAward = context.getResources().getInteger(
64                R.integer.config_wifi_framework_SAME_BSSID_AWARD);
65        mSameNetworkAward = context.getResources().getInteger(
66                R.integer.config_wifi_framework_current_network_boost);
67        mLastSelectionAward = context.getResources().getInteger(
68                R.integer.config_wifi_framework_LAST_SELECTION_AWARD);
69        mSecurityAward = context.getResources().getInteger(
70                R.integer.config_wifi_framework_SECURITY_AWARD);
71        mBand5GHzAward = context.getResources().getInteger(
72                R.integer.config_wifi_framework_5GHz_preference_boost_factor);
73        mThresholdSaturatedRssi24 = context.getResources().getInteger(
74                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz);
75        mThresholdSaturatedRssi5 = context.getResources().getInteger(
76                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz);
77    }
78
79    private void localLog(String log) {
80        mLocalLog.log(log);
81    }
82
83    /**
84     * Get the evaluator name.
85     */
86    public String getName() {
87        return NAME;
88    }
89
90    /**
91     * Update all the saved networks' selection status
92     */
93    private void updateSavedNetworkSelectionStatus() {
94        List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks();
95        if (savedNetworks.size() == 0) {
96            localLog("No saved networks.");
97            return;
98        }
99
100        StringBuffer sbuf = new StringBuffer();
101        for (WifiConfiguration network : savedNetworks) {
102            /**
103             * Ignore Passpoint networks. Passpoint networks are also considered as "saved"
104             * network, but without being persisted to the storage. They are managed
105             * by {@link PasspointNetworkEvaluator}.
106             */
107            if (network.isPasspoint()) {
108                continue;
109            }
110
111            WifiConfiguration.NetworkSelectionStatus status =
112                    network.getNetworkSelectionStatus();
113
114            // If a configuration is temporarily disabled, re-enable it before trying
115            // to connect to it.
116            mWifiConfigManager.tryEnableNetwork(network.networkId);
117
118            //TODO(b/30928589): Enable "permanently" disabled networks if we are in DISCONNECTED
119            // state.
120
121            // Clear the cached candidate, score and seen.
122            mWifiConfigManager.clearNetworkCandidateScanResult(network.networkId);
123
124            boolean networkDisabled = false;
125            boolean networkStringLogged = false;
126            for (int index = WifiConfiguration.NetworkSelectionStatus
127                    .NETWORK_SELECTION_DISABLED_STARTING_INDEX;
128                    index < WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_DISABLED_MAX;
129                    index++) {
130                int count = status.getDisableReasonCounter(index);
131                if (count > 0) {
132                    networkDisabled = true;
133                    if (!networkStringLogged) {
134                        sbuf.append("  ").append(WifiNetworkSelector.toNetworkString(network))
135                                .append(" ");
136                        networkStringLogged = true;
137                    }
138                    sbuf.append("reason=")
139                            .append(WifiConfiguration.NetworkSelectionStatus
140                                    .getNetworkDisableReasonString(index))
141                            .append(", count=").append(count).append("; ");
142                }
143            }
144
145            if (networkDisabled) {
146                sbuf.append("\n");
147            }
148        }
149
150        if (sbuf.length() > 0) {
151            localLog("Disabled saved networks:");
152            localLog(sbuf.toString());
153        }
154    }
155
156    /**
157     * Update the evaluator.
158     */
159    public void update(List<ScanDetail> scanDetails) {
160        updateSavedNetworkSelectionStatus();
161    }
162
163    private int calculateBssidScore(ScanResult scanResult, WifiConfiguration network,
164                        WifiConfiguration currentNetwork, String currentBssid,
165                        StringBuffer sbuf) {
166        int score = 0;
167        boolean is5GHz = scanResult.is5GHz();
168
169        sbuf.append("[ ").append(scanResult.SSID).append(" ").append(scanResult.BSSID)
170                .append(" RSSI:").append(scanResult.level).append(" ] ");
171        // Calculate the RSSI score.
172        int rssiSaturationThreshold = is5GHz ? mThresholdSaturatedRssi5 : mThresholdSaturatedRssi24;
173        int rssi = scanResult.level < rssiSaturationThreshold ? scanResult.level
174                : rssiSaturationThreshold;
175        score += (rssi + mRssiScoreOffset) * mRssiScoreSlope;
176        sbuf.append(" RSSI score: ").append(score).append(",");
177
178        // 5GHz band bonus.
179        if (is5GHz) {
180            score += mBand5GHzAward;
181            sbuf.append(" 5GHz bonus: ").append(mBand5GHzAward).append(",");
182        }
183
184        // Last user selection award.
185        int lastUserSelectedNetworkId = mWifiConfigManager.getLastSelectedNetwork();
186        if (lastUserSelectedNetworkId != WifiConfiguration.INVALID_NETWORK_ID
187                && lastUserSelectedNetworkId == network.networkId) {
188            long timeDifference = mClock.getElapsedSinceBootMillis()
189                    - mWifiConfigManager.getLastSelectedTimeStamp();
190            if (timeDifference > 0) {
191                int bonus = mLastSelectionAward - (int) (timeDifference / 1000 / 60);
192                score += bonus > 0 ? bonus : 0;
193                sbuf.append(" User selection ").append(timeDifference / 1000 / 60)
194                        .append(" minutes ago, bonus: ").append(bonus).append(",");
195            }
196        }
197
198        // Same network award.
199        if (currentNetwork != null
200                && (network.networkId == currentNetwork.networkId
201                //TODO(b/36788683): re-enable linked configuration check
202                /* || network.isLinked(currentNetwork) */)) {
203            score += mSameNetworkAward;
204            sbuf.append(" Same network bonus: ").append(mSameNetworkAward).append(",");
205
206            // When firmware roaming is supported, equivalent BSSIDs (the ones under the
207            // same network as the currently connected one) get the same BSSID award.
208            if (mConnectivityHelper.isFirmwareRoamingSupported()
209                    && currentBssid != null && !currentBssid.equals(scanResult.BSSID)) {
210                score += mSameBssidAward;
211                sbuf.append(" Equivalent BSSID bonus: ").append(mSameBssidAward).append(",");
212            }
213        }
214
215        // Same BSSID award.
216        if (currentBssid != null && currentBssid.equals(scanResult.BSSID)) {
217            score += mSameBssidAward;
218            sbuf.append(" Same BSSID bonus: ").append(mSameBssidAward).append(",");
219        }
220
221        // Security award.
222        if (!WifiConfigurationUtil.isConfigForOpenNetwork(network)) {
223            score += mSecurityAward;
224            sbuf.append(" Secure network bonus: ").append(mSecurityAward).append(",");
225        }
226
227        sbuf.append(" ## Total score: ").append(score).append("\n");
228
229        return score;
230    }
231
232    /**
233     * Evaluate all the networks from the scan results and return
234     * the WifiConfiguration of the network chosen for connection.
235     *
236     * @return configuration of the chosen network;
237     *         null if no network in this category is available.
238     */
239    public WifiConfiguration evaluateNetworks(List<ScanDetail> scanDetails,
240                    WifiConfiguration currentNetwork, String currentBssid, boolean connected,
241                    boolean untrustedNetworkAllowed,
242                    List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks) {
243        int highestScore = Integer.MIN_VALUE;
244        ScanResult scanResultCandidate = null;
245        WifiConfiguration candidate = null;
246        StringBuffer scoreHistory = new StringBuffer();
247
248        for (ScanDetail scanDetail : scanDetails) {
249            ScanResult scanResult = scanDetail.getScanResult();
250            int highestScoreOfScanResult = Integer.MIN_VALUE;
251            int candidateIdOfScanResult = WifiConfiguration.INVALID_NETWORK_ID;
252
253            // One ScanResult can be associated with more than one networks, hence we calculate all
254            // the scores and use the highest one as the ScanResult's score.
255            List<WifiConfiguration> associatedConfigurations = null;
256            WifiConfiguration associatedConfiguration =
257                    mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(scanDetail);
258
259            if (associatedConfiguration == null) {
260                continue;
261            } else {
262                associatedConfigurations =
263                    new ArrayList<>(Arrays.asList(associatedConfiguration));
264            }
265
266            for (WifiConfiguration network : associatedConfigurations) {
267                /**
268                 * Ignore Passpoint networks. Passpoint networks are also considered as "saved"
269                 * network, but without being persisted to the storage. They are being evaluated
270                 * by {@link PasspointNetworkEvaluator}.
271                 */
272                if (network.isPasspoint()) {
273                    continue;
274                }
275
276                WifiConfiguration.NetworkSelectionStatus status =
277                        network.getNetworkSelectionStatus();
278                status.setSeenInLastQualifiedNetworkSelection(true);
279
280                if (!status.isNetworkEnabled()) {
281                    continue;
282                } else if (network.BSSID != null &&  !network.BSSID.equals("any")
283                        && !network.BSSID.equals(scanResult.BSSID)) {
284                    // App has specified the only BSSID to connect for this
285                    // configuration. So only the matching ScanResult can be a candidate.
286                    localLog("Network " + WifiNetworkSelector.toNetworkString(network)
287                            + " has specified BSSID " + network.BSSID + ". Skip "
288                            + scanResult.BSSID);
289                    continue;
290                } else if (TelephonyUtil.isSimConfig(network)
291                        && !mWifiConfigManager.isSimPresent()) {
292                    // Don't select if security type is EAP SIM/AKA/AKA' when SIM is not present.
293                    continue;
294                }
295
296                int score = calculateBssidScore(scanResult, network, currentNetwork, currentBssid,
297                        scoreHistory);
298
299                // Set candidate ScanResult for all saved networks to ensure that users can
300                // override network selection. See WifiNetworkSelector#setUserConnectChoice.
301                // TODO(b/36067705): consider alternative designs to push filtering/selecting of
302                // user connect choice networks to RecommendedNetworkEvaluator.
303                if (score > status.getCandidateScore() || (score == status.getCandidateScore()
304                        && status.getCandidate() != null
305                        && scanResult.level > status.getCandidate().level)) {
306                    mWifiConfigManager.setNetworkCandidateScanResult(
307                            network.networkId, scanResult, score);
308                }
309
310                // If the network is marked to use external scores, or is an open network with
311                // curate saved open networks enabled, do not consider it for network selection.
312                if (network.useExternalScores) {
313                    localLog("Network " + WifiNetworkSelector.toNetworkString(network)
314                            + " has external score.");
315                    continue;
316                }
317
318                if (score > highestScoreOfScanResult) {
319                    highestScoreOfScanResult = score;
320                    candidateIdOfScanResult = network.networkId;
321                }
322            }
323
324            if (connectableNetworks != null) {
325                connectableNetworks.add(Pair.create(scanDetail,
326                        mWifiConfigManager.getConfiguredNetwork(candidateIdOfScanResult)));
327            }
328
329            if (highestScoreOfScanResult > highestScore
330                    || (highestScoreOfScanResult == highestScore
331                    && scanResultCandidate != null
332                    && scanResult.level > scanResultCandidate.level)) {
333                highestScore = highestScoreOfScanResult;
334                scanResultCandidate = scanResult;
335                mWifiConfigManager.setNetworkCandidateScanResult(
336                        candidateIdOfScanResult, scanResultCandidate, highestScore);
337                // Reload the network config with the updated info.
338                candidate = mWifiConfigManager.getConfiguredNetwork(candidateIdOfScanResult);
339            }
340        }
341
342        if (scoreHistory.length() > 0) {
343            localLog("\n" + scoreHistory.toString());
344        }
345
346        if (scanResultCandidate == null) {
347            localLog("did not see any good candidates.");
348        }
349        return candidate;
350    }
351}
352