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