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