1 /*
2  * Copyright (C) 2014 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.content.Context;
20 import android.net.NetworkKey;
21 import android.net.NetworkScoreManager;
22 import android.net.WifiKey;
23 import android.net.wifi.ScanResult;
24 import android.net.wifi.WifiConfiguration;
25 import android.net.wifi.WifiConfiguration.KeyMgmt;
26 import android.net.wifi.WifiConnectionStatistics;
27 import android.os.Process;
28 import android.provider.Settings;
29 import android.text.TextUtils;
30 import android.util.Log;
31 import android.os.SystemClock;
32 
33 import java.io.BufferedReader;
34 import java.io.IOException;
35 import java.io.StringReader;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.HashMap;
39 import java.util.Iterator;
40 import java.util.List;
41 
42 /**
43  * AutoJoin controller is responsible for WiFi Connect decision
44  *
45  * It runs in the thread context of WifiStateMachine
46  *
47  */
48 public class WifiAutoJoinController {
49 
50     private Context mContext;
51     private WifiStateMachine mWifiStateMachine;
52     private WifiConfigStore mWifiConfigStore;
53     private WifiNative mWifiNative;
54 
55     private NetworkScoreManager scoreManager;
56     private WifiNetworkScoreCache mNetworkScoreCache;
57 
58     private static final String TAG = "WifiAutoJoinController ";
59     private static boolean DBG = false;
60     private static boolean VDBG = false;
61     private static final boolean mStaStaSupported = false;
62 
63     public static int mScanResultMaximumAge = 40000; /* milliseconds unit */
64     public static int mScanResultAutoJoinAge = 5000; /* milliseconds unit */
65 
66     private String mCurrentConfigurationKey = null; //used by autojoin
67 
68     private final HashMap<String, ScanDetail> scanResultCache = new HashMap<>();
69 
70     private WifiConnectionStatistics mWifiConnectionStatistics;
71 
72     /**
73      * Whether to allow connections to untrusted networks.
74      */
75     private boolean mAllowUntrustedConnections = false;
76 
77     /* For debug purpose only: if the scored override a score */
78     boolean didOverride = false;
79 
80     // Lose the non-auth failure blacklisting after 8 hours
81     private final static long loseBlackListHardMilli = 1000 * 60 * 60 * 8;
82     // Lose some temporary blacklisting after 30 minutes
83     private final static long loseBlackListSoftMilli = 1000 * 60 * 30;
84 
85     /**
86      * @see android.provider.Settings.Global#WIFI_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS
87      */
88     private static final long DEFAULT_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS = 1000 * 60; // 1 minute
89 
90     public static final int AUTO_JOIN_IDLE = 0;
91     public static final int AUTO_JOIN_ROAMING = 1;
92     public static final int AUTO_JOIN_EXTENDED_ROAMING = 2;
93     public static final int AUTO_JOIN_OUT_OF_NETWORK_ROAMING = 3;
94 
95     public static final int HIGH_THRESHOLD_MODIFIER = 5;
96 
97     public static final int MAX_RSSI_DELTA = 50;
98 
99     // Below are AutoJoin wide parameters indicating if we should be aggressive before joining
100     // weak network. Note that we cannot join weak network that are going to be marked as unanted by
101     // ConnectivityService because this will trigger link flapping.
102     /**
103      * There was a non-blacklisted configuration that we bailed from because of a weak signal
104      */
105     boolean didBailDueToWeakRssi = false;
106     /**
107      * number of time we consecutively bailed out of an eligible network because its signal
108      * was too weak
109      */
110     int weakRssiBailCount = 0;
111 
WifiAutoJoinController(Context c, WifiStateMachine w, WifiConfigStore s, WifiConnectionStatistics st, WifiNative n)112     WifiAutoJoinController(Context c, WifiStateMachine w, WifiConfigStore s,
113                            WifiConnectionStatistics st, WifiNative n) {
114         mContext = c;
115         mWifiStateMachine = w;
116         mWifiConfigStore = s;
117         mWifiNative = n;
118         mNetworkScoreCache = null;
119         mWifiConnectionStatistics = st;
120         scoreManager =
121                 (NetworkScoreManager) mContext.getSystemService(Context.NETWORK_SCORE_SERVICE);
122         if (scoreManager == null)
123             logDbg("Registered scoreManager NULL " + " service " + Context.NETWORK_SCORE_SERVICE);
124 
125         if (scoreManager != null) {
126             mNetworkScoreCache = new WifiNetworkScoreCache(mContext);
127             scoreManager.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
128         } else {
129             logDbg("No network score service: Couldnt register as a WiFi score Manager, type="
130                     + Integer.toString(NetworkKey.TYPE_WIFI)
131                     + " service " + Context.NETWORK_SCORE_SERVICE);
132             mNetworkScoreCache = null;
133         }
134     }
135 
enableVerboseLogging(int verbose)136     void enableVerboseLogging(int verbose) {
137         if (verbose > 0) {
138             DBG = true;
139             VDBG = true;
140         } else {
141             DBG = false;
142             VDBG = false;
143         }
144     }
145 
146     /**
147      * Flush out scan results older than mScanResultMaximumAge
148      */
ageScanResultsOut(int delay)149     private void ageScanResultsOut(int delay) {
150         if (delay <= 0) {
151             delay = mScanResultMaximumAge; // Something sane
152         }
153         long milli = System.currentTimeMillis();
154         if (VDBG) {
155             logDbg("ageScanResultsOut delay " + Integer.valueOf(delay) + " size "
156                     + Integer.valueOf(scanResultCache.size()) + " now " + Long.valueOf(milli));
157         }
158 
159         Iterator<HashMap.Entry<String, ScanDetail>> iter = scanResultCache.entrySet().iterator();
160         while (iter.hasNext()) {
161             HashMap.Entry<String, ScanDetail> entry = iter.next();
162             ScanDetail scanDetail = entry.getValue();
163             if ((scanDetail.getSeen() + delay) < milli) {
164                 iter.remove();
165             }
166         }
167     }
168 
169 
averageRssiAndRemoveFromCache(ScanResult result)170     void averageRssiAndRemoveFromCache(ScanResult result) {
171         // Fetch the previous instance for this result
172         ScanDetail sd = scanResultCache.get(result.BSSID);
173         if (sd != null) {
174             ScanResult sr = sd.getScanResult();
175             if (mWifiConfigStore.scanResultRssiLevelPatchUp != 0
176                     && result.level == 0
177                     && sr.level < -20) {
178                 // A 'zero' RSSI reading is most likely a chip problem which returns
179                 // an unknown RSSI, hence ignore it
180                 result.level = sr.level;
181             }
182 
183             // If there was a previous cache result for this BSSID, average the RSSI values
184             result.averageRssi(sr.level, sr.seen, mScanResultMaximumAge);
185 
186             // Remove the previous Scan Result - this is not necessary
187             scanResultCache.remove(result.BSSID);
188         } else if (mWifiConfigStore.scanResultRssiLevelPatchUp != 0 && result.level == 0) {
189             // A 'zero' RSSI reading is most likely a chip problem which returns
190             // an unknown RSSI, hence initialize it to a sane value
191             result.level = mWifiConfigStore.scanResultRssiLevelPatchUp;
192         }
193     }
194 
addToUnscoredNetworks(ScanResult result, List<NetworkKey> unknownScanResults)195     void addToUnscoredNetworks(ScanResult result, List<NetworkKey> unknownScanResults) {
196         WifiKey wkey;
197         // Quoted SSIDs are the only one valid at this stage
198         try {
199             wkey = new WifiKey("\"" + result.SSID + "\"", result.BSSID);
200         } catch (IllegalArgumentException e) {
201             logDbg("AutoJoinController: received badly encoded SSID=[" + result.SSID +
202                     "] ->skipping this network");
203             wkey = null;
204         }
205         if (wkey != null) {
206             NetworkKey nkey = new NetworkKey(wkey);
207             //if we don't know this scan result then request a score from the scorer
208             unknownScanResults.add(nkey);
209         }
210         if (VDBG) {
211             String cap = "";
212             if (result.capabilities != null)
213                 cap = result.capabilities;
214             logDbg(result.SSID + " " + result.BSSID + " rssi="
215                     + result.level + " cap " + cap + " tsf " + result.timestamp + " is not scored");
216         }
217     }
218 
addToScanCache(List<ScanDetail> scanList)219     int addToScanCache(List<ScanDetail> scanList) {
220         int numScanResultsKnown = 0; // Record number of scan results we knew about
221         WifiConfiguration associatedConfig = null;
222         boolean didAssociate = false;
223         long now = System.currentTimeMillis();
224 
225         ArrayList<NetworkKey> unknownScanResults = new ArrayList<NetworkKey>();
226 
227         for (ScanDetail scanDetail : scanList) {
228             ScanResult result = scanDetail.getScanResult();
229             if (result.SSID == null) continue;
230 
231             if (VDBG) {
232                 logDbg(" addToScanCache " + result.SSID + " " + result.BSSID
233                         + " tsf=" + result.timestamp
234                         + " now= " + now + " uptime=" + SystemClock.uptimeMillis()
235                         + " elapsed=" + SystemClock.elapsedRealtime());
236             }
237 
238             // Make sure we record the last time we saw this result
239             scanDetail.setSeen();
240 
241             averageRssiAndRemoveFromCache(result);
242 
243             if (!mNetworkScoreCache.isScoredNetwork(result)) {
244                 addToUnscoredNetworks(result, unknownScanResults);
245             } else {
246                 if (VDBG) {
247                     String cap = "";
248                     if (result.capabilities != null)
249                         cap = result.capabilities;
250                     int score = mNetworkScoreCache.getNetworkScore(result);
251                     logDbg(result.SSID + " " + result.BSSID + " rssi="
252                             + result.level + " cap " + cap + " is scored : " + score);
253                 }
254             }
255 
256             // scanResultCache.put(result.BSSID, new ScanResult(result));
257             scanResultCache.put(result.BSSID, scanDetail);
258             // Add this BSSID to the scanResultCache of a Saved WifiConfiguration
259             didAssociate = mWifiConfigStore.updateSavedNetworkHistory(scanDetail);
260 
261             // If not successful, try to associate this BSSID to an existing Saved WifiConfiguration
262             if (!didAssociate) {
263                 // We couldn't associate the scan result to a Saved WifiConfiguration
264                 // Hence it is untrusted
265                 result.untrusted = true;
266             } else {
267                 // If the scan result has been blacklisted fir 18 hours -> unblacklist
268                 if ((now - result.blackListTimestamp) > loseBlackListHardMilli) {
269                     result.setAutoJoinStatus(ScanResult.ENABLED);
270                 }
271             }
272             if (didAssociate) {
273                 numScanResultsKnown++;
274                 result.isAutoJoinCandidate++;
275             } else {
276                 result.isAutoJoinCandidate = 0;
277             }
278         }
279 
280         if (unknownScanResults.size() != 0) {
281             NetworkKey[] newKeys =
282                     unknownScanResults.toArray(new NetworkKey[unknownScanResults.size()]);
283             // Kick the score manager, we will get updated scores asynchronously
284             scoreManager.requestScores(newKeys);
285         }
286         return numScanResultsKnown;
287     }
288 
logDbg(String message)289     void logDbg(String message) {
290         logDbg(message, false);
291     }
292 
logDbg(String message, boolean stackTrace)293     void logDbg(String message, boolean stackTrace) {
294         if (stackTrace) {
295             Log.d(TAG, message + " stack:"
296                     + Thread.currentThread().getStackTrace()[2].getMethodName() + " - "
297                     + Thread.currentThread().getStackTrace()[3].getMethodName() + " - "
298                     + Thread.currentThread().getStackTrace()[4].getMethodName() + " - "
299                     + Thread.currentThread().getStackTrace()[5].getMethodName());
300         } else {
301             Log.d(TAG, message);
302         }
303     }
304 
305     // Called directly from WifiStateMachine
newSupplicantResults(boolean doAutoJoin)306     int newSupplicantResults(boolean doAutoJoin) {
307         int numScanResultsKnown;
308         List<ScanDetail> scanList = mWifiStateMachine.getScanResultsListNoCopyUnsync();
309         numScanResultsKnown = addToScanCache(scanList);
310         ageScanResultsOut(mScanResultMaximumAge);
311         if (DBG) {
312             logDbg("newSupplicantResults size=" + Integer.valueOf(scanResultCache.size())
313                     + " known=" + numScanResultsKnown + " "
314                     + doAutoJoin);
315         }
316         if (doAutoJoin) {
317             attemptAutoJoin();
318         }
319         mWifiConfigStore.writeKnownNetworkHistory(false);
320         return numScanResultsKnown;
321     }
322 
323 
324     /**
325      * Not used at the moment
326      * should be a call back from WifiScanner HAL ??
327      * this function is not hooked and working yet, it will receive scan results from WifiScanners
328      * with the list of IEs,then populate the capabilities by parsing the IEs and inject the scan
329      * results as normal.
330      */
newHalScanResults()331     void newHalScanResults() {
332         List<ScanDetail> scanList = null;//mWifiScanner.syncGetScanResultsList();
333         String akm = WifiParser.parse_akm(null, null);
334         logDbg(akm);
335         addToScanCache(scanList);
336         ageScanResultsOut(0);
337         attemptAutoJoin();
338         mWifiConfigStore.writeKnownNetworkHistory(false);
339     }
340 
341     /**
342      * network link quality changed, called directly from WifiTrafficPoller,
343      * or by listening to Link Quality intent
344      */
linkQualitySignificantChange()345     void linkQualitySignificantChange() {
346         attemptAutoJoin();
347     }
348 
349     /**
350      * compare a WifiConfiguration against the current network, return a delta score
351      * If not associated, and the candidate will always be better
352      * For instance if the candidate is a home network versus an unknown public wifi,
353      * the delta will be infinite, else compare Kepler scores etc…
354      * Negatve return values from this functions are meaningless per se, just trying to
355      * keep them distinct for debug purpose (i.e. -1, -2 etc...)
356      */
compareNetwork(WifiConfiguration candidate, String lastSelectedConfiguration)357     private int compareNetwork(WifiConfiguration candidate,
358                                String lastSelectedConfiguration) {
359         if (candidate == null)
360             return -3;
361 
362         WifiConfiguration currentNetwork = mWifiStateMachine.getCurrentWifiConfiguration();
363         if (currentNetwork == null) {
364             // Return any absurdly high score, if we are not connected there is no current
365             // network to...
366             return 1000;
367         }
368 
369         if (candidate.configKey(true).equals(currentNetwork.configKey(true))) {
370             return -2;
371         }
372 
373         if (DBG) {
374             logDbg("compareNetwork will compare " + candidate.configKey()
375                     + " with current " + currentNetwork.configKey());
376         }
377         int order = compareWifiConfigurations(currentNetwork, candidate);
378 
379         // The lastSelectedConfiguration is the configuration the user has manually selected
380         // thru WifiPicker, or that a 3rd party app asked us to connect to via the
381         // enableNetwork with disableOthers=true WifiManager API
382         // As this is a direct user choice, we strongly prefer this configuration,
383         // hence give +/-100
384         if ((lastSelectedConfiguration != null)
385                 && currentNetwork.configKey().equals(lastSelectedConfiguration)) {
386             // currentNetwork is the last selected configuration,
387             // so keep it above connect choices (+/-60) and
388             // above RSSI/scorer based selection of linked configuration (+/- 50)
389             // by reducing order by -100
390             order = order - 100;
391             if (VDBG) {
392                 logDbg("     ...and prefers -100 " + currentNetwork.configKey()
393                         + " over " + candidate.configKey()
394                         + " because it is the last selected -> "
395                         + Integer.toString(order));
396             }
397         } else if ((lastSelectedConfiguration != null)
398                 && candidate.configKey().equals(lastSelectedConfiguration)) {
399             // candidate is the last selected configuration,
400             // so keep it above connect choices (+/-60) and
401             // above RSSI/scorer based selection of linked configuration (+/- 50)
402             // by increasing order by +100
403             order = order + 100;
404             if (VDBG) {
405                 logDbg("     ...and prefers +100 " + candidate.configKey()
406                         + " over " + currentNetwork.configKey()
407                         + " because it is the last selected -> "
408                         + Integer.toString(order));
409             }
410         }
411 
412         return order;
413     }
414 
415     /**
416      * update the network history fields fo that configuration
417      * - if userTriggered, we mark the configuration as "non selfAdded" since the user has seen it
418      * and took over management
419      * - if it is a "connect", remember which network were there at the point of the connect, so
420      * as those networks get a relative lower score than the selected configuration
421      *
422      * @param netId
423      * @param userTriggered : if the update come from WiFiManager
424      * @param connect       : if the update includes a connect
425      */
updateConfigurationHistory(int netId, boolean userTriggered, boolean connect)426     public void updateConfigurationHistory(int netId, boolean userTriggered, boolean connect) {
427         WifiConfiguration selected = mWifiConfigStore.getWifiConfiguration(netId);
428         if (selected == null) {
429             logDbg("updateConfigurationHistory nid=" + netId + " no selected configuration!");
430             return;
431         }
432 
433         if (selected.SSID == null) {
434             logDbg("updateConfigurationHistory nid=" + netId +
435                     " no SSID in selected configuration!");
436             return;
437         }
438 
439         if (userTriggered) {
440             // Reenable autojoin for this network,
441             // since the user want to connect to this configuration
442             selected.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED);
443             selected.selfAdded = false;
444             selected.dirty = true;
445         }
446 
447         if (DBG && userTriggered) {
448             if (selected.connectChoices != null) {
449                 logDbg("updateConfigurationHistory will update "
450                         + Integer.toString(netId) + " now: "
451                         + Integer.toString(selected.connectChoices.size())
452                         + " uid=" + Integer.toString(selected.creatorUid), true);
453             } else {
454                 logDbg("updateConfigurationHistory will update "
455                         + Integer.toString(netId)
456                         + " uid=" + Integer.toString(selected.creatorUid), true);
457             }
458         }
459 
460         if (connect && userTriggered) {
461             boolean found = false;
462             int choice = 0;
463             int size = 0;
464 
465             // Reset the triggered disabled count, because user wanted to connect to this
466             // configuration, and we were not.
467             selected.numUserTriggeredWifiDisableLowRSSI = 0;
468             selected.numUserTriggeredWifiDisableBadRSSI = 0;
469             selected.numUserTriggeredWifiDisableNotHighRSSI = 0;
470             selected.numUserTriggeredJoinAttempts++;
471 
472             List<WifiConfiguration> networks =
473                     mWifiConfigStore.getRecentConfiguredNetworks(12000, false);
474             if (networks != null) size = networks.size();
475             logDbg("updateConfigurationHistory found " + size + " networks");
476             if (networks != null) {
477                 for (WifiConfiguration config : networks) {
478                     if (DBG) {
479                         logDbg("updateConfigurationHistory got " + config.SSID + " nid="
480                                 + Integer.toString(config.networkId));
481                     }
482 
483                     if (selected.configKey(true).equals(config.configKey(true))) {
484                         found = true;
485                         continue;
486                     }
487 
488                     // If the selection was made while config was visible with reasonably good RSSI
489                     // then register the user preference, else ignore
490                     if (config.visibility == null ||
491                             (config.visibility.rssi24 < mWifiConfigStore.thresholdBadRssi24.get()
492                             && config.visibility.rssi5 < mWifiConfigStore.thresholdBadRssi5.get())
493                     ) {
494                         continue;
495                     }
496 
497                     choice = MAX_RSSI_DELTA + 10; // Make sure the choice overrides the RSSI diff
498 
499                     // The selected configuration was preferred over a recently seen config
500                     // hence remember the user's choice:
501                     // add the recently seen config to the selected's connectChoices array
502 
503                     if (selected.connectChoices == null) {
504                         selected.connectChoices = new HashMap<String, Integer>();
505                     }
506 
507                     logDbg("updateConfigurationHistory add a choice " + selected.configKey(true)
508                             + " over " + config.configKey(true)
509                             + " choice " + Integer.toString(choice));
510 
511                     // Add the visible config to the selected's connect choice list
512                     selected.connectChoices.put(config.configKey(true), choice);
513 
514                     if (config.connectChoices != null) {
515                         if (VDBG) {
516                             logDbg("updateConfigurationHistory will remove "
517                                     + selected.configKey(true) + " from " + config.configKey(true));
518                         }
519                         // Remove the selected from the recently seen config's connectChoice list
520                         config.connectChoices.remove(selected.configKey(true));
521 
522                         if (selected.linkedConfigurations != null) {
523                             // Remove the selected's linked configuration from the
524                             // recently seen config's connectChoice list
525                             for (String key : selected.linkedConfigurations.keySet()) {
526                                 config.connectChoices.remove(key);
527                             }
528                         }
529                     }
530                 }
531                 if (found == false) {
532                     // We haven't found the configuration that the user just selected in our
533                     // scan cache.
534                     // In that case we will need a new scan before attempting to connect to this
535                     // configuration anyhow and thus we can process the scan results then.
536                     logDbg("updateConfigurationHistory try to connect to an old network!! : "
537                             + selected.configKey());
538                 }
539 
540                 if (selected.connectChoices != null) {
541                     if (VDBG)
542                         logDbg("updateConfigurationHistory " + Integer.toString(netId)
543                                 + " now: " + Integer.toString(selected.connectChoices.size()));
544                 }
545             }
546         }
547 
548         // TODO: write only if something changed
549         if (userTriggered || connect) {
550             mWifiConfigStore.writeKnownNetworkHistory(false);
551         }
552     }
553 
getConnectChoice(WifiConfiguration source, WifiConfiguration target, boolean strict)554     int getConnectChoice(WifiConfiguration source, WifiConfiguration target, boolean strict) {
555         int choice = 0;
556         if (source == null || target == null) {
557             return 0;
558         }
559 
560         if (source.connectChoices != null
561                 && source.connectChoices.containsKey(target.configKey(true))) {
562             Integer val = source.connectChoices.get(target.configKey(true));
563             if (val != null) {
564                 choice = val;
565             }
566         } else if (source.linkedConfigurations != null) {
567             for (String key : source.linkedConfigurations.keySet()) {
568                 WifiConfiguration config = mWifiConfigStore.getWifiConfiguration(key);
569                 if (config != null) {
570                     if (config.connectChoices != null) {
571                         Integer val = config.connectChoices.get(target.configKey(true));
572                         if (val != null) {
573                             choice = val;
574                         }
575                     }
576                 }
577             }
578         }
579 
580         if (!strict && choice == 0) {
581             // We didn't find the connect choice; fallback to some default choices
582             int sourceScore = getSecurityScore(source);
583             int targetScore = getSecurityScore(target);
584             choice = sourceScore - targetScore;
585         }
586 
587         return choice;
588     }
589 
compareWifiConfigurationsFromVisibility(WifiConfiguration.Visibility a, int aRssiBoost, String dbgA, WifiConfiguration.Visibility b, int bRssiBoost, String dbgB)590     int compareWifiConfigurationsFromVisibility(WifiConfiguration.Visibility a, int aRssiBoost,
591                                                 String dbgA, WifiConfiguration.Visibility b, int bRssiBoost, String dbgB) {
592 
593         int aRssiBoost5 = 0; // 5GHz RSSI boost to apply for purpose band selection (5GHz pref)
594         int bRssiBoost5 = 0; // 5GHz RSSI boost to apply for purpose band selection (5GHz pref)
595 
596         int aScore = 0;
597         int bScore = 0;
598 
599         boolean aPrefers5GHz = false;
600         boolean bPrefers5GHz = false;
601 
602         /**
603          * Calculate a boost to apply to RSSI value of configuration we want to join on 5GHz:
604          * Boost RSSI value of 5GHz bands iff the base value is better than threshold,
605          * penalize the RSSI value of 5GHz band iff the base value is lower than threshold
606          * This implements band preference where we prefer 5GHz if RSSI5 is good enough, whereas
607          * we prefer 2.4GHz otherwise.
608          */
609         aRssiBoost5 = rssiBoostFrom5GHzRssi(a.rssi5, dbgA + "->");
610         bRssiBoost5 = rssiBoostFrom5GHzRssi(b.rssi5, dbgB + "->");
611 
612         // Select which band to use for a
613         if (a.rssi5 + aRssiBoost5 > a.rssi24) {
614             // Prefer a's 5GHz
615             aPrefers5GHz = true;
616         }
617 
618         // Select which band to use for b
619         if (b.rssi5 + bRssiBoost5 > b.rssi24) {
620             // Prefer b's 5GHz
621             bPrefers5GHz = true;
622         }
623 
624         if (aPrefers5GHz) {
625             if (bPrefers5GHz) {
626                 // If both a and b are on 5GHz then we don't apply the 5GHz RSSI boost to either
627                 // one, but directly compare the RSSI values, this improves stability,
628                 // since the 5GHz RSSI boost can introduce large fluctuations
629                 aScore = a.rssi5 + aRssiBoost;
630             } else {
631                 // If only a is on 5GHz, then apply the 5GHz preference boost to a
632                 aScore = a.rssi5 + aRssiBoost + aRssiBoost5;
633             }
634         } else {
635             aScore = a.rssi24 + aRssiBoost;
636         }
637 
638         if (bPrefers5GHz) {
639             if (aPrefers5GHz) {
640                 // If both a and b are on 5GHz then we don't apply the 5GHz RSSI boost to either
641                 // one, but directly compare the RSSI values, this improves stability,
642                 // since the 5GHz RSSI boost can introduce large fluctuations
643                 bScore = b.rssi5 + bRssiBoost;
644             } else {
645                 // If only b is on 5GHz, then apply the 5GHz preference boost to b
646                 bScore = b.rssi5 + bRssiBoost + bRssiBoost5;
647             }
648         } else {
649             bScore = b.rssi24 + bRssiBoost;
650         }
651 
652         if (VDBG) {
653             logDbg("        " + dbgA + " is5=" + aPrefers5GHz + " score=" + aScore
654                     + " " + dbgB + " is5=" + bPrefers5GHz + " score=" + bScore);
655         }
656 
657         // Debug only, record RSSI comparison parameters
658         if (a != null) {
659             a.score = aScore;
660             a.currentNetworkBoost = aRssiBoost;
661             a.bandPreferenceBoost = aRssiBoost5;
662         }
663         if (b != null) {
664             b.score = bScore;
665             b.currentNetworkBoost = bRssiBoost;
666             b.bandPreferenceBoost = bRssiBoost5;
667         }
668 
669         // Compare a and b
670         // If a score is higher then a > b and the order is descending (negative)
671         // If b score is higher then a < b and the order is ascending (positive)
672         return bScore - aScore;
673     }
674 
675     // Compare WifiConfiguration by RSSI, and return a comparison value in the range [-50, +50]
676     // The result represents "approximately" an RSSI difference measured in dBM
677     // Adjusted with various parameters:
678     // +) current network gets a +15 boost
679     // +) 5GHz signal, if they are strong enough, get a +15 or +25 boost, representing the
680     // fact that at short range we prefer 5GHz band as it is cleaner of interference and
681     // provides for wider channels
compareWifiConfigurationsRSSI(WifiConfiguration a, WifiConfiguration b, String currentConfiguration)682     int compareWifiConfigurationsRSSI(WifiConfiguration a, WifiConfiguration b,
683                                       String currentConfiguration) {
684         int order = 0;
685 
686         // Boost used so as to favor current config
687         int aRssiBoost = 0;
688         int bRssiBoost = 0;
689 
690         // Retrieve the visibility
691         WifiConfiguration.Visibility astatus = a.visibility;
692         WifiConfiguration.Visibility bstatus = b.visibility;
693         if (astatus == null || bstatus == null) {
694             // Error visibility wasn't set
695             logDbg("    compareWifiConfigurations NULL band status!");
696             return 0;
697         }
698 
699         // Apply Hysteresis, boost RSSI of current configuration
700         if (null != currentConfiguration) {
701             if (a.configKey().equals(currentConfiguration)) {
702                 aRssiBoost = mWifiConfigStore.currentNetworkBoost;
703             } else if (b.configKey().equals(currentConfiguration)) {
704                 bRssiBoost = mWifiConfigStore.currentNetworkBoost;
705             }
706         }
707 
708         if (VDBG) {
709             logDbg("    compareWifiConfigurationsRSSI: " + a.configKey()
710                             + " rssi=" + Integer.toString(astatus.rssi24)
711                             + "," + Integer.toString(astatus.rssi5)
712                             + " boost=" + Integer.toString(aRssiBoost)
713                             + " " + b.configKey() + " rssi="
714                             + Integer.toString(bstatus.rssi24) + ","
715                             + Integer.toString(bstatus.rssi5)
716                             + " boost=" + Integer.toString(bRssiBoost)
717             );
718         }
719 
720         order = compareWifiConfigurationsFromVisibility(
721                 a.visibility, aRssiBoost, a.configKey(),
722                 b.visibility, bRssiBoost, b.configKey());
723 
724         // Normalize the order to [-50, +50] = [ -MAX_RSSI_DELTA, MAX_RSSI_DELTA]
725         if (order > MAX_RSSI_DELTA) order = MAX_RSSI_DELTA;
726         else if (order < -MAX_RSSI_DELTA) order = -MAX_RSSI_DELTA;
727 
728         if (VDBG) {
729             String prefer = " = ";
730             if (order > 0) {
731                 prefer = " < "; // Ascending
732             } else if (order < 0) {
733                 prefer = " > "; // Descending
734             }
735             logDbg("    compareWifiConfigurationsRSSI " + a.configKey()
736                     + " rssi=(" + a.visibility.rssi24
737                     + "," + a.visibility.rssi5
738                     + ") num=(" + a.visibility.num24
739                     + "," + a.visibility.num5 + ")"
740                     + prefer + b.configKey()
741                     + " rssi=(" + b.visibility.rssi24
742                     + "," + b.visibility.rssi5
743                     + ") num=(" + b.visibility.num24
744                     + "," + b.visibility.num5 + ")"
745                     + " -> " + order);
746         }
747 
748         return order;
749     }
750 
751     /**
752      * b/18490330 only use scorer for untrusted networks
753      * <p/>
754      * int compareWifiConfigurationsWithScorer(WifiConfiguration a, WifiConfiguration b) {
755      * <p/>
756      * boolean aIsActive = false;
757      * boolean bIsActive = false;
758      * <p/>
759      * // Apply Hysteresis : boost RSSI of current configuration before
760      * // looking up the score
761      * if (null != mCurrentConfigurationKey) {
762      * if (a.configKey().equals(mCurrentConfigurationKey)) {
763      * aIsActive = true;
764      * } else if (b.configKey().equals(mCurrentConfigurationKey)) {
765      * bIsActive = true;
766      * }
767      * }
768      * int scoreA = getConfigNetworkScore(a, mScanResultAutoJoinAge, aIsActive);
769      * int scoreB = getConfigNetworkScore(b, mScanResultAutoJoinAge, bIsActive);
770      * <p/>
771      * // Both configurations need to have a score for the scorer to be used
772      * // ...and the scores need to be different:-)
773      * if (scoreA == WifiNetworkScoreCache.INVALID_NETWORK_SCORE
774      * || scoreB == WifiNetworkScoreCache.INVALID_NETWORK_SCORE) {
775      * if (VDBG)  {
776      * logDbg("    compareWifiConfigurationsWithScorer no-scores: "
777      * + a.configKey()
778      * + " "
779      * + b.configKey());
780      * }
781      * return 0;
782      * }
783      * <p/>
784      * if (VDBG) {
785      * String prefer = " = ";
786      * if (scoreA < scoreB) {
787      * prefer = " < ";
788      * } if (scoreA > scoreB) {
789      * prefer = " > ";
790      * }
791      * logDbg("    compareWifiConfigurationsWithScorer " + a.configKey()
792      * + " rssi=(" + a.visibility.rssi24
793      * + "," + a.visibility.rssi5
794      * + ") num=(" + a.visibility.num24
795      * + "," + a.visibility.num5 + ")"
796      * + " sc=" + scoreA
797      * + prefer + b.configKey()
798      * + " rssi=(" + b.visibility.rssi24
799      * + "," + b.visibility.rssi5
800      * + ") num=(" + b.visibility.num24
801      * + "," + b.visibility.num5 + ")"
802      * + " sc=" + scoreB
803      * + " -> " + Integer.toString(scoreB - scoreA));
804      * }
805      * <p/>
806      * // If scoreA > scoreB, the comparison is descending hence the return value is negative
807      * return scoreB - scoreA;
808      * }
809      */
810 
getSecurityScore(WifiConfiguration config)811     int getSecurityScore(WifiConfiguration config) {
812 
813         if (TextUtils.isEmpty(config.SSID) == false) {
814             if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP)
815                     || config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)
816                     || config.allowedKeyManagement.get(KeyMgmt.WPA2_PSK)) {
817                 /* enterprise or PSK networks get highest score */
818                 return 100;
819             } else if (config.allowedKeyManagement.get(KeyMgmt.NONE)) {
820                 /* open networks have lowest score */
821                 return 33;
822             }
823         } else if (TextUtils.isEmpty(config.FQDN) == false) {
824             /* passpoint networks have medium preference */
825             return 66;
826         }
827 
828         /* bad network */
829         return 0;
830     }
831 
compareWifiConfigurations(WifiConfiguration a, WifiConfiguration b)832     int compareWifiConfigurations(WifiConfiguration a, WifiConfiguration b) {
833         int order = 0;
834         boolean linked = false;
835 
836         if ((a.linkedConfigurations != null) && (b.linkedConfigurations != null)
837                 && (a.autoJoinStatus == WifiConfiguration.AUTO_JOIN_ENABLED)
838                 && (b.autoJoinStatus == WifiConfiguration.AUTO_JOIN_ENABLED)) {
839             if ((a.linkedConfigurations.get(b.configKey(true)) != null)
840                     && (b.linkedConfigurations.get(a.configKey(true)) != null)) {
841                 linked = true;
842             }
843         }
844 
845         if (a.ephemeral && b.ephemeral == false) {
846             if (VDBG) {
847                 logDbg("    compareWifiConfigurations ephemeral and prefers " + b.configKey()
848                         + " over " + a.configKey());
849             }
850             return 1; // b is of higher priority - ascending
851         }
852         if (b.ephemeral && a.ephemeral == false) {
853             if (VDBG) {
854                 logDbg("    compareWifiConfigurations ephemeral and prefers " + a.configKey()
855                         + " over " + b.configKey());
856             }
857             return -1; // a is of higher priority - descending
858         }
859 
860         // Apply RSSI, in the range [-5, +5]
861         // after band adjustment, +n difference roughly corresponds to +10xn dBm
862         order = order + compareWifiConfigurationsRSSI(a, b, mCurrentConfigurationKey);
863 
864         // If the configurations are not linked, compare by user's choice, only a
865         // very high RSSI difference can then override the choice
866         if (!linked) {
867             int choice;
868 
869             choice = getConnectChoice(a, b, false);
870             if (choice > 0) {
871                 // a is of higher priority - descending
872                 order = order - choice;
873                 if (VDBG) {
874                     logDbg("    compareWifiConfigurations prefers " + a.configKey()
875                             + " over " + b.configKey()
876                             + " due to user choice of " + choice
877                             + " order -> " + Integer.toString(order));
878                 }
879                 if (a.visibility != null) {
880                     a.visibility.lastChoiceBoost = choice;
881                     a.visibility.lastChoiceConfig = b.configKey();
882                 }
883             }
884 
885             choice = getConnectChoice(b, a, false);
886             if (choice > 0) {
887                 // a is of lower priority - ascending
888                 order = order + choice;
889                 if (VDBG) {
890                     logDbg("    compareWifiConfigurations prefers " + b.configKey() + " over "
891                             + a.configKey() + " due to user choice of " + choice
892                             + " order ->" + Integer.toString(order));
893                 }
894                 if (b.visibility != null) {
895                     b.visibility.lastChoiceBoost = choice;
896                     b.visibility.lastChoiceConfig = a.configKey();
897                 }
898             }
899         }
900 
901         if (order == 0) {
902             // We don't know anything - pick the last seen i.e. K behavior
903             // we should do this only for recently picked configurations
904             if (a.priority > b.priority) {
905                 // a is of higher priority - descending
906                 if (VDBG) {
907                     logDbg("    compareWifiConfigurations prefers -1 " + a.configKey() + " over "
908                             + b.configKey() + " due to priority");
909                 }
910 
911                 order = -1;
912             } else if (a.priority < b.priority) {
913                 // a is of lower priority - ascending
914                 if (VDBG) {
915                     logDbg("    compareWifiConfigurations prefers +1 " + b.configKey() + " over "
916                             + a.configKey() + " due to priority");
917                 }
918                 order = 1;
919             }
920         }
921 
922         String sorder = " == ";
923         if (order > 0) {
924             sorder = " < ";
925         } else if (order < 0) {
926             sorder = " > ";
927         }
928 
929         if (VDBG) {
930             logDbg("compareWifiConfigurations: " + a.configKey() + sorder
931                     + b.configKey() + " order " + Integer.toString(order));
932         }
933 
934         return order;
935     }
936 
isBadCandidate(int rssi5, int rssi24)937     boolean isBadCandidate(int rssi5, int rssi24) {
938         return (rssi5 < -80 && rssi24 < -90);
939     }
940 
941     /*
942     int compareWifiConfigurationsTop(WifiConfiguration a, WifiConfiguration b) {
943         int scorerOrder = compareWifiConfigurationsWithScorer(a, b);
944         int order = compareWifiConfigurations(a, b);
945 
946         if (scorerOrder * order < 0) {
947             if (VDBG) {
948                 logDbg("    -> compareWifiConfigurationsTop: " +
949                         "scorer override " + scorerOrder + " " + order);
950             }
951             // For debugging purpose, remember that an override happened
952             // during that autojoin Attempt
953             didOverride = true;
954             a.numScorerOverride++;
955             b.numScorerOverride++;
956         }
957 
958         if (scorerOrder != 0) {
959             // If the scorer came up with a result then use the scorer's result, else use
960             // the order provided by the base comparison function
961             order = scorerOrder;
962         }
963         return order;
964     }
965     */
966 
rssiBoostFrom5GHzRssi(int rssi, String dbg)967     public int rssiBoostFrom5GHzRssi(int rssi, String dbg) {
968         if (!mWifiConfigStore.enable5GHzPreference) {
969             return 0;
970         }
971         if (rssi
972                 > mWifiConfigStore.bandPreferenceBoostThreshold5.get()) {
973             // Boost by 2 dB for each point
974             //    Start boosting at -65
975             //    Boost by 20 if above -55
976             //    Boost by 40 if abore -45
977             int boost = mWifiConfigStore.bandPreferenceBoostFactor5
978                     * (rssi - mWifiConfigStore.bandPreferenceBoostThreshold5.get());
979             if (boost > 50) {
980                 // 50 dB boost allows jumping from 2.4 to 5GHz
981                 // consistently
982                 boost = 50;
983             }
984             if (VDBG && dbg != null) {
985                 logDbg("        " + dbg + ":    rssi5 " + rssi + " 5GHz-boost " + boost);
986             }
987             return boost;
988         }
989 
990         if (rssi
991                 < mWifiConfigStore.bandPreferencePenaltyThreshold5.get()) {
992             // penalize if < -75
993             int boost = mWifiConfigStore.bandPreferencePenaltyFactor5
994                     * (rssi - mWifiConfigStore.bandPreferencePenaltyThreshold5.get());
995             return boost;
996         }
997         return 0;
998     }
999 
1000     /**
1001      * attemptRoam() function implements the core of the same SSID switching algorithm
1002      * <p/>
1003      * Run thru all recent scan result of a WifiConfiguration and select the
1004      * best one.
1005      */
attemptRoam(ScanResult a, WifiConfiguration current, int age, String currentBSSID)1006     public ScanResult attemptRoam(ScanResult a,
1007                                   WifiConfiguration current, int age, String currentBSSID) {
1008         if (current == null) {
1009             if (VDBG) {
1010                 logDbg("attemptRoam not associated");
1011             }
1012             return a;
1013         }
1014 
1015         ScanDetailCache scanDetailCache =
1016                 mWifiConfigStore.getScanDetailCache(current);
1017 
1018         if (scanDetailCache == null) {
1019             if (VDBG) {
1020                 logDbg("attemptRoam no scan cache");
1021             }
1022             return a;
1023         }
1024         if (scanDetailCache.size() > 6) {
1025             if (VDBG) {
1026                 logDbg("attemptRoam scan cache size "
1027                         + scanDetailCache.size() + " --> bail");
1028             }
1029             // Implement same SSID roaming only for configurations
1030             // that have less than 4 BSSIDs
1031             return a;
1032         }
1033 
1034         if (current.BSSID != null && !current.BSSID.equals("any")) {
1035             if (DBG) {
1036                 logDbg("attemptRoam() BSSID is set "
1037                         + current.BSSID + " -> bail");
1038             }
1039             return a;
1040         }
1041 
1042         // Determine which BSSID we want to associate to, taking account
1043         // relative strength of 5 and 2.4 GHz BSSIDs
1044         long nowMs = System.currentTimeMillis();
1045 
1046         for (ScanDetail sd : scanDetailCache.values()) {
1047             ScanResult b = sd.getScanResult();
1048             int bRssiBoost5 = 0;
1049             int aRssiBoost5 = 0;
1050             int bRssiBoost = 0;
1051             int aRssiBoost = 0;
1052             if ((sd.getSeen() == 0) || (b.BSSID == null)
1053                     || ((nowMs - sd.getSeen()) > age)
1054                     || b.autoJoinStatus != ScanResult.ENABLED
1055                     || b.numIpConfigFailures > 8) {
1056                 continue;
1057             }
1058 
1059             // Pick first one
1060             if (a == null) {
1061                 a = b;
1062                 continue;
1063             }
1064 
1065             if (b.numIpConfigFailures < (a.numIpConfigFailures - 1)) {
1066                 // Prefer a BSSID that doesn't have less number of Ip config failures
1067                 logDbg("attemptRoam: "
1068                         + b.BSSID + " rssi=" + b.level + " ipfail=" + b.numIpConfigFailures
1069                         + " freq=" + b.frequency
1070                         + " > "
1071                         + a.BSSID + " rssi=" + a.level + " ipfail=" + a.numIpConfigFailures
1072                         + " freq=" + a.frequency);
1073                 a = b;
1074                 continue;
1075             }
1076 
1077             // Apply hysteresis: we favor the currentBSSID by giving it a boost
1078             if (currentBSSID != null && currentBSSID.equals(b.BSSID)) {
1079                 // Reduce the benefit of hysteresis if RSSI <= -75
1080                 if (b.level <= mWifiConfigStore.bandPreferencePenaltyThreshold5.get()) {
1081                     bRssiBoost = mWifiConfigStore.associatedHysteresisLow;
1082                 } else {
1083                     bRssiBoost = mWifiConfigStore.associatedHysteresisHigh;
1084                 }
1085             }
1086             if (currentBSSID != null && currentBSSID.equals(a.BSSID)) {
1087                 if (a.level <= mWifiConfigStore.bandPreferencePenaltyThreshold5.get()) {
1088                     // Reduce the benefit of hysteresis if RSSI <= -75
1089                     aRssiBoost = mWifiConfigStore.associatedHysteresisLow;
1090                 } else {
1091                     aRssiBoost = mWifiConfigStore.associatedHysteresisHigh;
1092                 }
1093             }
1094 
1095             // Favor 5GHz: give a boost to 5GHz BSSIDs, with a slightly progressive curve
1096             //   Boost the BSSID if it is on 5GHz, above a threshold
1097             //   But penalize it if it is on 5GHz and below threshold
1098             //
1099             //   With he current threshold values, 5GHz network with RSSI above -55
1100             //   Are given a boost of 30DB which is enough to overcome the current BSSID
1101             //   hysteresis (+14) plus 2.4/5 GHz signal strength difference on most cases
1102             //
1103             // The "current BSSID" Boost must be added to the BSSID's level so as to introduce\
1104             // soem amount of hysteresis
1105             if (b.is5GHz()) {
1106                 bRssiBoost5 = rssiBoostFrom5GHzRssi(b.level + bRssiBoost, b.BSSID);
1107             }
1108             if (a.is5GHz()) {
1109                 aRssiBoost5 = rssiBoostFrom5GHzRssi(a.level + aRssiBoost, a.BSSID);
1110             }
1111 
1112             if (VDBG) {
1113                 String comp = " < ";
1114                 if (b.level + bRssiBoost + bRssiBoost5 > a.level + aRssiBoost + aRssiBoost5) {
1115                     comp = " > ";
1116                 }
1117                 logDbg("attemptRoam: "
1118                         + b.BSSID + " rssi=" + b.level + " boost=" + Integer.toString(bRssiBoost)
1119                         + "/" + Integer.toString(bRssiBoost5) + " freq=" + b.frequency
1120                         + comp
1121                         + a.BSSID + " rssi=" + a.level + " boost=" + Integer.toString(aRssiBoost)
1122                         + "/" + Integer.toString(aRssiBoost5) + " freq=" + a.frequency);
1123             }
1124 
1125             // Compare the RSSIs after applying the hysteresis boost and the 5GHz
1126             // boost if applicable
1127             if (b.level + bRssiBoost + bRssiBoost5 > a.level + aRssiBoost + aRssiBoost5) {
1128                 // b is the better BSSID
1129                 a = b;
1130             }
1131         }
1132         if (a != null) {
1133             if (VDBG) {
1134                 StringBuilder sb = new StringBuilder();
1135                 sb.append("attemptRoam: " + current.configKey() +
1136                         " Found " + a.BSSID + " rssi=" + a.level + " freq=" + a.frequency);
1137                 if (currentBSSID != null) {
1138                     sb.append(" Current: " + currentBSSID);
1139                 }
1140                 sb.append("\n");
1141                 logDbg(sb.toString());
1142             }
1143         }
1144         return a;
1145     }
1146 
1147     /**
1148      * getNetworkScore()
1149      * <p/>
1150      * if scorer is present, get the network score of a WifiConfiguration
1151      * <p/>
1152      * Note: this should be merge with setVisibility
1153      *
1154      * @param config
1155      * @return score
1156      */
getConfigNetworkScore(WifiConfiguration config, int age, boolean isActive)1157     int getConfigNetworkScore(WifiConfiguration config, int age, boolean isActive) {
1158 
1159         if (mNetworkScoreCache == null) {
1160             if (VDBG) {
1161                 logDbg("       getConfigNetworkScore for " + config.configKey()
1162                         + "  -> no scorer, hence no scores");
1163             }
1164             return WifiNetworkScoreCache.INVALID_NETWORK_SCORE;
1165         }
1166 
1167         if (mWifiConfigStore.getScanDetailCache(config) == null) {
1168             if (VDBG) {
1169                 logDbg("       getConfigNetworkScore for " + config.configKey()
1170                         + " -> no scan cache");
1171             }
1172             return WifiNetworkScoreCache.INVALID_NETWORK_SCORE;
1173         }
1174 
1175         // Get current date
1176         long nowMs = System.currentTimeMillis();
1177 
1178         int startScore = -10000;
1179 
1180         // Run thru all cached scan results
1181         for (ScanDetail sd : mWifiConfigStore.getScanDetailCache(config).values()) {
1182             ScanResult result = sd.getScanResult();
1183             if ((nowMs - sd.getSeen()) < age) {
1184                 int sc = mNetworkScoreCache.getNetworkScore(result, isActive);
1185                 if (sc > startScore) {
1186                     startScore = sc;
1187                 }
1188             }
1189         }
1190         if (startScore == -10000) {
1191             startScore = WifiNetworkScoreCache.INVALID_NETWORK_SCORE;
1192         }
1193         if (VDBG) {
1194             if (startScore == WifiNetworkScoreCache.INVALID_NETWORK_SCORE) {
1195                 logDbg("    getConfigNetworkScore for " + config.configKey()
1196                         + " -> no available score");
1197             } else {
1198                 logDbg("    getConfigNetworkScore for " + config.configKey()
1199                         + " isActive=" + isActive
1200                         + " score = " + Integer.toString(startScore));
1201             }
1202         }
1203 
1204         return startScore;
1205     }
1206 
1207     /**
1208      * Set whether connections to untrusted connections are allowed.
1209      */
setAllowUntrustedConnections(boolean allow)1210     void setAllowUntrustedConnections(boolean allow) {
1211         boolean changed = mAllowUntrustedConnections != allow;
1212         mAllowUntrustedConnections = allow;
1213         if (changed) {
1214             // Trigger a scan so as to reattempt autojoin
1215             mWifiStateMachine.startScanForUntrustedSettingChange();
1216         }
1217     }
1218 
isOpenNetwork(ScanResult result)1219     private boolean isOpenNetwork(ScanResult result) {
1220         return !result.capabilities.contains("WEP") &&
1221                 !result.capabilities.contains("PSK") &&
1222                 !result.capabilities.contains("EAP");
1223     }
1224 
haveRecentlySeenScoredBssid(WifiConfiguration config)1225     private boolean haveRecentlySeenScoredBssid(WifiConfiguration config) {
1226         long ephemeralOutOfRangeTimeoutMs = Settings.Global.getLong(
1227                 mContext.getContentResolver(),
1228                 Settings.Global.WIFI_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS,
1229                 DEFAULT_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS);
1230 
1231         // Check whether the currently selected network has a score curve. If
1232         // ephemeralOutOfRangeTimeoutMs is <= 0, then this is all we check, and we stop here.
1233         // Otherwise, we stop here if the currently selected network has a score. If it doesn't, we
1234         // keep going - it could be that another BSSID is in range (has been seen recently) which
1235         // has a score, even if the one we're immediately connected to doesn't.
1236         ScanResult currentScanResult = mWifiStateMachine.getCurrentScanResult();
1237         boolean currentNetworkHasScoreCurve = currentScanResult != null
1238                 && mNetworkScoreCache.hasScoreCurve(currentScanResult);
1239         if (ephemeralOutOfRangeTimeoutMs <= 0 || currentNetworkHasScoreCurve) {
1240             if (DBG) {
1241                 if (currentNetworkHasScoreCurve) {
1242                     logDbg("Current network has a score curve, keeping network: "
1243                             + currentScanResult);
1244                 } else {
1245                     logDbg("Current network has no score curve, giving up: " + config.SSID);
1246                 }
1247             }
1248             return currentNetworkHasScoreCurve;
1249         }
1250 
1251         if (mWifiConfigStore.getScanDetailCache(config) == null
1252                 || mWifiConfigStore.getScanDetailCache(config).isEmpty()) {
1253             return false;
1254         }
1255 
1256         long currentTimeMs = System.currentTimeMillis();
1257         for (ScanDetail sd : mWifiConfigStore.getScanDetailCache(config).values()) {
1258             ScanResult result = sd.getScanResult();
1259             if (currentTimeMs > sd.getSeen()
1260                     && currentTimeMs - sd.getSeen() < ephemeralOutOfRangeTimeoutMs
1261                     && mNetworkScoreCache.hasScoreCurve(result)) {
1262                 if (DBG) {
1263                     logDbg("Found scored BSSID, keeping network: " + result.BSSID);
1264                 }
1265                 return true;
1266             }
1267         }
1268 
1269         if (DBG) {
1270             logDbg("No recently scored BSSID found, giving up connection: " + config.SSID);
1271         }
1272         return false;
1273     }
1274 
1275     // After WifiStateMachine ask the supplicant to associate or reconnect
1276     // we might still obtain scan results from supplicant
1277     // however the supplicant state in the mWifiInfo and supplicant state tracker
1278     // are updated when we get the supplicant state change message which can be
1279     // processed after the SCAN_RESULT message, so at this point the framework doesn't
1280     // know that supplicant is ASSOCIATING.
1281     // A good fix for this race condition would be for the WifiStateMachine to add
1282     // a new transient state where it expects to get the supplicant message indicating
1283     // that it started the association process and within which critical operations
1284     // like autojoin should be deleted.
1285 
1286     // This transient state would remove the need for the roam Wathchdog which
1287     // basically does that.
1288 
1289     // At the moment, we just query the supplicant state synchronously with the
1290     // mWifiNative.status() command, which allow us to know that
1291     // supplicant has started association process, even though we didnt yet get the
1292     // SUPPLICANT_STATE_CHANGE message.
1293 
1294     private static final List<String> ASSOC_STATES = Arrays.asList(
1295             "ASSOCIATING",
1296             "ASSOCIATED",
1297             "FOUR_WAY_HANDSHAKE",
1298             "GROUP_KEY_HANDSHAKE");
1299 
getNetID(String wpaStatus)1300     private int getNetID(String wpaStatus) {
1301         if (VDBG) {
1302             logDbg("attemptAutoJoin() status=" + wpaStatus);
1303         }
1304 
1305         try {
1306             int id = WifiConfiguration.INVALID_NETWORK_ID;
1307             String state = null;
1308             BufferedReader br = new BufferedReader(new StringReader(wpaStatus));
1309             String line;
1310             while ((line = br.readLine()) != null) {
1311                 int split = line.indexOf('=');
1312                 if (split < 0) {
1313                     continue;
1314                 }
1315 
1316                 String name = line.substring(0, split);
1317                 if (name.equals("id")) {
1318                     try {
1319                         id = Integer.parseInt(line.substring(split + 1));
1320                         if (state != null) {
1321                             break;
1322                         }
1323                     } catch (NumberFormatException nfe) {
1324                         return WifiConfiguration.INVALID_NETWORK_ID;
1325                     }
1326                 } else if (name.equals("wpa_state")) {
1327                     state = line.substring(split + 1);
1328                     if (ASSOC_STATES.contains(state)) {
1329                         return WifiConfiguration.INVALID_NETWORK_ID;
1330                     } else if (id >= 0) {
1331                         break;
1332                     }
1333                 }
1334             }
1335             return id;
1336         } catch (IOException ioe) {
1337             return WifiConfiguration.INVALID_NETWORK_ID;    // Won't happen
1338         }
1339     }
1340 
setCurrentConfigurationKey(WifiConfiguration currentConfig, int supplicantNetId)1341     private boolean setCurrentConfigurationKey(WifiConfiguration currentConfig,
1342                                                int supplicantNetId) {
1343         if (currentConfig != null) {
1344             if (supplicantNetId != currentConfig.networkId
1345                     // https://b.corp.google.com/issue?id=16484607
1346                     // mark this condition as an error only if the mismatched networkId are valid
1347                     && supplicantNetId != WifiConfiguration.INVALID_NETWORK_ID
1348                     && currentConfig.networkId != WifiConfiguration.INVALID_NETWORK_ID) {
1349                 logDbg("attemptAutoJoin() ERROR wpa_supplicant out of sync nid="
1350                         + Integer.toString(supplicantNetId) + " WifiStateMachine="
1351                         + Integer.toString(currentConfig.networkId));
1352                 mWifiStateMachine.disconnectCommand();
1353                 return false;
1354             } else if (currentConfig.ephemeral && (!mAllowUntrustedConnections ||
1355                     !haveRecentlySeenScoredBssid(currentConfig))) {
1356                 // The current connection is untrusted (the framework added it), but we're either
1357                 // no longer allowed to connect to such networks, the score has been nullified
1358                 // since we connected, or the scored BSSID has gone out of range.
1359                 // Drop the current connection and perform the rest of autojoin.
1360                 logDbg("attemptAutoJoin() disconnecting from unwanted ephemeral network");
1361                 mWifiStateMachine.disconnectCommand(Process.WIFI_UID,
1362                         mAllowUntrustedConnections ? 1 : 0);
1363                 return false;
1364             } else {
1365                 mCurrentConfigurationKey = currentConfig.configKey();
1366                 return true;
1367             }
1368         } else {
1369             // If not invalid, then maybe in the process of associating, skip this attempt
1370             return supplicantNetId == WifiConfiguration.INVALID_NETWORK_ID;
1371         }
1372     }
1373 
updateBlackListStatus(WifiConfiguration config, long now)1374     private void updateBlackListStatus(WifiConfiguration config, long now) {
1375         // Wait for 5 minutes before reenabling config that have known,
1376         // repeated connection or DHCP failures
1377         if (config.disableReason == WifiConfiguration.DISABLED_DHCP_FAILURE
1378                 || config.disableReason
1379                 == WifiConfiguration.DISABLED_ASSOCIATION_REJECT
1380                 || config.disableReason
1381                 == WifiConfiguration.DISABLED_AUTH_FAILURE) {
1382             if (config.blackListTimestamp == 0
1383                     || (config.blackListTimestamp > now)) {
1384                 // Sanitize the timestamp
1385                 config.blackListTimestamp = now;
1386             }
1387             if ((now - config.blackListTimestamp) >
1388                     mWifiConfigStore.wifiConfigBlacklistMinTimeMilli) {
1389                 // Re-enable the WifiConfiguration
1390                 config.status = WifiConfiguration.Status.ENABLED;
1391 
1392                 // Reset the blacklist condition
1393                 config.numConnectionFailures = 0;
1394                 config.numIpConfigFailures = 0;
1395                 config.numAuthFailures = 0;
1396                 config.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED);
1397 
1398                 config.dirty = true;
1399             } else {
1400                 if (VDBG) {
1401                     long delay = mWifiConfigStore.wifiConfigBlacklistMinTimeMilli
1402                             - (now - config.blackListTimestamp);
1403                     logDbg("attemptautoJoin " + config.configKey()
1404                             + " dont unblacklist yet, waiting for "
1405                             + delay + " ms");
1406                 }
1407             }
1408         }
1409         // Avoid networks disabled because of AUTH failure altogether
1410         if (DBG) {
1411             logDbg("attemptAutoJoin skip candidate due to auto join status "
1412                     + Integer.toString(config.autoJoinStatus) + " key "
1413                     + config.configKey(true)
1414                     + " reason " + config.disableReason);
1415         }
1416     }
1417 
underSoftThreshold(WifiConfiguration config)1418     boolean underSoftThreshold(WifiConfiguration config) {
1419         return config.visibility.rssi24 < mWifiConfigStore.thresholdUnblacklistThreshold24Soft.get()
1420                 && config.visibility.rssi5 < mWifiConfigStore.thresholdUnblacklistThreshold5Soft.get();
1421     }
1422 
underHardThreshold(WifiConfiguration config)1423     boolean underHardThreshold(WifiConfiguration config) {
1424         return config.visibility.rssi24 < mWifiConfigStore.thresholdUnblacklistThreshold24Hard.get()
1425                 && config.visibility.rssi5 < mWifiConfigStore.thresholdUnblacklistThreshold5Hard.get();
1426     }
1427 
underThreshold(WifiConfiguration config, int rssi24, int rssi5)1428     boolean underThreshold(WifiConfiguration config, int rssi24, int rssi5) {
1429         return config.visibility.rssi24 < rssi24 && config.visibility.rssi5 < rssi5;
1430     }
1431 
1432     /**
1433      * attemptAutoJoin() function implements the core of the a network switching algorithm
1434      * Return false if no acceptable networks were found.
1435      */
attemptAutoJoin()1436     boolean attemptAutoJoin() {
1437         boolean found = false;
1438         didOverride = false;
1439         didBailDueToWeakRssi = false;
1440         int networkSwitchType = AUTO_JOIN_IDLE;
1441         int age = mScanResultAutoJoinAge;
1442 
1443         long now = System.currentTimeMillis();
1444 
1445         String lastSelectedConfiguration = mWifiConfigStore.getLastSelectedConfiguration();
1446         if (lastSelectedConfiguration != null) {
1447             age = 14000;
1448         }
1449         // Reset the currentConfiguration Key, and set it only if WifiStateMachine and
1450         // supplicant agree
1451         mCurrentConfigurationKey = null;
1452         WifiConfiguration currentConfiguration = mWifiStateMachine.getCurrentWifiConfiguration();
1453 
1454         WifiConfiguration candidate = null;
1455         // Obtain the subset of recently seen networks
1456         List<WifiConfiguration> list =
1457                 mWifiConfigStore.getRecentConfiguredNetworks(age, false);
1458         if (list == null) {
1459             if (VDBG) logDbg("attemptAutoJoin nothing known=" +
1460                     mWifiConfigStore.getConfiguredNetworksSize());
1461             return false;
1462         }
1463 
1464         // Find the currently connected network: ask the supplicant directly
1465         int supplicantNetId = getNetID(mWifiNative.status(true));
1466 
1467         if (DBG) {
1468             String conf = "";
1469             String last = "";
1470             if (currentConfiguration != null) {
1471                 conf = " current=" + currentConfiguration.configKey();
1472             }
1473             if (lastSelectedConfiguration != null) {
1474                 last = " last=" + lastSelectedConfiguration;
1475             }
1476             logDbg("attemptAutoJoin() num recent config " + Integer.toString(list.size())
1477                     + conf + last
1478                     + " ---> suppNetId=" + Integer.toString(supplicantNetId));
1479         }
1480 
1481         if (!setCurrentConfigurationKey(currentConfiguration, supplicantNetId)) {
1482             return false;
1483         }
1484 
1485         int currentNetId = -1;
1486         if (currentConfiguration != null) {
1487             // If we are associated to a configuration, it will
1488             // be compared thru the compareNetwork function
1489             currentNetId = currentConfiguration.networkId;
1490         }
1491 
1492         /**
1493          * Run thru all visible configurations without looking at the one we
1494          * are currently associated to
1495          * select Best Network candidate from known WifiConfigurations
1496          */
1497         for (WifiConfiguration config : list) {
1498             if (config.SSID == null) {
1499                 continue;
1500             }
1501 
1502             if (config.autoJoinStatus >= WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE) {
1503                 updateBlackListStatus(config, now);
1504                 continue;
1505             }
1506 
1507             if (config.userApproved == WifiConfiguration.USER_PENDING ||
1508                     config.userApproved == WifiConfiguration.USER_BANNED) {
1509                 if (DBG) {
1510                     logDbg("attemptAutoJoin skip candidate due to user approval status "
1511                             + WifiConfiguration.userApprovedAsString(config.userApproved) + " key "
1512                             + config.configKey(true));
1513                 }
1514                 continue;
1515             }
1516 
1517             // Try to un-blacklist based on elapsed time
1518             if (config.blackListTimestamp > 0) {
1519                 if (now < config.blackListTimestamp) {
1520                     /**
1521                      * looks like there was a change in the system clock since we black listed, and
1522                      * timestamp is not meaningful anymore, hence lose it.
1523                      * this event should be rare enough so that we still want to lose the black list
1524                      */
1525                     config.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED);
1526                 } else {
1527                     if ((now - config.blackListTimestamp) > loseBlackListHardMilli) {
1528                         // Reenable it after 18 hours, i.e. next day
1529                         config.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED);
1530                     } else if ((now - config.blackListTimestamp) > loseBlackListSoftMilli) {
1531                         // Lose blacklisting due to bad link
1532                         config.setAutoJoinStatus(config.autoJoinStatus - 8);
1533                     }
1534                 }
1535             }
1536 
1537             if (config.visibility == null) {
1538                 continue;
1539             }
1540 
1541             // Try to unblacklist based on good visibility
1542             if (underSoftThreshold(config)) {
1543                 if (DBG) {
1544                     logDbg("attemptAutoJoin do not unblacklist due to low visibility " +
1545                             config.configKey() + " status=" + config.autoJoinStatus);
1546                 }
1547             } else if (underHardThreshold(config)) {
1548                 // If the network is simply temporary disabled, don't allow reconnect until
1549                 // RSSI becomes good enough
1550                 config.setAutoJoinStatus(config.autoJoinStatus - 1);
1551                 if (DBG) {
1552                     logDbg("attemptAutoJoin good candidate seen, bumped soft -> status=" +
1553                             config.configKey() + " status=" + config.autoJoinStatus);
1554                 }
1555             } else {
1556                 config.setAutoJoinStatus(config.autoJoinStatus - 3);
1557                 if (DBG) {
1558                     logDbg("attemptAutoJoin good candidate seen, bumped hard -> status=" +
1559                             config.configKey() + " status=" + config.autoJoinStatus);
1560                 }
1561             }
1562 
1563             if (config.autoJoinStatus >=
1564                     WifiConfiguration.AUTO_JOIN_TEMPORARY_DISABLED) {
1565                 // Network is blacklisted, skip
1566                 if (DBG) {
1567                     logDbg("attemptAutoJoin skip blacklisted -> status=" +
1568                             config.configKey() + " status=" + config.autoJoinStatus);
1569                 }
1570                 continue;
1571             }
1572             if (config.networkId == currentNetId) {
1573                 if (DBG) {
1574                     logDbg("attemptAutoJoin skip current candidate  "
1575                             + Integer.toString(currentNetId)
1576                             + " key " + config.configKey(true));
1577                 }
1578                 continue;
1579             }
1580 
1581             boolean isLastSelected = false;
1582             if (lastSelectedConfiguration != null &&
1583                     config.configKey().equals(lastSelectedConfiguration)) {
1584                 isLastSelected = true;
1585             }
1586 
1587             if (config.lastRoamingFailure != 0
1588                     && currentConfiguration != null
1589                     && (lastSelectedConfiguration == null
1590                     || !config.configKey().equals(lastSelectedConfiguration))) {
1591                 // Apply blacklisting for roaming to this config if:
1592                 //   - the target config had a recent roaming failure
1593                 //   - we are currently associated
1594                 //   - the target config is not the last selected
1595                 if (now > config.lastRoamingFailure
1596                         && (now - config.lastRoamingFailure)
1597                         < config.roamingFailureBlackListTimeMilli) {
1598                     if (DBG) {
1599                         logDbg("compareNetwork not switching to " + config.configKey()
1600                                 + " from current " + currentConfiguration.configKey()
1601                                 + " because it is blacklisted due to roam failure, "
1602                                 + " blacklist remain time = "
1603                                 + (now - config.lastRoamingFailure) + " ms");
1604                     }
1605                     continue;
1606                 }
1607             }
1608 
1609             int boost = config.autoJoinUseAggressiveJoinAttemptThreshold + weakRssiBailCount;
1610             if (underThreshold(config,
1611                     mWifiConfigStore.thresholdInitialAutoJoinAttemptMin24RSSI.get() - boost,
1612                     mWifiConfigStore.thresholdInitialAutoJoinAttemptMin5RSSI.get() - boost)) {
1613 
1614                 if (DBG) {
1615                     logDbg("attemptAutoJoin skip due to low visibility " + config.configKey());
1616                 }
1617 
1618                 // Don't try to autojoin a network that is too far but
1619                 // If that configuration is a user's choice however, try anyway
1620                 if (!isLastSelected) {
1621                     config.autoJoinBailedDueToLowRssi = true;
1622                     didBailDueToWeakRssi = true;
1623                     continue;
1624                 } else {
1625                     // Next time, try to be a bit more aggressive in auto-joining
1626                     if (config.autoJoinUseAggressiveJoinAttemptThreshold
1627                             < WifiConfiguration.MAX_INITIAL_AUTO_JOIN_RSSI_BOOST
1628                             && config.autoJoinBailedDueToLowRssi) {
1629                         config.autoJoinUseAggressiveJoinAttemptThreshold += 4;
1630                     }
1631                 }
1632             }
1633             // NOTE: If this condition is updated, update NETWORK_STATUS_UNWANTED_DISABLE_AUTOJOIN.
1634             if (config.numNoInternetAccessReports > 0
1635                     && !isLastSelected
1636                     && !config.validatedInternetAccess) {
1637                 // Avoid autoJoining this network because last time we used it, it didn't
1638                 // have internet access, and we never manage to validate internet access on this
1639                 // network configuration
1640                 if (DBG) {
1641                     logDbg("attemptAutoJoin skip candidate due to no InternetAccess  "
1642                             + config.configKey(true)
1643                             + " num reports " + config.numNoInternetAccessReports);
1644                 }
1645                 continue;
1646             }
1647 
1648             if (DBG) {
1649                 String cur = "";
1650                 if (candidate != null) {
1651                     cur = " current candidate " + candidate.configKey();
1652                 }
1653                 logDbg("attemptAutoJoin trying id="
1654                         + Integer.toString(config.networkId) + " "
1655                         + config.configKey(true)
1656                         + " status=" + config.autoJoinStatus
1657                         + cur);
1658             }
1659 
1660             if (candidate == null) {
1661                 candidate = config;
1662             } else {
1663                 if (VDBG) {
1664                     logDbg("attemptAutoJoin will compare candidate  " + candidate.configKey()
1665                             + " with " + config.configKey());
1666                 }
1667 
1668                 int order = compareWifiConfigurations(candidate, config);
1669                 if (VDBG) {
1670                     logDbg("attemptAutoJoin compareWifiConfigurations returned " + order);
1671                 }
1672 
1673                 // The lastSelectedConfiguration is the configuration the user has manually selected
1674                 // thru WifiPicker, or that a 3rd party app asked us to connect to via the
1675                 // enableNetwork with disableOthers=true WifiManager API
1676                 // As this is a direct user choice, we strongly prefer this configuration,
1677                 // hence give +/-100
1678                 if ((lastSelectedConfiguration != null)
1679                         && candidate.configKey().equals(lastSelectedConfiguration)) {
1680                     // candidate is the last selected configuration,
1681                     // so keep it above connect choices (+/-60) and
1682                     // above RSSI/scorer based selection of linked configuration (+/- 50)
1683                     // by reducing order by -100
1684                     order = order - 100;
1685                     if (VDBG) {
1686                         logDbg("     ...and prefers -100 " + candidate.configKey()
1687                                 + " over " + config.configKey()
1688                                 + " because it is the last selected -> "
1689                                 + Integer.toString(order));
1690                     }
1691                 } else if ((lastSelectedConfiguration != null)
1692                         && config.configKey().equals(lastSelectedConfiguration)) {
1693                     // config is the last selected configuration,
1694                     // so keep it above connect choices (+/-60) and
1695                     // above RSSI/scorer based selection of linked configuration (+/- 50)
1696                     // by increasing order by +100
1697                     order = order + 100;
1698                     if (VDBG) {
1699                         logDbg("     ...and prefers +100 " + config.configKey()
1700                                 + " over " + candidate.configKey()
1701                                 + " because it is the last selected -> "
1702                                 + Integer.toString(order));
1703                     }
1704                 }
1705 
1706                 if (order > 0) {
1707                     // Ascending : candidate < config
1708                     candidate = config;
1709                 }
1710             }
1711         }
1712 
1713         // Now, go thru scan result to try finding a better untrusted network
1714         if (mNetworkScoreCache != null && mAllowUntrustedConnections) {
1715             int rssi5 = WifiConfiguration.INVALID_RSSI;
1716             int rssi24 = WifiConfiguration.INVALID_RSSI;
1717             if (candidate != null) {
1718                 rssi5 = candidate.visibility.rssi5;
1719                 rssi24 = candidate.visibility.rssi24;
1720             }
1721 
1722             // Get current date
1723             long nowMs = System.currentTimeMillis();
1724             int currentScore = -10000;
1725             // The untrusted network with highest score
1726             ScanDetail untrustedCandidate = null;
1727             // Look for untrusted scored network only if the current candidate is bad
1728             if (isBadCandidate(rssi24, rssi5)) {
1729                 for (ScanDetail scanDetail : scanResultCache.values()) {
1730                     ScanResult result = scanDetail.getScanResult();
1731                     // We look only at untrusted networks with a valid SSID
1732                     // A trusted result would have been looked at thru it's Wificonfiguration
1733                     if (TextUtils.isEmpty(result.SSID) || !result.untrusted ||
1734                             !isOpenNetwork(result)) {
1735                         continue;
1736                     }
1737                     String quotedSSID = "\"" + result.SSID + "\"";
1738                     if (mWifiConfigStore.mDeletedEphemeralSSIDs.contains(quotedSSID)) {
1739                         // SSID had been Forgotten by user, then don't score it
1740                         continue;
1741                     }
1742                     if ((nowMs - result.seen) < mScanResultAutoJoinAge) {
1743                         // Increment usage count for the network
1744                         mWifiConnectionStatistics.incrementOrAddUntrusted(quotedSSID, 0, 1);
1745 
1746                         boolean isActiveNetwork = currentConfiguration != null
1747                                 && currentConfiguration.SSID.equals(quotedSSID);
1748                         int score = mNetworkScoreCache.getNetworkScore(result, isActiveNetwork);
1749                         if (score != WifiNetworkScoreCache.INVALID_NETWORK_SCORE
1750                                 && score > currentScore) {
1751                             // Highest score: Select this candidate
1752                             currentScore = score;
1753                             untrustedCandidate = scanDetail;
1754                             if (VDBG) {
1755                                 logDbg("AutoJoinController: found untrusted candidate "
1756                                         + result.SSID
1757                                         + " RSSI=" + result.level
1758                                         + " freq=" + result.frequency
1759                                         + " score=" + score);
1760                             }
1761                         }
1762                     }
1763                 }
1764             }
1765             if (untrustedCandidate != null) {
1766                 // At this point, we have an untrusted network candidate.
1767                 // Create the new ephemeral configuration and see if we should switch over
1768                 candidate =
1769                         mWifiConfigStore.wifiConfigurationFromScanResult(untrustedCandidate);
1770                 candidate.allowedKeyManagement.set(KeyMgmt.NONE);
1771                 candidate.ephemeral = true;
1772                 candidate.dirty = true;
1773             }
1774         }
1775 
1776         long lastUnwanted =
1777                 System.currentTimeMillis()
1778                         - mWifiConfigStore.lastUnwantedNetworkDisconnectTimestamp;
1779         if (candidate == null
1780                 && lastSelectedConfiguration == null
1781                 && currentConfiguration == null
1782                 && didBailDueToWeakRssi
1783                 && (mWifiConfigStore.lastUnwantedNetworkDisconnectTimestamp == 0
1784                 || lastUnwanted > (1000 * 60 * 60 * 24 * 7))
1785                 ) {
1786             // We are bailing out of autojoin although we are seeing a weak configuration, and
1787             // - we didn't find another valid candidate
1788             // - we are not connected
1789             // - without a user network selection choice
1790             // - ConnectivityService has not triggered an unwanted network disconnect
1791             //       on this device for a week (hence most likely there is no SIM card or cellular)
1792             // If all those conditions are met, then boost the RSSI of the weak networks
1793             // that we are seeing so as we will eventually pick one
1794             if (weakRssiBailCount < 10)
1795                 weakRssiBailCount += 1;
1796         } else {
1797             if (weakRssiBailCount > 0)
1798                 weakRssiBailCount -= 1;
1799         }
1800 
1801         /**
1802          *  If candidate is found, check the state of the connection so as
1803          *  to decide if we should be acting on this candidate and switching over
1804          */
1805         int networkDelta = compareNetwork(candidate, lastSelectedConfiguration);
1806         if (DBG && candidate != null) {
1807             String doSwitch = "";
1808             String current = "";
1809             if (networkDelta < 0) {
1810                 doSwitch = " -> not switching";
1811             }
1812             if (currentConfiguration != null) {
1813                 current = " with current " + currentConfiguration.configKey();
1814             }
1815             logDbg("attemptAutoJoin networkSwitching candidate "
1816                     + candidate.configKey()
1817                     + current
1818                     + " linked=" + (currentConfiguration != null
1819                     && currentConfiguration.isLinked(candidate))
1820                     + " : delta="
1821                     + Integer.toString(networkDelta) + " "
1822                     + doSwitch);
1823         }
1824 
1825         /**
1826          * Ask WifiStateMachine permission to switch :
1827          * if user is currently streaming voice traffic,
1828          * then we should not be allowed to switch regardless of the delta
1829          */
1830         if (mWifiStateMachine.shouldSwitchNetwork(networkDelta)) {      // !!! JNo: Here!
1831             if (mStaStaSupported) {
1832                 logDbg("mStaStaSupported --> error do nothing now ");
1833             } else {
1834                 if (currentConfiguration != null && currentConfiguration.isLinked(candidate)) {
1835                     networkSwitchType = AUTO_JOIN_EXTENDED_ROAMING;
1836                 } else {
1837                     networkSwitchType = AUTO_JOIN_OUT_OF_NETWORK_ROAMING;
1838                 }
1839                 if (DBG) {
1840                     logDbg("AutoJoin auto connect with netId "
1841                             + Integer.toString(candidate.networkId)
1842                             + " to " + candidate.configKey());
1843                 }
1844                 if (didOverride) {
1845                     candidate.numScorerOverrideAndSwitchedNetwork++;
1846                 }
1847                 candidate.numAssociation++;
1848                 mWifiConnectionStatistics.numAutoJoinAttempt++;
1849 
1850                 if (candidate.ephemeral) {
1851                     // We found a new candidate that we are going to connect to, then
1852                     // increase its connection count
1853                     mWifiConnectionStatistics.
1854                             incrementOrAddUntrusted(candidate.SSID, 1, 0);
1855                 }
1856 
1857                 if (candidate.BSSID == null || candidate.BSSID.equals("any")) {
1858                     // First step we selected the configuration we want to connect to
1859                     // Second step: Look for the best Scan result for this configuration
1860                     // TODO this algorithm should really be done in one step
1861                     String currentBSSID = mWifiStateMachine.getCurrentBSSID();
1862                     ScanResult roamCandidate =
1863                             attemptRoam(null, candidate, mScanResultAutoJoinAge, null);
1864                     if (roamCandidate != null && currentBSSID != null
1865                             && currentBSSID.equals(roamCandidate.BSSID)) {
1866                         // Sanity, we were already asociated to that candidate
1867                         roamCandidate = null;
1868                     }
1869                     if (roamCandidate != null && roamCandidate.is5GHz()) {
1870                         // If the configuration hasn't a default BSSID selected, and the best
1871                         // candidate is 5GHZ, then select this candidate so as WifiStateMachine and
1872                         // supplicant will pick it first
1873                         candidate.autoJoinBSSID = roamCandidate.BSSID;
1874                         if (VDBG) {
1875                             logDbg("AutoJoinController: lock to 5GHz "
1876                                     + candidate.autoJoinBSSID
1877                                     + " RSSI=" + roamCandidate.level
1878                                     + " freq=" + roamCandidate.frequency);
1879                         }
1880                     } else {
1881                         // We couldnt find a roam candidate
1882                         candidate.autoJoinBSSID = "any";
1883                     }
1884                 }
1885                 mWifiStateMachine.sendMessage(WifiStateMachine.CMD_AUTO_CONNECT,
1886                         candidate.networkId, networkSwitchType, candidate);
1887                 found = true;
1888             }
1889         }
1890 
1891         if (networkSwitchType == AUTO_JOIN_IDLE && !mWifiConfigStore.enableHalBasedPno.get()) {
1892             String currentBSSID = mWifiStateMachine.getCurrentBSSID();
1893             // Attempt same WifiConfiguration roaming
1894             ScanResult roamCandidate =
1895                     attemptRoam(null, currentConfiguration, mScanResultAutoJoinAge, currentBSSID);
1896             if (roamCandidate != null && currentBSSID != null
1897                     && currentBSSID.equals(roamCandidate.BSSID)) {
1898                 roamCandidate = null;
1899             }
1900             if (roamCandidate != null && mWifiStateMachine.shouldSwitchNetwork(999)) {
1901                 if (DBG) {
1902                     logDbg("AutoJoin auto roam with netId "
1903                             + Integer.toString(currentConfiguration.networkId)
1904                             + " " + currentConfiguration.configKey() + " to BSSID="
1905                             + roamCandidate.BSSID + " freq=" + roamCandidate.frequency
1906                             + " RSSI=" + roamCandidate.level);
1907                 }
1908                 networkSwitchType = AUTO_JOIN_ROAMING;
1909                 mWifiConnectionStatistics.numAutoRoamAttempt++;
1910 
1911                 mWifiStateMachine.sendMessage(WifiStateMachine.CMD_AUTO_ROAM,
1912                         currentConfiguration.networkId, 1, roamCandidate);
1913                 found = true;
1914             }
1915         }
1916         if (VDBG) logDbg("Done attemptAutoJoin status=" + Integer.toString(networkSwitchType));
1917         return found;
1918     }
1919 
logDenial(String reason, WifiConfiguration config)1920     private void logDenial(String reason, WifiConfiguration config) {
1921         if (!DBG) {
1922             return;
1923         }
1924         logDbg(reason + config.toString());
1925     }
1926 
getWifiConfiguration(WifiNative.WifiPnoNetwork network)1927     WifiConfiguration getWifiConfiguration(WifiNative.WifiPnoNetwork network) {
1928         if (network.configKey != null) {
1929             return mWifiConfigStore.getWifiConfiguration(network.configKey);
1930         }
1931         return null;
1932     }
1933 
getPnoList(WifiConfiguration current)1934     ArrayList<WifiNative.WifiPnoNetwork> getPnoList(WifiConfiguration current) {
1935         int size = -1;
1936         ArrayList<WifiNative.WifiPnoNetwork> list = new ArrayList<WifiNative.WifiPnoNetwork>();
1937 
1938         if (mWifiConfigStore.mCachedPnoList != null) {
1939             size = mWifiConfigStore.mCachedPnoList.size();
1940         }
1941 
1942         if (DBG) {
1943             String s = "";
1944             if (current != null) {
1945                 s = " for: " + current.configKey();
1946             }
1947             Log.e(TAG, " get Pno List total size:" + size + s);
1948         }
1949         if (current != null) {
1950             String configKey = current.configKey();
1951             /**
1952              * If we are currently associated to a WifiConfiguration then include
1953              * only those networks that have a higher priority
1954              */
1955             for (WifiNative.WifiPnoNetwork network : mWifiConfigStore.mCachedPnoList) {
1956                 WifiConfiguration config = getWifiConfiguration(network);
1957                 if (config == null) {
1958                     continue;
1959                 }
1960                 if (config.autoJoinStatus
1961                         >= WifiConfiguration.AUTO_JOIN_DISABLED_NO_CREDENTIALS) {
1962                      continue;
1963                 }
1964 
1965                 if (!configKey.equals(network.configKey)) {
1966                     int choice = getConnectChoice(config, current, true);
1967                     if (choice > 0) {
1968                         // config is of higher priority
1969                         if (DBG) {
1970                             Log.e(TAG, " Pno List adding:" + network.configKey
1971                                     + " choice " + choice);
1972                         }
1973                         list.add(network);
1974                         network.rssi_threshold = mWifiConfigStore.thresholdGoodRssi24.get();
1975                     }
1976                 }
1977             }
1978         } else {
1979             for (WifiNative.WifiPnoNetwork network : mWifiConfigStore.mCachedPnoList) {
1980                 WifiConfiguration config = getWifiConfiguration(network);
1981                 if (config == null) {
1982                     continue;
1983                 }
1984                 if (config.autoJoinStatus
1985                         >= WifiConfiguration.AUTO_JOIN_DISABLED_NO_CREDENTIALS) {
1986                     continue;
1987                 }
1988                 list.add(network);
1989                 network.rssi_threshold = mWifiConfigStore.thresholdGoodRssi24.get();
1990             }
1991         }
1992         return list;
1993     }
1994 }
1995 
1996