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