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