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