WifiAutoJoinController.java revision 448c9536a302c58a79e271b1721c08b8882f800e
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 (rssi
810                > mWifiConfigStore.thresholdBandPreferenceLowRssi5) {
811            // Boost by 2 dB for each point
812            //    Start boosting at -65
813            //    Boost by 20 if above -55
814            //    Boost by 40 if abore -45
815            int boost = mWifiConfigStore.factorBandPreferenceLowRssi5
816                    *(rssi - mWifiConfigStore.thresholdBandPreferenceLowRssi5);
817            if (boost > 50) {
818                // 50 dB boost is set so as to overcome the hysteresis of +20 plus a difference of
819                // 25 dB between 2.4 and 5GHz band. This allows jumping from 2.4 to 5GHz
820                // consistently
821                boost = 50;
822            }
823            if (VDBG && dbg != null) {
824                logDbg("        " + dbg + ":    rssi5 " + rssi + " boost " + boost);
825            }
826            return boost;
827        }
828
829        if (rssi
830                < mWifiConfigStore.thresholdBandPreferenceRssi24) {
831            // penalize by 10 if < -75
832            return -10;
833        }
834        return 0;
835    }
836        /**
837         * attemptRoam() function implements the core of the same SSID switching algorithm
838         *
839         * Run thru all recent scan result of a WifiConfiguration and select the
840         * best one.
841         */
842    public ScanResult attemptRoam(ScanResult a,
843                                  WifiConfiguration current, int age, String currentBSSID) {
844        if (current == null) {
845            if (VDBG)   {
846                logDbg("attemptRoam not associated");
847            }
848            return a;
849        }
850        if (current.scanResultCache == null) {
851            if (VDBG)   {
852                logDbg("attemptRoam no scan cache");
853            }
854            return a;
855        }
856        if (current.scanResultCache.size() > 6) {
857            if (VDBG)   {
858                logDbg("attemptRoam scan cache size "
859                        + current.scanResultCache.size() + " --> bail");
860            }
861            // Implement same SSID roaming only for configurations
862            // that have less than 4 BSSIDs
863            return a;
864        }
865
866        if (current.BSSID != null && !current.BSSID.equals("any")) {
867            if (DBG)   {
868                logDbg("attemptRoam() BSSID is set "
869                        + current.BSSID + " -> bail");
870            }
871            return a;
872        }
873
874        // Determine which BSSID we want to associate to, taking account
875        // relative strength of 5 and 2.4 GHz BSSIDs
876        long nowMs = System.currentTimeMillis();
877
878        for (ScanResult b : current.scanResultCache.values()) {
879            int bRssiBoost5 = 0;
880            int aRssiBoost5 = 0;
881            int bRssiBoost = 0;
882            int aRssiBoost = 0;
883            if ((b.seen == 0) || (b.BSSID == null)
884                    || ((nowMs - b.seen) > age)
885                    || b.autoJoinStatus != ScanResult.ENABLED) {
886                continue;
887            }
888
889            // Pick first one
890            if (a == null) {
891                a = b;
892                continue;
893            }
894
895            // Apply hysteresis: we favor the currentBSSID by giving it a boost
896            if (currentBSSID != null && currentBSSID.equals(b.BSSID)) {
897                // Reduce the benefit of hysteresis if RSSI <= -75
898                if (b.level <= mWifiConfigStore.thresholdBandPreferenceRssi24) {
899                    bRssiBoost = +8;
900                } else {
901                    bRssiBoost = +14;
902                }
903            }
904            if (currentBSSID != null && currentBSSID.equals(a.BSSID)) {
905                if (a.level <= mWifiConfigStore.thresholdBandPreferenceRssi24) {
906                    // Reduce the benefit of hysteresis if RSSI <= -75
907                    aRssiBoost = +8;
908                } else {
909                    aRssiBoost = +14;
910                }
911            }
912
913            // Favor 5GHz: give a boost to 5GHz BSSIDs, with a slightly progressive curve
914            //   Boost the BSSID if it is on 5GHz, above a threshold
915            //   But penalize it if it is on 5GHz and below threshold
916            //
917            //   With he current threshold values, 5GHz network with RSSI above -55
918            //   Are given a boost of 30DB which is enough to overcome the current BSSID
919            //   hysteresis (+14) plus 2.4/5 GHz signal strength difference on most cases
920            //
921            // The "current BSSID" Boost must be added to the BSSID's level so as to introduce\
922            // soem amount of hysteresis
923            if (b.is5GHz()) {
924                bRssiBoost5 = rssiBoostFrom5GHzRssi(b.level + bRssiBoost, b.BSSID);
925            }
926            if (a.is5GHz()) {
927                aRssiBoost5 = rssiBoostFrom5GHzRssi(a.level + aRssiBoost, a.BSSID);
928            }
929
930            if (VDBG)  {
931                String comp = " < ";
932                if (b.level + bRssiBoost + bRssiBoost5 > a.level +aRssiBoost + aRssiBoost5) {
933                    comp = " > ";
934                }
935                logDbg("attemptRoam: "
936                        + b.BSSID + " rssi=" + b.level + " boost=" + Integer.toString(bRssiBoost)
937                        + "/" + Integer.toString(bRssiBoost5) + " freq=" + b.frequency
938                        + comp
939                        + a.BSSID + " rssi=" + a.level + " boost=" + Integer.toString(aRssiBoost)
940                        + "/" + Integer.toString(aRssiBoost5) + " freq=" + a.frequency);
941            }
942
943            // Compare the RSSIs after applying the hysteresis boost and the 5GHz
944            // boost if applicable
945            if (b.level + bRssiBoost + bRssiBoost5 > a.level +aRssiBoost + aRssiBoost5) {
946                // b is the better BSSID
947                a = b;
948            }
949        }
950        if (a != null) {
951            if (VDBG)  {
952                StringBuilder sb = new StringBuilder();
953                sb.append("attemptRoam: " + current.configKey() +
954                        " Found " + a.BSSID + " rssi=" + a.level + " freq=" + a.frequency);
955                if (currentBSSID != null) {
956                    sb.append(" Current: " + currentBSSID);
957                }
958                sb.append("\n");
959                logDbg(sb.toString());
960            }
961        }
962        return a;
963    }
964
965    /**
966     * getNetworkScore()
967     *
968     * if scorer is present, get the network score of a WifiConfiguration
969     *
970     * Note: this should be merge with setVisibility
971     *
972     * @param config
973     * @return score
974     */
975    int getConfigNetworkScore(WifiConfiguration config, int age, int rssiBoost) {
976
977        if (mNetworkScoreCache == null) {
978            if (VDBG) {
979                logDbg("       getConfigNetworkScore for " + config.configKey()
980                        + "  -> no scorer, hence no scores");
981            }
982            return WifiNetworkScoreCache.INVALID_NETWORK_SCORE;
983        }
984        if (config.scanResultCache == null) {
985            if (VDBG) {
986                logDbg("       getConfigNetworkScore for " + config.configKey()
987                        + " -> no scan cache");
988            }
989            return WifiNetworkScoreCache.INVALID_NETWORK_SCORE;
990        }
991
992        // Get current date
993        long nowMs = System.currentTimeMillis();
994
995        int startScore = -10000;
996
997        // Run thru all cached scan results
998        for (ScanResult result : config.scanResultCache.values()) {
999            if ((nowMs - result.seen) < age) {
1000                int sc = mNetworkScoreCache.getNetworkScore(result, rssiBoost);
1001                if (sc > startScore) {
1002                    startScore = sc;
1003                }
1004            }
1005        }
1006        if (startScore == -10000) {
1007            startScore = WifiNetworkScoreCache.INVALID_NETWORK_SCORE;
1008        }
1009        if (VDBG) {
1010            if (startScore == WifiNetworkScoreCache.INVALID_NETWORK_SCORE) {
1011                logDbg("    getConfigNetworkScore for " + config.configKey()
1012                        + " -> no available score");
1013            } else {
1014                logDbg("    getConfigNetworkScore for " + config.configKey()
1015                        + " boost=" + Integer.toString(rssiBoost)
1016                        + " score = " + Integer.toString(startScore));
1017            }
1018        }
1019
1020        return startScore;
1021    }
1022
1023    /**
1024     * attemptAutoJoin() function implements the core of the a network switching algorithm
1025     */
1026    void attemptAutoJoin() {
1027        didOverride = false;
1028        int networkSwitchType = AUTO_JOIN_IDLE;
1029
1030        String lastSelectedConfiguration = mWifiConfigStore.getLastSelectedConfiguration();
1031
1032        // Reset the currentConfiguration Key, and set it only if WifiStateMachine and
1033        // supplicant agree
1034        mCurrentConfigurationKey = null;
1035        WifiConfiguration currentConfiguration = mWifiStateMachine.getCurrentWifiConfiguration();
1036
1037        WifiConfiguration candidate = null;
1038
1039        // Obtain the subset of recently seen networks
1040        List<WifiConfiguration> list = mWifiConfigStore.getRecentConfiguredNetworks(3000, false);
1041        if (list == null) {
1042            if (VDBG)  logDbg("attemptAutoJoin nothing known=" +
1043                    mWifiConfigStore.getconfiguredNetworkSize());
1044            return;
1045        }
1046
1047        // Find the currently connected network: ask the supplicant directly
1048        String val = mWifiNative.status();
1049        String status[] = val.split("\\r?\\n");
1050        if (VDBG) {
1051            logDbg("attemptAutoJoin() status=" + val + " split="
1052                    + Integer.toString(status.length));
1053        }
1054
1055        int supplicantNetId = -1;
1056        for (String key : status) {
1057            if (key.regionMatches(0, "id=", 0, 3)) {
1058                int idx = 3;
1059                supplicantNetId = 0;
1060                while (idx < key.length()) {
1061                    char c = key.charAt(idx);
1062
1063                    if ((c >= 0x30) && (c <= 0x39)) {
1064                        supplicantNetId *= 10;
1065                        supplicantNetId += c - 0x30;
1066                        idx++;
1067                    } else {
1068                        break;
1069                    }
1070                }
1071            } else if (key.contains("wpa_state=ASSOCIATING")
1072                    || key.contains("wpa_state=ASSOCIATED")
1073                    || key.contains("wpa_state=FOUR_WAY_HANDSHAKE")
1074                    || key.contains("wpa_state=GROUP_KEY_HANDSHAKE")) {
1075                if (DBG) {
1076                    logDbg("attemptAutoJoin: bail out due to sup state " + key);
1077                }
1078                // After WifiStateMachine ask the supplicant to associate or reconnect
1079                // we might still obtain scan results from supplicant
1080                // however the supplicant state in the mWifiInfo and supplicant state tracker
1081                // are updated when we get the supplicant state change message which can be
1082                // processed after the SCAN_RESULT message, so at this point the framework doesn't
1083                // know that supplicant is ASSOCIATING.
1084                // A good fix for this race condition would be for the WifiStateMachine to add
1085                // a new transient state where it expects to get the supplicant message indicating
1086                // that it started the association process and within which critical operations
1087                // like autojoin should be deleted.
1088
1089                // This transient state would remove the need for the roam Wathchdog which
1090                // basically does that.
1091
1092                // At the moment, we just query the supplicant state synchronously with the
1093                // mWifiNative.status() command, which allow us to know that
1094                // supplicant has started association process, even though we didnt yet get the
1095                // SUPPLICANT_STATE_CHANGE message.
1096                return;
1097            }
1098        }
1099        if (DBG) {
1100            String conf = "";
1101            String last = "";
1102            if (currentConfiguration != null) {
1103                conf = " curent=" + currentConfiguration.configKey();
1104            }
1105            if (lastSelectedConfiguration != null) {
1106                last = " last=" + lastSelectedConfiguration;
1107            }
1108            logDbg("attemptAutoJoin() num recent config " + Integer.toString(list.size())
1109                    + conf + last
1110                    + " ---> suppNetId=" + Integer.toString(supplicantNetId));
1111        }
1112
1113        if (currentConfiguration != null) {
1114            if (supplicantNetId != currentConfiguration.networkId
1115                    //https://b.corp.google.com/issue?id=16484607
1116                    //mark this confition as an error only if the mismatched networkId are valid
1117                    && supplicantNetId != WifiConfiguration.INVALID_NETWORK_ID
1118                    && currentConfiguration.networkId != WifiConfiguration.INVALID_NETWORK_ID) {
1119                logDbg("attemptAutoJoin() ERROR wpa_supplicant out of sync nid="
1120                        + Integer.toString(supplicantNetId) + " WifiStateMachine="
1121                        + Integer.toString(currentConfiguration.networkId));
1122                mWifiStateMachine.disconnectCommand();
1123                return;
1124            } else {
1125                mCurrentConfigurationKey = currentConfiguration.configKey();
1126            }
1127        } else {
1128            if (supplicantNetId != WifiConfiguration.INVALID_NETWORK_ID) {
1129                // Maybe in the process of associating, skip this attempt
1130                return;
1131            }
1132        }
1133
1134        int currentNetId = -1;
1135        if (currentConfiguration != null) {
1136            // If we are associated to a configuration, it will
1137            // be compared thru the compareNetwork function
1138            currentNetId = currentConfiguration.networkId;
1139        }
1140
1141        /**
1142         * Run thru all visible configurations without looking at the one we
1143         * are currently associated to
1144         * select Best Network candidate from known WifiConfigurations
1145         */
1146        for (WifiConfiguration config : list) {
1147            if ((config.status == WifiConfiguration.Status.DISABLED)
1148                    && (config.disableReason == WifiConfiguration.DISABLED_AUTH_FAILURE)) {
1149                if (DBG) {
1150                    logDbg("attemptAutoJoin skip candidate due to auth failure: "
1151                            + config.configKey(true));
1152                }
1153                continue;
1154            }
1155
1156            if (config.SSID == null) {
1157                continue;
1158            }
1159
1160            if (config.autoJoinStatus >=
1161                    WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE) {
1162                // Avoid networks disabled because of AUTH failure altogether
1163                if (DBG) {
1164                    logDbg("attemptAutoJoin skip candidate due to auto join status "
1165                            + Integer.toString(config.autoJoinStatus) + " key "
1166                            + config.configKey(true));
1167                }
1168                continue;
1169            }
1170
1171            // Try to un-blacklist based on elapsed time
1172            if (config.blackListTimestamp > 0) {
1173                long now = System.currentTimeMillis();
1174                if (now < config.blackListTimestamp) {
1175                    /**
1176                     * looks like there was a change in the system clock since we black listed, and
1177                     * timestamp is not meaningful anymore, hence lose it.
1178                     * this event should be rare enough so that we still want to lose the black list
1179                     */
1180                    config.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED);
1181                } else {
1182                    if ((now - config.blackListTimestamp) > loseBlackListHardMilli) {
1183                        // Reenable it after 18 hours, i.e. next day
1184                        config.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED);
1185                    } else if ((now - config.blackListTimestamp) > loseBlackListSoftMilli) {
1186                        // Lose blacklisting due to bad link
1187                        config.setAutoJoinStatus(config.autoJoinStatus - 8);
1188                    }
1189                }
1190            }
1191
1192            // Try to unblacklist based on good visibility
1193            if (config.visibility.rssi5 < mWifiConfigStore.thresholdUnblacklistThreshold5Soft
1194                    && config.visibility.rssi24
1195                    < mWifiConfigStore.thresholdUnblacklistThreshold24Soft) {
1196                if (DBG) {
1197                    logDbg("attemptAutoJoin do not unblacklist due to low visibility "
1198                            + config.autoJoinStatus
1199                            + " key " + config.configKey(true)
1200                            + " rssi=(" + config.visibility.rssi24
1201                            + "," + config.visibility.rssi5
1202                            + ") num=(" + config.visibility.num24
1203                            + "," + config.visibility.num5 + ")");
1204                }
1205            } else if (config.visibility.rssi5 < mWifiConfigStore.thresholdUnblacklistThreshold5Hard
1206                    && config.visibility.rssi24
1207                    < mWifiConfigStore.thresholdUnblacklistThreshold24Hard) {
1208                // If the network is simply temporary disabled, don't allow reconnect until
1209                // RSSI becomes good enough
1210                config.setAutoJoinStatus(config.autoJoinStatus - 1);
1211                if (DBG) {
1212                    logDbg("attemptAutoJoin good candidate seen, bumped soft -> status="
1213                            + config.autoJoinStatus
1214                            + " " + config.configKey(true) + " rssi=("
1215                            + config.visibility.rssi24 + "," + config.visibility.rssi5
1216                            + ") num=(" + config.visibility.num24
1217                            + "," + config.visibility.num5 + ")");
1218                }
1219            } else {
1220                config.setAutoJoinStatus(config.autoJoinStatus - 3);
1221                if (DBG) {
1222                    logDbg("attemptAutoJoin good candidate seen, bumped hard -> status="
1223                            + config.autoJoinStatus
1224                            + " " + config.configKey(true) + " rssi=("
1225                            + config.visibility.rssi24 + "," + config.visibility.rssi5
1226                            + ") num=(" + config.visibility.num24
1227                            + "," + config.visibility.num5 + ")");
1228                }
1229            }
1230
1231            if (config.autoJoinStatus >=
1232                    WifiConfiguration.AUTO_JOIN_TEMPORARY_DISABLED) {
1233                // Network is blacklisted, skip
1234                if (DBG) {
1235                    logDbg("attemptAutoJoin skip blacklisted -> status="
1236                            + config.autoJoinStatus
1237                            + " " + config.configKey(true) + " rssi=("
1238                            + config.visibility.rssi24 + "," + config.visibility.rssi5
1239                            + ") num=(" + config.visibility.num24
1240                            + "," + config.visibility.num5 + ")");
1241                }
1242                continue;
1243            }
1244            if (config.networkId == currentNetId) {
1245                if (DBG) {
1246                    logDbg("attemptAutoJoin skip current candidate  "
1247                            + Integer.toString(currentNetId)
1248                            + " key " + config.configKey(true));
1249                }
1250                continue;
1251            }
1252
1253            if (lastSelectedConfiguration == null ||
1254                    !config.configKey().equals(lastSelectedConfiguration)) {
1255                // Don't try to autojoin a network that is too far but
1256                // If that configuration is a user's choice however, try anyway
1257                if (config.visibility == null) {
1258                    continue;
1259                }
1260                if (config.visibility.rssi5
1261                        < mWifiConfigStore.thresholdInitialAutoJoinAttemptMin5RSSI
1262                        && config.visibility.rssi24
1263                        < mWifiConfigStore.thresholdInitialAutoJoinAttemptMin24RSSI) {
1264                    if (DBG) {
1265                        logDbg("attemptAutoJoin skip due to low visibility -> status="
1266                                + config.autoJoinStatus
1267                                + " key " + config.configKey(true) + " rssi="
1268                                + config.visibility.rssi24 + ", " + config.visibility.rssi5
1269                                + " num=" + config.visibility.num24
1270                                + ", " + config.visibility.num5);
1271                    }
1272                    continue;
1273                }
1274                if (config.noInternetAccess) {
1275                    // Avoid autojoining this network because last time we used it, it didn't
1276                    // have internet access
1277                    if (DBG) {
1278                        logDbg("attemptAutoJoin skip candidate due to noInternetAccess flag "
1279                                + config.configKey(true));
1280                    }
1281                    continue;
1282                }
1283            }
1284
1285            if (DBG) {
1286                logDbg("attemptAutoJoin trying candidate id="
1287                        + Integer.toString(config.networkId) + " "
1288                        + config.configKey(true)
1289                        + " status=" + config.autoJoinStatus);
1290            }
1291
1292            if (candidate == null) {
1293                candidate = config;
1294            } else {
1295                if (VDBG)  {
1296                    logDbg("attemptAutoJoin will compare candidate  " + candidate.configKey()
1297                            + " with " + config.configKey());
1298                }
1299                int order = compareWifiConfigurationsTop(candidate, config);
1300
1301                // The lastSelectedConfiguration is the configuration the user has manually selected
1302                // thru WifiPicker, or that a 3rd party app asked us to connect to via the
1303                // enableNetwork with disableOthers=true WifiManager API
1304                // As this is a direct user choice, we strongly prefer this configuration,
1305                // hence give +/-100
1306                if ((lastSelectedConfiguration != null)
1307                        && candidate.configKey().equals(lastSelectedConfiguration)) {
1308                    // candidate is the last selected configuration,
1309                    // so keep it above connect choices (+/-60) and
1310                    // above RSSI/scorer based selection of linked configuration (+/- 50)
1311                    // by reducing order by -100
1312                    order = order - 100;
1313                    if (VDBG)   {
1314                        logDbg("     ...and prefers -100 " + candidate.configKey()
1315                                + " over " + config.configKey()
1316                                + " because it is the last selected -> "
1317                                + Integer.toString(order));
1318                    }
1319                } else if ((lastSelectedConfiguration != null)
1320                        && config.configKey().equals(lastSelectedConfiguration)) {
1321                    // config is the last selected configuration,
1322                    // so keep it above connect choices (+/-60) and
1323                    // above RSSI/scorer based selection of linked configuration (+/- 50)
1324                    // by increasing order by +100
1325                    order = order + 100;
1326                    if (VDBG)   {
1327                        logDbg("     ...and prefers +100 " + config.configKey()
1328                                + " over " + candidate.configKey()
1329                                + " because it is the last selected -> "
1330                                + Integer.toString(order));
1331                    }
1332                }
1333
1334                if (order > 0) {
1335                    // Ascending : candidate < config
1336                    candidate = config;
1337                }
1338            }
1339        }
1340
1341        // Wait for VPN to be available on the system to make use of this code
1342        // Now, go thru scan result to try finding a better untrusted network
1343        if (mNetworkScoreCache != null) {
1344            int rssi5 = WifiConfiguration.INVALID_RSSI;
1345            int rssi24 = WifiConfiguration.INVALID_RSSI;
1346            WifiConfiguration.Visibility visibility;
1347            if (candidate != null) {
1348                rssi5 = candidate.visibility.rssi5;
1349                rssi24 = candidate.visibility.rssi24;
1350            }
1351
1352            // Get current date
1353            long nowMs = System.currentTimeMillis();
1354            int currentScore = -10000;
1355            // The untrusted network with highest score
1356            ScanResult untrustedCandidate = null;
1357            // Look for untrusted scored network only if the current candidate is bad
1358            if (isBadCandidate(rssi24, rssi5)) {
1359                for (ScanResult result : scanResultCache.values()) {
1360                    int rssiBoost = 0;
1361                    // We look only at untrusted networks with a valid SSID
1362                    // A trusted result would have been looked at thru it's Wificonfiguration
1363                    if (TextUtils.isEmpty(result.SSID) || !result.untrusted) {
1364                        continue;
1365                    }
1366                    if ((nowMs - result.seen) < 3000) {
1367                        // Increment usage count for the network
1368                        mWifiConnectionStatistics.incrementOrAddUntrusted(result.SSID, 0, 1);
1369
1370                        if (lastUntrustedBSSID != null
1371                                && result.BSSID.equals(lastUntrustedBSSID)) {
1372                            // Apply a large hysteresis to the untrusted network we are connected to
1373                            rssiBoost = 25;
1374                        }
1375                        int score = mNetworkScoreCache.getNetworkScore(result, rssiBoost);
1376                        if (score != WifiNetworkScoreCache.INVALID_NETWORK_SCORE
1377                                && score > currentScore) {
1378                            // Highest score: Select this candidate
1379                            currentScore = score;
1380                            untrustedCandidate = result;
1381                            if (VDBG) {
1382                                logDbg("AutoJoinController: found untrusted candidate "
1383                                        + result.SSID
1384                                + " RSSI=" + result.level
1385                                + " freq=" + result.frequency
1386                                + " score=" + score);
1387                            }
1388                        }
1389                    }
1390                }
1391            }
1392            if (untrustedCandidate != null) {
1393                if (lastUntrustedBSSID == null
1394                        || !untrustedCandidate.SSID.equals(lastUntrustedBSSID)) {
1395                    // We found a new candidate that we are going to connect to, then
1396                    // increase its connection count
1397                    mWifiConnectionStatistics.
1398                            incrementOrAddUntrusted(untrustedCandidate.SSID, 1, 0);
1399                    // Remember which SSID we are connecting to
1400                    lastUntrustedBSSID = untrustedCandidate.SSID;
1401                }
1402            }
1403            // Now we don't have VPN, and thus don't actually connect to the untrusted candidate
1404            untrustedCandidate = null;
1405        }
1406
1407        /**
1408         *  If candidate is found, check the state of the connection so as
1409         *  to decide if we should be acting on this candidate and switching over
1410         */
1411        int networkDelta = compareNetwork(candidate);
1412        if (DBG && candidate != null) {
1413            String doSwitch = "";
1414            String current = "";
1415            if (networkDelta < 0) {
1416                doSwitch = " -> not switching";
1417            }
1418            if (currentConfiguration != null) {
1419                current = " with current " + currentConfiguration.configKey();
1420            }
1421            logDbg("attemptAutoJoin networkSwitching candidate "
1422                    + candidate.configKey()
1423                    + current
1424                    + " linked=" + (currentConfiguration != null
1425                            && currentConfiguration.isLinked(candidate))
1426                    + " : delta="
1427                    + Integer.toString(networkDelta) + " "
1428                    + doSwitch);
1429        }
1430
1431        /**
1432         * Ask WifiStateMachine permission to switch :
1433         * if user is currently streaming voice traffic,
1434         * then we should not be allowed to switch regardless of the delta
1435         */
1436        if (mWifiStateMachine.shouldSwitchNetwork(networkDelta)) {
1437            if (mStaStaSupported) {
1438                logDbg("mStaStaSupported --> error do nothing now ");
1439            } else {
1440                if (currentConfiguration != null && currentConfiguration.isLinked(candidate)) {
1441                    networkSwitchType = AUTO_JOIN_EXTENDED_ROAMING;
1442                } else {
1443                    networkSwitchType = AUTO_JOIN_OUT_OF_NETWORK_ROAMING;
1444                }
1445                if (DBG) {
1446                    logDbg("AutoJoin auto connect with netId "
1447                            + Integer.toString(candidate.networkId)
1448                            + " to " + candidate.configKey());
1449                }
1450                if (didOverride) {
1451                    candidate.numScorerOverrideAndSwitchedNetwork++;
1452                }
1453                candidate.numAssociation++;
1454                mWifiConnectionStatistics.numAutoJoinAttempt++;
1455
1456                if (candidate.BSSID == null || candidate.BSSID.equals("any")) {
1457                    // First step we selected the configuration we want to connect to
1458                    // Second step: Look for the best Scan result for this configuration
1459                    // TODO this algorithm should really be done in one step
1460                    String currentBSSID = mWifiStateMachine.getCurrentBSSID();
1461                    ScanResult roamCandidate = attemptRoam(null, candidate, 3000, null);
1462                    if (roamCandidate != null && currentBSSID != null
1463                            && currentBSSID.equals(roamCandidate.BSSID)) {
1464                        // Sanity, we were already asociated to that candidate
1465                        roamCandidate = null;
1466                    }
1467                    if (roamCandidate != null && roamCandidate.is5GHz()) {
1468                        // If the configuration hasn't a default BSSID selected, and the best
1469                        // candidate is 5GHZ, then select this candidate so as WifiStateMachine and
1470                        // supplicant will pick it first
1471                        candidate.autoJoinBSSID = roamCandidate.BSSID;
1472                        if (VDBG) {
1473                            logDbg("AutoJoinController: lock to 5GHz "
1474                                    + candidate.autoJoinBSSID
1475                                    + " RSSI=" + roamCandidate.level
1476                                    + " freq=" + roamCandidate.frequency);
1477                        }
1478                    } else {
1479                        // We couldnt find a roam candidate
1480                        candidate.autoJoinBSSID = "any";
1481                    }
1482                }
1483                mWifiStateMachine.sendMessage(WifiStateMachine.CMD_AUTO_CONNECT,
1484                            candidate.networkId, networkSwitchType, candidate);
1485            }
1486        }
1487
1488        if (networkSwitchType == AUTO_JOIN_IDLE) {
1489            String currentBSSID = mWifiStateMachine.getCurrentBSSID();
1490            // Attempt same WifiConfiguration roaming
1491            ScanResult roamCandidate = attemptRoam(null, currentConfiguration, 3000,
1492                    currentBSSID);
1493            /**
1494             *  TODO: (post L initial release)
1495             *  consider handling linked configurations roaming (i.e. extended Roaming)
1496             *  thru the attemptRoam function which makes use of the RSSI roaming threshold.
1497             *  At the moment, extended roaming is only handled thru the attemptAutoJoin()
1498             *  function which compare configurations.
1499             *
1500             *  The advantage of making use of attemptRoam function is that this function
1501             *  will looks at all the BSSID of each configurations, instead of only looking
1502             *  at WifiConfiguration.visibility which keeps trackonly of the RSSI/band of the
1503             *  two highest BSSIDs.
1504             */
1505            // Attempt linked WifiConfiguration roaming
1506            /* if (currentConfiguration != null
1507                    && currentConfiguration.linkedConfigurations != null) {
1508                for (String key : currentConfiguration.linkedConfigurations.keySet()) {
1509                    WifiConfiguration link = mWifiConfigStore.getWifiConfiguration(key);
1510                    if (link != null) {
1511                        roamCandidate = attemptRoam(roamCandidate, link, 3000,
1512                                currentBSSID);
1513                    }
1514                }
1515            }*/
1516            if (roamCandidate != null && currentBSSID != null
1517                    && currentBSSID.equals(roamCandidate.BSSID)) {
1518                roamCandidate = null;
1519            }
1520            if (roamCandidate != null && mWifiStateMachine.shouldSwitchNetwork(999)) {
1521                if (DBG) {
1522                    logDbg("AutoJoin auto roam with netId "
1523                            + Integer.toString(currentConfiguration.networkId)
1524                            + " " + currentConfiguration.configKey() + " to BSSID="
1525                            + roamCandidate.BSSID + " freq=" + roamCandidate.frequency
1526                            + " RSSI=" + roamCandidate.frequency);
1527                }
1528                networkSwitchType = AUTO_JOIN_ROAMING;
1529                mWifiConnectionStatistics.numAutoRoamAttempt++;
1530
1531                mWifiStateMachine.sendMessage(WifiStateMachine.CMD_AUTO_ROAM,
1532                            currentConfiguration.networkId, 1, roamCandidate);
1533            }
1534        }
1535        if (VDBG) logDbg("Done attemptAutoJoin status=" + Integer.toString(networkSwitchType));
1536    }
1537}
1538
1539