SavedNetworkEvaluator.java revision e4b4b229331da3964671606f18557b2e7f681b45
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.database.ContentObserver;
21import android.net.wifi.ScanResult;
22import android.net.wifi.WifiConfiguration;
23import android.os.Handler;
24import android.os.Looper;
25import android.provider.Settings;
26import android.util.LocalLog;
27import android.util.Pair;
28
29import com.android.internal.R;
30import com.android.internal.annotations.VisibleForTesting;
31
32import java.util.ArrayList;
33import java.util.Arrays;
34import java.util.List;
35
36/**
37 * This class is the WifiNetworkSelector.NetworkEvaluator implementation for
38 * saved networks.
39 */
40public class SavedNetworkEvaluator implements WifiNetworkSelector.NetworkEvaluator {
41    private static final String NAME = "WifiSavedNetworkEvaluator";
42    private final WifiConfigManager mWifiConfigManager;
43    private final Clock mClock;
44    private final LocalLog mLocalLog;
45    private final int mRssiScoreSlope;
46    private final int mRssiScoreOffset;
47    private final int mSameBssidAward;
48    private final int mSameNetworkAward;
49    private final int mBand5GHzAward;
50    private final int mLastSelectionAward;
51    private final int mPasspointSecurityAward;
52    private final int mSecurityAward;
53    private final int mNoInternetPenalty;
54    private final int mThresholdSaturatedRssi24;
55    @VisibleForTesting final ContentObserver mContentObserver;
56    private boolean mCurateSavedOpenNetworks;
57
58    SavedNetworkEvaluator(final Context context, WifiConfigManager configManager, Clock clock,
59            LocalLog localLog, Looper looper, final FrameworkFacade frameworkFacade) {
60        mWifiConfigManager = configManager;
61        mClock = clock;
62        mLocalLog = localLog;
63
64        mRssiScoreSlope = context.getResources().getInteger(
65                R.integer.config_wifi_framework_RSSI_SCORE_SLOPE);
66        mRssiScoreOffset = context.getResources().getInteger(
67                R.integer.config_wifi_framework_RSSI_SCORE_OFFSET);
68        mSameBssidAward = context.getResources().getInteger(
69                R.integer.config_wifi_framework_SAME_BSSID_AWARD);
70        mSameNetworkAward = context.getResources().getInteger(
71                R.integer.config_wifi_framework_current_network_boost);
72        mLastSelectionAward = context.getResources().getInteger(
73                R.integer.config_wifi_framework_LAST_SELECTION_AWARD);
74        mPasspointSecurityAward = context.getResources().getInteger(
75                R.integer.config_wifi_framework_PASSPOINT_SECURITY_AWARD);
76        mSecurityAward = context.getResources().getInteger(
77                R.integer.config_wifi_framework_SECURITY_AWARD);
78        mBand5GHzAward = context.getResources().getInteger(
79                R.integer.config_wifi_framework_5GHz_preference_boost_factor);
80        mThresholdSaturatedRssi24 = context.getResources().getInteger(
81                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz);
82        mNoInternetPenalty = (mThresholdSaturatedRssi24 + mRssiScoreOffset)
83                * mRssiScoreSlope + mBand5GHzAward + mSameNetworkAward
84                + mSameBssidAward + mSecurityAward;
85        mContentObserver = new ContentObserver(new Handler(looper)) {
86            @Override
87            public void onChange(boolean selfChange) {
88                boolean networkRecommendationsEnabled = frameworkFacade.getIntegerSetting(context,
89                                Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0) == 1;
90                boolean curateSavedOpenNetworks = frameworkFacade.getIntegerSetting(context,
91                        Settings.Global.CURATE_SAVED_OPEN_NETWORKS, 0) == 1;
92                mCurateSavedOpenNetworks = networkRecommendationsEnabled && curateSavedOpenNetworks;
93            }
94        };
95        mContentObserver.onChange(false /* selfChange*/);
96        context.getContentResolver().registerContentObserver(
97                Settings.Global.getUriFor(Settings.Global.CURATE_SAVED_OPEN_NETWORKS),
98                false /* notifyForDescendents */, mContentObserver);
99        context.getContentResolver().registerContentObserver(
100                Settings.Global.getUriFor(Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED),
101                false /* notifyForDescendents */, mContentObserver);
102    }
103
104    private void localLog(String log) {
105        if (mLocalLog != null) {
106            mLocalLog.log(log);
107        }
108    }
109
110    /**
111     * Get the evaluator name.
112     */
113    public String getName() {
114        return NAME;
115    }
116
117    /**
118     * Update all the saved networks' selection status
119     */
120    private void updateSavedNetworkSelectionStatus() {
121        List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks();
122        if (savedNetworks.size() == 0) {
123            localLog("No saved networks.");
124            return;
125        }
126
127        StringBuffer sbuf = new StringBuffer("Saved Networks List: \n");
128        for (WifiConfiguration network : savedNetworks) {
129            WifiConfiguration.NetworkSelectionStatus status =
130                    network.getNetworkSelectionStatus();
131
132            // If a configuration is temporarily disabled, re-enable it before trying
133            // to connect to it.
134            mWifiConfigManager.tryEnableNetwork(network.networkId);
135
136            //TODO(b/30928589): Enable "permanently" disabled networks if we are in DISCONNECTED
137            // state.
138
139            // Clear the cached candidate, score and seen.
140            mWifiConfigManager.clearNetworkCandidateScanResult(network.networkId);
141
142            sbuf.append(" ").append(WifiNetworkSelector.toNetworkString(network)).append(" ")
143                    .append(" User Preferred BSSID: ").append(network.BSSID)
144                    .append(" FQDN: ").append(network.FQDN).append(" ")
145                    .append(status.getNetworkStatusString()).append(" Disable account: ");
146            for (int index = WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE;
147                    index < WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_DISABLED_MAX;
148                    index++) {
149                sbuf.append(status.getDisableReasonCounter(index)).append(" ");
150            }
151            sbuf.append("Connect Choice: ").append(status.getConnectChoice())
152                .append(" set time: ").append(status.getConnectChoiceTimestamp())
153                .append("\n");
154        }
155        localLog(sbuf.toString());
156    }
157
158    /**
159     * Update the evaluator.
160     */
161    public void update(List<ScanDetail> scanDetails) {
162        updateSavedNetworkSelectionStatus();
163    }
164
165    private int calculateBssidScore(ScanResult scanResult, WifiConfiguration network,
166                        WifiConfiguration currentNetwork, String currentBssid,
167                        StringBuffer sbuf) {
168        int score = 0;
169
170        sbuf.append("[ ").append(scanResult).append("] ");
171        // Calculate the RSSI score.
172        int rssi = scanResult.level <= mThresholdSaturatedRssi24
173                ? scanResult.level : mThresholdSaturatedRssi24;
174        score += (rssi + mRssiScoreOffset) * mRssiScoreSlope;
175        sbuf.append(" RSSI score: ").append(score).append(",");
176
177        // 5GHz band bonus.
178        if (scanResult.is5GHz()) {
179            score += mBand5GHzAward;
180            sbuf.append(" 5GHz bonus: ").append(mBand5GHzAward)
181                .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 selected it last time ").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                || network.isLinked(currentNetwork))) {
202            score += mSameNetworkAward;
203            sbuf.append(" Same network the current one bonus: ")
204                    .append(mSameNetworkAward).append(",");
205        }
206
207        // Same BSSID award.
208        if (currentBssid != null && currentBssid.equals(scanResult.BSSID)) {
209            score += mSameBssidAward;
210            sbuf.append(" Same BSSID as the current one bonus: ").append(mSameBssidAward)
211                .append(",");
212        }
213
214        // Security award.
215        if (network.isPasspoint()) {
216            score += mPasspointSecurityAward;
217            sbuf.append(" Passpoint bonus: ").append(mPasspointSecurityAward).append(",");
218        } else if (!WifiConfigurationUtil.isConfigForOpenNetwork(network)) {
219            score += mSecurityAward;
220            sbuf.append(" Secure network bonus: ").append(mSecurityAward).append(",");
221        }
222
223        // No internet penalty.
224        if (network.numNoInternetAccessReports > 0 && !network.validatedInternetAccess) {
225            score -= mNoInternetPenalty;
226            sbuf.append(" No internet penalty: -").append(mNoInternetPenalty).append(",");
227        }
228
229        sbuf.append(" ## Total score: ").append(score).append("\n");
230
231        return score;
232    }
233
234    private WifiConfiguration adjustCandidateWithUserSelection(WifiConfiguration candidate,
235                        ScanResult scanResultCandidate) {
236        WifiConfiguration tempConfig = candidate;
237
238        while (tempConfig.getNetworkSelectionStatus().getConnectChoice() != null) {
239            String key = tempConfig.getNetworkSelectionStatus().getConnectChoice();
240            tempConfig = mWifiConfigManager.getConfiguredNetwork(key);
241
242            if (tempConfig != null) {
243                WifiConfiguration.NetworkSelectionStatus tempStatus =
244                        tempConfig.getNetworkSelectionStatus();
245                if (tempStatus.getCandidate() != null && tempStatus.isNetworkEnabled()) {
246                    scanResultCandidate = tempStatus.getCandidate();
247                    candidate = tempConfig;
248                }
249            } else {
250                localLog("Connect choice: " + key + " has no corresponding saved config.");
251                break;
252            }
253        }
254        localLog("After user selection adjustment, the final candidate is:"
255                + WifiNetworkSelector.toNetworkString(candidate) + " : "
256                + scanResultCandidate.BSSID);
257        return candidate;
258    }
259
260    /**
261     * Evaluate all the networks from the scan results and return
262     * the WifiConfiguration of the network chosen for connection.
263     *
264     * @return configuration of the chosen network;
265     *         null if no network in this category is available.
266     */
267    public WifiConfiguration evaluateNetworks(List<ScanDetail> scanDetails,
268                    WifiConfiguration currentNetwork, String currentBssid, boolean connected,
269                    boolean untrustedNetworkAllowed,
270                    List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks) {
271        int highestScore = Integer.MIN_VALUE;
272        ScanResult scanResultCandidate = null;
273        WifiConfiguration candidate = null;
274        StringBuffer scoreHistory = new StringBuffer();
275
276        for (ScanDetail scanDetail : scanDetails) {
277            ScanResult scanResult = scanDetail.getScanResult();
278            int highestScoreOfScanResult = Integer.MIN_VALUE;
279            int score;
280            int candidateIdOfScanResult = WifiConfiguration.INVALID_NETWORK_ID;
281
282            // One ScanResult can be associated with more than one networks, hence we calculate all
283            // the scores and use the highest one as the ScanResult's score.
284            // TODO(b/31065385): WifiConfigManager does not support passpoint networks currently.
285            // So this list has just one entry always.
286            List<WifiConfiguration> associatedConfigurations = null;
287            WifiConfiguration associatedConfiguration =
288                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail);
289
290            if (associatedConfiguration == null) {
291                continue;
292            } else {
293                associatedConfigurations =
294                    new ArrayList<>(Arrays.asList(associatedConfiguration));
295            }
296
297            for (WifiConfiguration network : associatedConfigurations) {
298                WifiConfiguration.NetworkSelectionStatus status =
299                        network.getNetworkSelectionStatus();
300                status.setSeenInLastQualifiedNetworkSelection(true);
301
302                if (!status.isNetworkEnabled()) {
303                    continue;
304                } else if (network.BSSID != null &&  !network.BSSID.equals("any")
305                        && !network.BSSID.equals(scanResult.BSSID)) {
306                    // App has specified the only BSSID to connect for this
307                    // configuration. So only the matching ScanResult can be a candidate.
308                    localLog("Network " + WifiNetworkSelector.toNetworkString(network)
309                            + " has specified BSSID " + network.BSSID + ". Skip "
310                            + scanResult.BSSID);
311                    continue;
312                }
313
314                // If the network is marked to use external scores, leave it to the
315                // external score evaluator to handle it.
316                if (network.useExternalScores) {
317                    localLog("Network " + WifiNetworkSelector.toNetworkString(network)
318                            + " has external score.");
319                    continue;
320                }
321
322                if (mCurateSavedOpenNetworks
323                        && WifiConfigurationUtil.isConfigForOpenNetwork(network)) {
324                    localLog("Network " + WifiNetworkSelector.toNetworkString(network)
325                            + " is open and CURATE_SAVED_OPEN_NETWORKS is enabled.");
326                    continue;
327                }
328
329                score = calculateBssidScore(scanResult, network, currentNetwork, currentBssid,
330                                               scoreHistory);
331
332                if (score > highestScoreOfScanResult) {
333                    highestScoreOfScanResult = score;
334                    candidateIdOfScanResult = network.networkId;
335                }
336
337                if (score > status.getCandidateScore() || (score == status.getCandidateScore()
338                          && status.getCandidate() != null
339                          && scanResult.level > status.getCandidate().level)) {
340                    mWifiConfigManager.setNetworkCandidateScanResult(
341                            candidateIdOfScanResult, scanResult, score);
342                }
343            }
344
345            if (connectableNetworks != null) {
346                connectableNetworks.add(Pair.create(scanDetail,
347                        mWifiConfigManager.getConfiguredNetwork(candidateIdOfScanResult)));
348            }
349
350            if (highestScoreOfScanResult > highestScore
351                    || (highestScoreOfScanResult == highestScore
352                    && scanResultCandidate != null
353                    && scanResult.level > scanResultCandidate.level)) {
354                highestScore = highestScoreOfScanResult;
355                scanResultCandidate = scanResult;
356                mWifiConfigManager.setNetworkCandidateScanResult(
357                        candidateIdOfScanResult, scanResultCandidate, highestScore);
358                // Reload the network config with the updated info.
359                candidate = mWifiConfigManager.getConfiguredNetwork(candidateIdOfScanResult);
360            }
361        }
362
363        if (scoreHistory.length() > 0) {
364            localLog("\n" + scoreHistory.toString());
365        }
366
367        if (scanResultCandidate != null) {
368            return adjustCandidateWithUserSelection(candidate, scanResultCandidate);
369        } else {
370            localLog("did not see any good candidates.");
371            return null;
372        }
373    }
374}
375