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