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 
17 package com.android.server.wifi;
18 
19 import android.annotation.NonNull;
20 import android.content.Context;
21 import android.net.wifi.ScanResult;
22 import android.net.wifi.WifiConfiguration;
23 import android.telephony.SubscriptionManager;
24 import android.util.LocalLog;
25 
26 import com.android.internal.R;
27 import com.android.internal.annotations.VisibleForTesting;
28 import com.android.server.wifi.util.TelephonyUtil;
29 
30 import java.util.List;
31 
32 /**
33  * This class is the WifiNetworkSelector.NetworkEvaluator implementation for
34  * saved networks.
35  */
36 public 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 SubscriptionManager mSubscriptionManager;
43     private final int mRssiScoreSlope;
44     private final int mRssiScoreOffset;
45     private final int mSameBssidAward;
46     private final int mSameNetworkAward;
47     private final int mBand5GHzAward;
48     private final int mLastSelectionAward;
49     private final int mSecurityAward;
50     private final ScoringParams mScoringParams;
51 
52     /**
53      * Time it takes for the mLastSelectionAward to decay by one point, in milliseconds
54      */
55     @VisibleForTesting
56     public static final int LAST_SELECTION_AWARD_DECAY_MSEC = 60 * 1000;
57 
58 
SavedNetworkEvaluator(final Context context, ScoringParams scoringParams, WifiConfigManager configManager, Clock clock, LocalLog localLog, WifiConnectivityHelper connectivityHelper, SubscriptionManager subscriptionManager)59     SavedNetworkEvaluator(final Context context, ScoringParams scoringParams,
60             WifiConfigManager configManager, Clock clock,
61             LocalLog localLog, WifiConnectivityHelper connectivityHelper,
62             SubscriptionManager subscriptionManager) {
63         mScoringParams = scoringParams;
64         mWifiConfigManager = configManager;
65         mClock = clock;
66         mLocalLog = localLog;
67         mConnectivityHelper = connectivityHelper;
68         mSubscriptionManager = subscriptionManager;
69 
70         mRssiScoreSlope = context.getResources().getInteger(
71                 R.integer.config_wifi_framework_RSSI_SCORE_SLOPE);
72         mRssiScoreOffset = context.getResources().getInteger(
73                 R.integer.config_wifi_framework_RSSI_SCORE_OFFSET);
74         mSameBssidAward = context.getResources().getInteger(
75                 R.integer.config_wifi_framework_SAME_BSSID_AWARD);
76         mSameNetworkAward = context.getResources().getInteger(
77                 R.integer.config_wifi_framework_current_network_boost);
78         mLastSelectionAward = context.getResources().getInteger(
79                 R.integer.config_wifi_framework_LAST_SELECTION_AWARD);
80         mSecurityAward = context.getResources().getInteger(
81                 R.integer.config_wifi_framework_SECURITY_AWARD);
82         mBand5GHzAward = context.getResources().getInteger(
83                 R.integer.config_wifi_framework_5GHz_preference_boost_factor);
84     }
85 
localLog(String log)86     private void localLog(String log) {
87         mLocalLog.log(log);
88     }
89 
90     /**
91      * Get the evaluator type.
92      */
93     @Override
getId()94     public @EvaluatorId int getId() {
95         return EVALUATOR_ID_SAVED;
96     }
97 
98     /**
99      * Get the evaluator name.
100      */
101     @Override
getName()102     public String getName() {
103         return NAME;
104     }
105 
106     /**
107      * Update the evaluator.
108      */
109     @Override
update(List<ScanDetail> scanDetails)110     public void update(List<ScanDetail> scanDetails) { }
111 
calculateBssidScore(ScanResult scanResult, WifiConfiguration network, WifiConfiguration currentNetwork, String currentBssid, StringBuffer sbuf)112     private int calculateBssidScore(ScanResult scanResult, WifiConfiguration network,
113                         WifiConfiguration currentNetwork, String currentBssid,
114                         StringBuffer sbuf) {
115         int score = 0;
116         boolean is5GHz = scanResult.is5GHz();
117 
118         sbuf.append("[ ").append(scanResult.SSID).append(" ").append(scanResult.BSSID)
119                 .append(" RSSI:").append(scanResult.level).append(" ] ");
120         // Calculate the RSSI score.
121         int rssiSaturationThreshold = mScoringParams.getGoodRssi(scanResult.frequency);
122         int rssi = Math.min(scanResult.level, rssiSaturationThreshold);
123         score += (rssi + mRssiScoreOffset) * mRssiScoreSlope;
124         sbuf.append(" RSSI score: ").append(score).append(",");
125 
126         // 5GHz band bonus.
127         if (is5GHz) {
128             score += mBand5GHzAward;
129             sbuf.append(" 5GHz bonus: ").append(mBand5GHzAward).append(",");
130         }
131 
132         // Last user selection award.
133         int lastUserSelectedNetworkId = mWifiConfigManager.getLastSelectedNetwork();
134         if (lastUserSelectedNetworkId != WifiConfiguration.INVALID_NETWORK_ID
135                 && lastUserSelectedNetworkId == network.networkId) {
136             long timeDifference = mClock.getElapsedSinceBootMillis()
137                     - mWifiConfigManager.getLastSelectedTimeStamp();
138             if (timeDifference > 0) {
139                 int decay = (int) (timeDifference / LAST_SELECTION_AWARD_DECAY_MSEC);
140                 int bonus = Math.max(mLastSelectionAward - decay, 0);
141                 score += bonus;
142                 sbuf.append(" User selection ").append(timeDifference)
143                         .append(" ms ago, bonus: ").append(bonus).append(",");
144             }
145         }
146 
147         // Same network award.
148         if (currentNetwork != null && network.networkId == currentNetwork.networkId) {
149             score += mSameNetworkAward;
150             sbuf.append(" Same network bonus: ").append(mSameNetworkAward).append(",");
151 
152             // When firmware roaming is supported, equivalent BSSIDs (the ones under the
153             // same network as the currently connected one) get the same BSSID award.
154             if (mConnectivityHelper.isFirmwareRoamingSupported()
155                     && currentBssid != null && !currentBssid.equals(scanResult.BSSID)) {
156                 score += mSameBssidAward;
157                 sbuf.append(" Equivalent BSSID bonus: ").append(mSameBssidAward).append(",");
158             }
159         }
160 
161         // Same BSSID award.
162         if (currentBssid != null && currentBssid.equals(scanResult.BSSID)) {
163             score += mSameBssidAward;
164             sbuf.append(" Same BSSID bonus: ").append(mSameBssidAward).append(",");
165         }
166 
167         // Security award.
168         if (!WifiConfigurationUtil.isConfigForOpenNetwork(network)) {
169             score += mSecurityAward;
170             sbuf.append(" Secure network bonus: ").append(mSecurityAward).append(",");
171         }
172 
173         sbuf.append(" ## Total score: ").append(score).append("\n");
174 
175         return score;
176     }
177 
178     /**
179      * Evaluate all the networks from the scan results and return
180      * the WifiConfiguration of the network chosen for connection.
181      *
182      * @return configuration of the chosen network;
183      *         null if no network in this category is available.
184      */
185     @Override
evaluateNetworks(List<ScanDetail> scanDetails, WifiConfiguration currentNetwork, String currentBssid, boolean connected, boolean untrustedNetworkAllowed, @NonNull OnConnectableListener onConnectableListener)186     public WifiConfiguration evaluateNetworks(List<ScanDetail> scanDetails,
187                     WifiConfiguration currentNetwork, String currentBssid, boolean connected,
188                     boolean untrustedNetworkAllowed,
189                     @NonNull OnConnectableListener onConnectableListener) {
190         int highestScore = Integer.MIN_VALUE;
191         ScanResult scanResultCandidate = null;
192         WifiConfiguration candidate = null;
193         StringBuffer scoreHistory = new StringBuffer();
194 
195         for (ScanDetail scanDetail : scanDetails) {
196             ScanResult scanResult = scanDetail.getScanResult();
197 
198             // One ScanResult can be associated with more than one network, hence we calculate all
199             // the scores and use the highest one as the ScanResult's score.
200             // TODO(b/112196799): this has side effects, rather not do that in an evaluator
201             WifiConfiguration network =
202                     mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(scanDetail);
203 
204             if (network == null) {
205                 continue;
206             }
207 
208             /**
209              * Ignore Passpoint and Ephemeral networks. They are configured networks,
210              * but without being persisted to the storage. They are evaluated by
211              * {@link PasspointNetworkEvaluator} and {@link ScoredNetworkEvaluator}
212              * respectively.
213              */
214             if (network.isPasspoint() || network.isEphemeral()) {
215                 continue;
216             }
217 
218             WifiConfiguration.NetworkSelectionStatus status =
219                     network.getNetworkSelectionStatus();
220             // TODO (b/112196799): another side effect
221             status.setSeenInLastQualifiedNetworkSelection(true);
222 
223             if (!status.isNetworkEnabled()) {
224                 continue;
225             } else if (network.BSSID != null &&  !network.BSSID.equals("any")
226                     && !network.BSSID.equals(scanResult.BSSID)) {
227                 // App has specified the only BSSID to connect for this
228                 // configuration. So only the matching ScanResult can be a candidate.
229                 localLog("Network " + WifiNetworkSelector.toNetworkString(network)
230                         + " has specified BSSID " + network.BSSID + ". Skip "
231                         + scanResult.BSSID);
232                 continue;
233             } else if (TelephonyUtil.isSimConfig(network)
234                     && !TelephonyUtil.isSimPresent(mSubscriptionManager)) {
235                 // Don't select if security type is EAP SIM/AKA/AKA' when SIM is not present.
236                 continue;
237             }
238 
239             int score = calculateBssidScore(scanResult, network, currentNetwork, currentBssid,
240                     scoreHistory);
241 
242             // Set candidate ScanResult for all saved networks to ensure that users can
243             // override network selection. See WifiNetworkSelector#setUserConnectChoice.
244             if (score > status.getCandidateScore()
245                     || (score == status.getCandidateScore()
246                         && status.getCandidate() != null
247                         && scanResult.level > status.getCandidate().level)) {
248                 mWifiConfigManager.setNetworkCandidateScanResult(
249                         network.networkId, scanResult, score);
250             }
251 
252             // If the network is marked to use external scores, or is an open network with
253             // curate saved open networks enabled, do not consider it for network selection.
254             if (network.useExternalScores) {
255                 localLog("Network " + WifiNetworkSelector.toNetworkString(network)
256                         + " has external score.");
257                 continue;
258             }
259 
260             onConnectableListener.onConnectable(scanDetail,
261                     mWifiConfigManager.getConfiguredNetwork(network.networkId), score);
262 
263             // TODO(b/112196799) - pull into common code
264             if (score > highestScore
265                     || (score == highestScore
266                     && scanResultCandidate != null
267                     && scanResult.level > scanResultCandidate.level)) {
268                 highestScore = score;
269                 scanResultCandidate = scanResult;
270                 mWifiConfigManager.setNetworkCandidateScanResult(
271                         network.networkId, scanResultCandidate, highestScore);
272                 // Reload the network config with the updated info.
273                 candidate = mWifiConfigManager.getConfiguredNetwork(network.networkId);
274             }
275         }
276 
277         if (scoreHistory.length() > 0) {
278             localLog("\n" + scoreHistory.toString());
279         }
280 
281         if (scanResultCandidate == null) {
282             localLog("did not see any good candidates.");
283         }
284         return candidate;
285     }
286 }
287