WifiAutoJoinController.java revision 3a2a3d226881cce8a4e511302231d843b0def303
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.wifi;
18
19import android.content.Context;
20
21import android.net.NetworkKey;
22import android.net.NetworkScoreManager;
23import android.net.WifiKey;
24import android.net.wifi.WifiConfiguration;
25import android.net.wifi.ScanResult;
26import android.net.wifi.WifiManager;
27
28import android.os.SystemClock;
29import android.os.Process;
30import android.util.Log;
31
32import java.util.ArrayList;
33import java.util.Collection;
34import java.util.Iterator;
35import java.util.HashMap;
36import java.util.List;
37import java.util.Date;
38
39/**
40 * AutoJoin controller is responsible for WiFi Connect decision
41 *
42 * It runs in the thread context of WifiStateMachine
43 *
44 */
45public class WifiAutoJoinController {
46
47    private Context mContext;
48    private WifiStateMachine mWifiStateMachine;
49    private WifiConfigStore mWifiConfigStore;
50    private WifiTrafficPoller mWifiTrafficPoller;
51    private WifiNative mWifiNative;
52
53    private NetworkScoreManager scoreManager;
54    private WifiNetworkScoreCache mNetworkScoreCache;
55
56    private static final String TAG = "WifiAutoJoinController ";
57    private static boolean DBG = false;
58    private static boolean VDBG = false;
59    private static final boolean mStaStaSupported = false;
60    private static final int SCAN_RESULT_CACHE_SIZE = 80;
61
62    private String mCurrentConfigurationKey = null; //used by autojoin
63
64    private HashMap<String, ScanResult> scanResultCache =
65            new HashMap<String, ScanResult>();
66
67    //lose the non-auth failure blacklisting after 8 hours
68    private final static long loseBlackListHardMilli = 1000 * 60 * 60 * 8;
69    //lose some temporary blacklisting after 30 minutes
70    private final static long loseBlackListSoftMilli = 1000 * 60 * 30;
71
72    public static final int AUTO_JOIN_IDLE = 0;
73    public static final int AUTO_JOIN_ROAMING = 1;
74    public static final int AUTO_JOIN_EXTENDED_ROAMING = 2;
75    public static final int AUTO_JOIN_OUT_OF_NETWORK_ROAMING = 3;
76
77    WifiAutoJoinController(Context c, WifiStateMachine w, WifiConfigStore s,
78                           WifiTrafficPoller t, WifiNative n) {
79        mContext = c;
80        mWifiStateMachine = w;
81        mWifiConfigStore = s;
82        mWifiTrafficPoller = t;
83        mWifiNative = n;
84        mNetworkScoreCache = null;
85        scoreManager =
86                (NetworkScoreManager) mContext.getSystemService(Context.NETWORK_SCORE_SERVICE);
87        if (scoreManager == null)
88            logDbg("Registered scoreManager NULL " + " service " + Context.NETWORK_SCORE_SERVICE);
89
90        if (scoreManager != null) {
91            mNetworkScoreCache = new WifiNetworkScoreCache(mContext);
92            scoreManager.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
93        } else {
94            logDbg("No network score service: Couldnt register as a WiFi score Manager, type="
95                    + Integer.toString(NetworkKey.TYPE_WIFI)
96                    + " service " + Context.NETWORK_SCORE_SERVICE);
97            mNetworkScoreCache = null;
98        }
99    }
100
101    void enableVerboseLogging(int verbose) {
102        if (verbose > 0 ) {
103            DBG = true;
104            VDBG = true;
105        } else {
106            DBG = false;
107            VDBG = false;
108        }
109    }
110
111    int mScanResultMaximumAge = 30000; /* milliseconds unit */
112
113    /*
114     * flush out scan results older than mScanResultMaximumAge
115     *
116     * */
117    private void ageScanResultsOut(int delay) {
118        if (delay <= 0) {
119            delay = mScanResultMaximumAge; //something sane
120        }
121        long milli = System.currentTimeMillis();
122        if (VDBG) {
123            logDbg("ageScanResultsOut delay " + Integer.valueOf(delay) + " size "
124                    + Integer.valueOf(scanResultCache.size()) + " now " + Long.valueOf(milli));
125        }
126
127        Iterator<HashMap.Entry<String,ScanResult>> iter = scanResultCache.entrySet().iterator();
128        while (iter.hasNext()) {
129            HashMap.Entry<String,ScanResult> entry = iter.next();
130            ScanResult result = entry.getValue();
131
132            if ((result.seen + delay) < milli) {
133                iter.remove();
134            }
135        }
136    }
137
138    void addToScanCache(List<ScanResult> scanList) {
139        WifiConfiguration associatedConfig;
140
141        ArrayList<NetworkKey> unknownScanResults = new ArrayList<NetworkKey>();
142
143        for(ScanResult result: scanList) {
144            if (result.SSID == null) continue;
145            result.seen = System.currentTimeMillis();
146
147            ScanResult sr = scanResultCache.get(result.BSSID);
148            if (sr != null) {
149                // if there was a previous cache result for this BSSID, average the RSSI values
150
151                int previous_rssi = sr.level;
152                long previously_seen_milli = sr.seen;
153
154                /* average RSSI with previously seen instances of this scan result */
155                int avg_rssi = result.level;
156
157                if ((previously_seen_milli > 0)
158                        && (previously_seen_milli < mScanResultMaximumAge/2)) {
159                    /*
160                    *
161                    * previously_seen_milli = 0 => RSSI = 0.5 * previous_seen_rssi + 0.5 * new_rssi
162                    *
163                    * If previously_seen_milli is 15+ seconds old:
164                    *      previously_seen_milli = 15000 => RSSI = new_rssi
165                    *
166                    */
167
168                    double alpha = 0.5 - (double)previously_seen_milli
169                            / (double)mScanResultMaximumAge;
170
171                    avg_rssi = (int)((double)avg_rssi * (1-alpha) + (double)previous_rssi * alpha);
172                }
173                result.level = avg_rssi;
174
175                //remove the previous Scan Result
176                scanResultCache.remove(result.BSSID);
177            } else {
178                if (!mNetworkScoreCache.isScoredNetwork(result)) {
179                    WifiKey wkey;
180                    //TODO : find out how we can get there without a valid UTF-8 encoded SSID
181                    //TODO: which will cause WifiKey constructor to fail
182                    try {
183                        wkey = new WifiKey("\"" + result.SSID + "\"", result.BSSID);
184                    } catch (IllegalArgumentException e) {
185                        logDbg("AutoJoinController: received badly encoded SSID=[" + result.SSID +
186                                "] ->skipping this network");
187                        wkey = null;
188                    }
189                    if (wkey != null) {
190                        NetworkKey nkey = new NetworkKey(wkey);
191                        //if we don't know this scan result then request a score to Herrevad
192                        unknownScanResults.add(nkey);
193                    }
194                }
195            }
196
197            scanResultCache.put(result.BSSID, new ScanResult(result));
198
199            //add this BSSID to the scanResultCache of the relevant WifiConfiguration
200            associatedConfig = mWifiConfigStore.updateSavedNetworkHistory(result);
201
202            //try to associate this BSSID to an existing Saved WifiConfiguration
203            if (associatedConfig == null) {
204                associatedConfig = mWifiConfigStore.associateWithConfiguration(result);
205                if (associatedConfig != null && associatedConfig.SSID != null) {
206                    if (VDBG) {
207                        logDbg("addToScanCache save associated config "
208                                + associatedConfig.SSID + " with " + associatedConfig.SSID);
209                    }
210                    mWifiStateMachine.sendMessage(WifiManager.SAVE_NETWORK, associatedConfig);
211                }
212            }
213        }
214
215        if (unknownScanResults.size() != 0) {
216            NetworkKey[] newKeys =
217                    unknownScanResults.toArray(new NetworkKey[unknownScanResults.size()]);
218                //kick the score manager, we will get updated scores asynchronously
219            scoreManager.requestScores(newKeys);
220        }
221    }
222
223    void logDbg(String message) {
224        logDbg(message, false);
225    }
226
227    void logDbg(String message, boolean stackTrace) {
228        long now = SystemClock.elapsedRealtimeNanos();
229        String ts = String.format("[%,d us] ", now / 1000);
230        if (stackTrace) {
231            Log.e(TAG, ts + message + " stack:"
232                    + Thread.currentThread().getStackTrace()[2].getMethodName() + " - "
233                    + Thread.currentThread().getStackTrace()[3].getMethodName() + " - "
234                    + Thread.currentThread().getStackTrace()[4].getMethodName() + " - "
235                    + Thread.currentThread().getStackTrace()[5].getMethodName());
236        } else {
237            Log.e(TAG, ts + message);
238        }
239    }
240
241    /* called directly from WifiStateMachine  */
242    void newSupplicantResults() {
243        List<ScanResult> scanList = mWifiStateMachine.syncGetScanResultsList();
244        addToScanCache(scanList);
245        ageScanResultsOut(mScanResultMaximumAge);
246        if (DBG)
247           logDbg("newSupplicantResults size=" + Integer.valueOf(scanResultCache.size()) );
248
249        attemptAutoJoin();
250        mWifiConfigStore.writeKnownNetworkHistory();
251
252    }
253
254
255    /* not used at the moment
256     * should be a call back from WifiScanner HAL ??
257     * this function is not hooked and working yet, it will receive scan results from WifiScanners
258     * with the list of IEs,then populate the capabilities by parsing the IEs and inject the scan
259     * results as normal.
260     */
261    void newHalScanResults() {
262        List<ScanResult> scanList = null;//mWifiScanner.syncGetScanResultsList();
263        String akm = WifiParser.parse_akm(null, null);
264        logDbg(akm);
265        addToScanCache(scanList);
266        ageScanResultsOut(0);
267        attemptAutoJoin();
268        mWifiConfigStore.writeKnownNetworkHistory();
269    }
270
271    /* network link quality changed, called directly from WifiTrafficPoller,
272    or by listening to Link Quality intent */
273    void linkQualitySignificantChange() {
274        attemptAutoJoin();
275    }
276
277    /*
278     * compare a WifiConfiguration against the current network, return a delta score
279     * If not associated, and the candidate will always be better
280     * For instance if the candidate is a home network versus an unknown public wifi,
281     * the delta will be infinite, else compare Kepler scores etc…
282     * Negatve return values from this functions are meaningless per se, just trying to
283     * keep them distinct for debug purpose (i.e. -1, -2 etc...)
284     ***/
285    private int compareNetwork(WifiConfiguration candidate) {
286        if (candidate == null)
287            return -3;
288
289        WifiConfiguration currentNetwork = mWifiStateMachine.getCurrentWifiConfiguration();
290        if (currentNetwork == null) {
291           return 1000;
292        }
293
294        if (candidate.configKey(true).equals(currentNetwork.configKey(true))) {
295            return -2;
296        }
297
298        int order = compareWifiConfigurations(currentNetwork, candidate);
299
300        if (order > 0) {
301            //ascending: currentNetwork < candidate
302            return 10; //will try switch over to the candidate
303        }
304
305        return 0;
306    }
307
308    /**
309     * update the network history fields fo that configuration
310     * - if userTriggered, we mark the configuration as "non selfAdded" since the user has seen it
311     * and took over management
312     * - if it is a "connect", remember which network were there at the point of the connect, so
313     * as those networks get a relative lower score than the selected configuration
314     *
315     * @param netId
316     * @param userTriggered : if the update come from WiFiManager
317     * @param connect : if the update includes a connect
318     **/
319    public void updateConfigurationHistory(int netId, boolean userTriggered, boolean connect) {
320        WifiConfiguration selected = mWifiConfigStore.getWifiConfiguration(netId);
321        if (selected == null) {
322            return;
323        }
324
325        if (selected.SSID == null) {
326            return;
327        }
328
329        if (userTriggered) {
330            // reenable autojoin for this network,
331            // since the user want to connect to this configuration
332            selected.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED);
333            selected.selfAdded = false;
334        }
335
336        if (DBG && userTriggered) {
337            if (selected.connectChoices != null) {
338                logDbg("updateConfigurationHistory will update "
339                        + Integer.toString(netId) + " now: "
340                        + Integer.toString(selected.connectChoices.size())
341                        + " uid=" + Integer.toString(selected.creatorUid), true);
342            } else {
343                logDbg("updateConfigurationHistory will update "
344                        + Integer.toString(netId)
345                        + " uid=" + Integer.toString(selected.creatorUid), true);
346            }
347        }
348
349        if (connect && userTriggered) {
350            boolean found = false;
351            List<WifiConfiguration> networks =
352                    mWifiConfigStore.getRecentConfiguredNetworks(12000, false);
353            if (networks != null) {
354                for (WifiConfiguration config : networks) {
355                    if (DBG) {
356                        logDbg("updateConfigurationHistory got " + config.SSID + " nid="
357                                + Integer.toString(config.networkId));
358                    }
359
360                    if (selected.configKey(true).equals(config.configKey(true))) {
361                        found = true;
362                        continue;
363                    }
364
365                    int rssi = WifiConfiguration.INVALID_RSSI;
366                    if (config.visibility != null) {
367                        rssi = config.visibility.rssi5;
368                        if (config.visibility.rssi24 > rssi)
369                            rssi = config.visibility.rssi24;
370                    }
371                    if (rssi < -80) {
372                        continue;
373                    }
374
375                    //the selected configuration was preferred over a recently seen config
376                    //hence remember the user's choice:
377                    //add the recently seen config to the selected's connectChoices array
378
379                    if (selected.connectChoices == null) {
380                        selected.connectChoices = new HashMap<String, Integer>();
381                    }
382
383                    logDbg("updateConfigurationHistory add a choice " + selected.configKey(true)
384                            + " over " + config.configKey(true)
385                            + " RSSI " + Integer.toString(rssi));
386
387                    //add the visible config to the selected's connect choice list
388                    selected.connectChoices.put(config.configKey(true), rssi);
389
390                    if (config.connectChoices != null) {
391                        if (VDBG) {
392                            logDbg("updateConfigurationHistory will remove "
393                                    + selected.configKey(true) + " from " + config.configKey(true));
394                        }
395                        //remove the selected from the recently seen config's connectChoice list
396                        config.connectChoices.remove(selected.configKey(true));
397
398                        if (selected.linkedConfigurations != null) {
399                           //remove the selected's linked configuration from the
400                           //recently seen config's connectChoice list
401                           for (String key : selected.linkedConfigurations.keySet()) {
402                               config.connectChoices.remove(key);
403                           }
404                        }
405                    }
406                }
407                if (found == false) {
408                     // log an error for now but do something stringer later
409                     // we will need a new scan before attempting to connect to this
410                     // configuration anyhow and thus we can process the scan results then
411                     logDbg("updateConfigurationHistory try to connect to an old network!! : "
412                             + selected.configKey());
413                }
414
415                if (selected.connectChoices != null) {
416                    if (VDBG)
417                        logDbg("updateConfigurationHistory " + Integer.toString(netId)
418                                + " now: " + Integer.toString(selected.connectChoices.size()));
419                }
420            }
421        }
422
423        //TODO: write only if something changed
424        if (userTriggered || connect) {
425            mWifiConfigStore.writeKnownNetworkHistory();
426        }
427    }
428
429    void printChoices(WifiConfiguration config) {
430        int num = 0;
431        if (config.connectChoices!= null) {
432            num = config.connectChoices.size();
433        }
434
435        logDbg("printChoices " + config.SSID + " num choices: " + Integer.toString(num));
436        if (config.connectChoices!= null) {
437            for (String key : config.connectChoices.keySet()) {
438                logDbg("                 " + key);
439            }
440        }
441    }
442
443    boolean hasConnectChoice(WifiConfiguration source, WifiConfiguration target) {
444        boolean found = false;
445        if (source == null)
446            return false;
447        if (target == null)
448            return false;
449
450        if (source.connectChoices != null) {
451            if ( source.connectChoices.get(target.configKey(true)) != null) {
452                found = true;
453            }
454        }
455
456        if (source.linkedConfigurations != null) {
457            for (String key : source.linkedConfigurations.keySet()) {
458                WifiConfiguration config = mWifiConfigStore.getWifiConfiguration(key);
459                if (config != null) {
460                    if (config.connectChoices != null) {
461                        if (config.connectChoices.get(target.configKey(true)) != null) {
462                            found = true;
463                        }
464                    }
465                }
466            }
467
468        }
469        return found;
470    }
471
472    int compareWifiConfigurationsRSSI(WifiConfiguration a, WifiConfiguration b) {
473        int order = 0;
474        int boost5 = 25;
475        WifiConfiguration.Visibility astatus = a.visibility;
476        WifiConfiguration.Visibility bstatus = b.visibility;
477        if (astatus == null || bstatus == null) {
478            //error -> cant happen, need to throw en exception
479            logDbg("compareWifiConfigurations NULL band status!");
480            return 0;
481        }
482        if ((astatus.rssi5 > -70) && (bstatus.rssi5 == WifiConfiguration.INVALID_RSSI)
483                && ((astatus.rssi5 + boost5) > (bstatus.rssi24))) {
484            //a is seen on 5GHz with good RSSI, greater rssi than b
485            //a is of higher priority - descending
486            order = -1;
487        } else if ((bstatus.rssi5 > -70) && (astatus.rssi5 == WifiConfiguration.INVALID_RSSI)
488                && ((bstatus.rssi5 + boost5) > (bstatus.rssi24))) {
489            //b is seen on 5GHz with good RSSI, greater rssi than a
490            //a is of lower priority - ascending
491            order = 1;
492        }
493        return order;
494    }
495
496
497    int compareWifiConfigurations(WifiConfiguration a, WifiConfiguration b) {
498        int order = 0;
499        String lastSelectedConfiguration = mWifiConfigStore.getLastSelectedConfiguration();
500        boolean linked = false;
501
502        if ((a.linkedConfigurations != null) && (b.linkedConfigurations != null)
503                && (a.autoJoinStatus == WifiConfiguration.AUTO_JOIN_ENABLED)
504                && (b.autoJoinStatus == WifiConfiguration.AUTO_JOIN_ENABLED)) {
505            if ((a.linkedConfigurations.get(b.configKey(true))!= null)
506                    && (b.linkedConfigurations.get(a.configKey(true))!= null)) {
507                linked = true;
508            }
509        }
510
511        if (a.ephemeral && b.ephemeral == false) {
512            if (VDBG) {
513                logDbg("compareWifiConfigurations ephemeral and prefers " + b.configKey()
514                        + " over " + a.configKey());
515            }
516            return 1; //b is of higher priority - ascending
517        }
518        if (b.ephemeral && a.ephemeral == false) {
519            if (VDBG) {
520                logDbg("compareWifiConfigurations ephemeral and prefers " +a.configKey()
521                        + " over " + b.configKey());
522            }
523            return -1; //a is of higher priority - descending
524        }
525
526        int aRssiBoost5 = 0;
527        int bRssiBoost5 = 0;
528        //apply Hysteresis: boost the RSSI value of the currently connected configuration
529        int aRssiBoost = 0;
530        int bRssiBoost = 0;
531        if (null != mCurrentConfigurationKey) {
532            if (a.configKey().equals(mCurrentConfigurationKey)) {
533                aRssiBoost += 10;
534            } else if (b.configKey().equals(mCurrentConfigurationKey)) {
535                bRssiBoost += 10;
536            }
537        }
538        if (linked) {
539            int ascore;
540            int bscore;
541            // then we try prefer 5GHz, and try to ignore user's choice
542            WifiConfiguration.Visibility astatus = a.visibility;
543            WifiConfiguration.Visibility bstatus = b.visibility;
544            if (astatus == null || bstatus == null) {
545                //error
546                logDbg("compareWifiConfigurations NULL band status!");
547                return 0;
548            }
549
550            if (VDBG)  {
551                logDbg("compareWifiConfigurations linked: " + Integer.toString(astatus.rssi5)
552                        + "," + Integer.toString(astatus.rssi24) + "   "
553                        + Integer.toString(bstatus.rssi5) + ","
554                        + Integer.toString(bstatus.rssi24));
555            }
556
557            //Boost RSSI value of 5GHz bands iff the base value is better than -65
558            //This implements band preference where we prefer 5GHz if RSSI5 is good enough, whereas
559            //we prefer 2.4GHz otherwise.
560            //Note that 2.4GHz doesn't need a boost since at equal power the RSSI is 6-10 dB higher
561            if ((astatus.rssi5+aRssiBoost) > WifiConfiguration.A_BAND_PREFERENCE_RSSI_THRESHOLD) {
562                aRssiBoost5 = 25;
563            }
564            if ((bstatus.rssi5+bRssiBoost) > WifiConfiguration.A_BAND_PREFERENCE_RSSI_THRESHOLD) {
565                bRssiBoost5 = 25;
566            }
567
568            if (astatus.rssi5+aRssiBoost5 > astatus.rssi24) {
569                //prefer a's 5GHz
570                ascore = astatus.rssi5 + aRssiBoost5 + aRssiBoost;
571            } else {
572                //prefer a's 2.4GHz
573                ascore = astatus.rssi24 + aRssiBoost;
574            }
575            if (bstatus.rssi5+bRssiBoost5 > bstatus.rssi24) {
576                //prefer b's 5GHz
577                bscore = bstatus.rssi5 + bRssiBoost5 + bRssiBoost;
578            } else {
579                //prefer b's 2.4GHz
580                bscore = bstatus.rssi24 + bRssiBoost;
581            }
582            if (ascore > bscore) {
583                //a is seen on 5GHz with good RSSI, greater rssi than b
584                //a is of higher priority - descending
585                order = -10;
586                if (VDBG) {
587                    logDbg("compareWifiConfigurations linked and prefers " + a.configKey()
588                            + " rssi=(" + a.visibility.rssi24
589                            + "," + a.visibility.rssi5
590                            + ") num=(" + a.visibility.num24
591                            + "," + a.visibility.num5 + ")"
592                            + " over " + b.configKey()
593                            + " rssi=(" + b.visibility.rssi24
594                            + "," + b.visibility.rssi5
595                            + ") num=(" + b.visibility.num24
596                            + "," + b.visibility.num5 + ")"
597                            + " due to RSSI");
598                }
599            } else if (bscore > ascore) {
600                //b is seen on 5GHz with good RSSI, greater rssi than a
601                //a is of lower priority - ascending
602                order = 10;
603                if (VDBG) {
604                    logDbg("compareWifiConfigurations linked and prefers " + b.configKey()
605                            + " rssi=(" + b.visibility.rssi24
606                            + "," + b.visibility.rssi5
607                            + ") num=(" + b.visibility.num24
608                            + "," + b.visibility.num5 + ")"
609                            + " over " + a.configKey()
610                            + " rssi=(" + a.visibility.rssi24
611                            + "," + a.visibility.rssi5
612                            + ") num=(" + a.visibility.num24
613                            + "," + a.visibility.num5 + ")"
614                            + " due to RSSI");
615                }
616            }
617        }
618
619        //compare by user's choice.
620        if (hasConnectChoice(a, b)) {
621            //a is of higher priority - descending
622            order = order -2;
623            if (VDBG)   {
624                logDbg("compareWifiConfigurations prefers -2 " + a.configKey()
625                        + " over " + b.configKey()
626                        + " due to user choice order -> " + Integer.toString(order));
627            }
628        }
629
630        if (hasConnectChoice(b, a)) {
631            //a is of lower priority - ascending
632            order = order + 2;
633            if (VDBG)   {
634                logDbg("compareWifiConfigurations prefers +2 " + b.configKey() + " over "
635                        + a.configKey() + " due to user choice order ->" + Integer.toString(order));
636            }
637        }
638
639        //TODO count the number of association rejection
640        // and use this to adjust the order by more than +/- 3
641        if ((a.status == WifiConfiguration.Status.DISABLED)
642                && (a.disableReason == WifiConfiguration.DISABLED_ASSOCIATION_REJECT)) {
643            //a is of lower priority - ascending
644            //lower the comparison score a bit
645            order = order +3;
646        }
647        if ((b.status == WifiConfiguration.Status.DISABLED)
648                && (b.disableReason == WifiConfiguration.DISABLED_ASSOCIATION_REJECT)) {
649            //a is of higher priority - descending
650            //lower the comparison score a bit
651            order = order -3;
652        }
653
654        if ((lastSelectedConfiguration != null)
655                && a.configKey().equals(lastSelectedConfiguration)) {
656            // a is the last selected configuration, so keep it above connect choices (+/-2) and
657            // above RSSI based selection of linked configuration (+/- 11)
658            // by giving a -11
659            // Additional other factors like BAD RSSI (still to do) and
660            // ASSOC_REJECTION high counts will then still
661            // tip the auto-join to roam
662            order = order - 11;
663            if (VDBG)   {
664                logDbg("compareWifiConfigurations prefers -11 " + a.configKey()
665                        + " over " + b.configKey() + " because a is the last selected -> "
666                        + Integer.toString(order));
667            }
668        } else if ((lastSelectedConfiguration != null)
669                && b.configKey().equals(lastSelectedConfiguration)) {
670            // b is the last selected configuration, so keep it above connect choices (+/-2) and
671            // above RSSI based selection of linked configuration (+/- 11)
672            // by giving a +11
673            // Additional other factors like BAD RSSI (still to do) and
674            // ASSOC_REJECTION high counts will then still
675            // tip the auto-join to roam
676            order = order + 11;
677            if (VDBG)   {
678                logDbg("compareWifiConfigurations prefers +11 " + a.configKey()
679                        + " over " + b.configKey() + " because b is the last selected -> "
680                        + Integer.toString(order));
681            }
682        }
683
684        if (order == 0) {
685            //we don't know anything - pick the last seen i.e. K behavior
686            //we should do this only for recently picked configurations
687            if (a.priority > b.priority) {
688                //a is of higher priority - descending
689                if (VDBG)   {
690                    logDbg("compareWifiConfigurations prefers -1 " + a.configKey() + " over "
691                            + b.configKey() + " due to priority");
692                }
693
694                order = -1;
695            } else if (a.priority < b.priority) {
696                //a is of lower priority - ascending
697                if (VDBG)  {
698                    logDbg("compareWifiConfigurations prefers +1 " + b.configKey() + " over "
699                            + a.configKey() + " due to priority");
700                }
701
702              order = 1;
703            } else {
704                //maybe just look at RSSI or band
705                if (VDBG)  {
706                    logDbg("compareWifiConfigurations prefers +1 " + b.configKey() + " over "
707                            + a.configKey() + " due to nothing");
708                }
709
710                order = compareWifiConfigurationsRSSI(a, b); //compare RSSI
711            }
712        }
713
714        String sorder = " == ";
715        if (order > 0)
716            sorder = " < ";
717        if (order < 0)
718            sorder = " > ";
719
720        if (VDBG)   {
721            logDbg("compareWifiConfigurations Done: " + a.configKey() + sorder
722                    + b.configKey() + " order " + Integer.toString(order));
723        }
724
725        return order;
726    }
727
728    /* attemptRoam function implement the core of the same SSID switching algorithm */
729    ScanResult attemptRoam(WifiConfiguration current, int age) {
730        ScanResult a = null;
731        if (current == null) {
732            if (VDBG)   {
733                logDbg("attemptRoam not associated");
734            }
735            return null;
736        }
737        if (current.scanResultCache == null) {
738            if (VDBG)   {
739                logDbg("attemptRoam no scan cache");
740            }
741            return null;
742        }
743        if (current.scanResultCache.size() > 6) {
744            if (VDBG)   {
745                logDbg("attemptRoam scan cache size "
746                        + current.scanResultCache.size() + " --> bail");
747            }
748            //implement same SSID roaming only for configurations
749            // that have less than 4 BSSIDs
750            return null;
751        }
752        String currentBSSID = mWifiStateMachine.getCurrentBSSID();
753        if (currentBSSID == null) {
754            if (DBG)   {
755                logDbg("attemptRoam currentBSSID unknown");
756            }
757            return null;
758        }
759
760        if (current.bssidOwnerUid!= 0 && current.bssidOwnerUid != Process.WIFI_UID) {
761            if (DBG)   {
762                logDbg("attemptRoam BSSID owner is "
763                        + Long.toString(current.bssidOwnerUid) + " -> bail");
764            }
765            return null;
766        }
767
768        //determine which BSSID we want to associate to, taking account
769        // relative strength of 5 and 2.4 GHz BSSIDs
770        long now_ms = System.currentTimeMillis();
771        int bRssiBoost5 = 0;
772        int aRssiBoost5 = 0;
773        int bRssiBoost = 0;
774        int aRssiBoost = 0;
775        for (ScanResult b : current.scanResultCache.values()) {
776
777            if ((b.seen == 0) || (b.BSSID == null)) {
778                continue;
779            }
780
781            if (b.status != ScanResult.ENABLED) {
782                continue;
783            }
784
785            if ((now_ms - b.seen) > age) continue;
786
787            //pick first one
788            if (a == null) {
789                a = b;
790                continue;
791            }
792
793            if (currentBSSID.equals(b.BSSID)) {
794                //reduce the benefit of hysteresis if RSSI <= -75
795                if (b.level <= WifiConfiguration.G_BAND_PREFERENCE_RSSI_THRESHOLD) {
796                    bRssiBoost = +6;
797                } else {
798                    bRssiBoost = +10;
799                }
800            }
801            if (currentBSSID.equals(a.BSSID)) {
802                if (a.level <= WifiConfiguration.G_BAND_PREFERENCE_RSSI_THRESHOLD) {
803                    //reduce the benefit of hysteresis if RSSI <= -75
804                    aRssiBoost = +6;
805                } else {
806                    aRssiBoost = +10;
807                }
808            }
809            if (b.is5GHz() && (b.level+bRssiBoost)
810                    > WifiConfiguration.A_BAND_PREFERENCE_RSSI_THRESHOLD) {
811                bRssiBoost5 = 25;
812            } else if (b.is5GHz() && (b.level+bRssiBoost)
813                    < WifiConfiguration.G_BAND_PREFERENCE_RSSI_THRESHOLD) {
814                bRssiBoost5 = -10;
815            }
816            if (a.is5GHz() && (a.level+aRssiBoost)
817                    > WifiConfiguration.A_BAND_PREFERENCE_RSSI_THRESHOLD) {
818                aRssiBoost5 = 25;
819            } else if (a.is5GHz() && (a.level+aRssiBoost)
820                    < WifiConfiguration.G_BAND_PREFERENCE_RSSI_THRESHOLD) {
821                aRssiBoost5 = -10;
822            }
823
824            if (VDBG)  {
825                String comp = " < ";
826                if (b.level + bRssiBoost + bRssiBoost5 > a.level +aRssiBoost + aRssiBoost5) {
827                    comp = " > ";
828                }
829                logDbg("attemptRoam: "
830                        + b.BSSID + " rssi=" + b.level + " boost=" + Integer.toString(bRssiBoost)
831                        + "/" + Integer.toString(bRssiBoost5) + " freq=" + b.frequency + comp
832                        + a.BSSID + " rssi=" + a.level + " boost=" + Integer.toString(aRssiBoost)
833                        + "/" + Integer.toString(aRssiBoost5) + " freq=" + a.frequency);
834            }
835
836            if (b.level + bRssiBoost + bRssiBoost5 > a.level +aRssiBoost + aRssiBoost5) {
837                //b is the better BSSID
838                a = b;
839            }
840        }
841        if (a != null) {
842            if (VDBG)  {
843                logDbg("attemptRoam: Found "
844                        + a.BSSID + " rssi=" + a.level + " freq=" + a.frequency
845                        + " Current: " + currentBSSID);
846            }
847            if (currentBSSID.equals(a.BSSID)) {
848                return null;
849            }
850        }
851        return a;
852    }
853
854    /* attemptAutoJoin function implement the core of the a network switching algorithm */
855    void attemptAutoJoin() {
856        int networkSwitchType = AUTO_JOIN_IDLE;
857
858        String lastSelectedConfiguration = mWifiConfigStore.getLastSelectedConfiguration();
859
860        // reset the currentConfiguration Key, and set it only if WifiStateMachine and
861        // supplicant agree
862        mCurrentConfigurationKey = null;
863        WifiConfiguration currentConfiguration = mWifiStateMachine.getCurrentWifiConfiguration();
864
865        WifiConfiguration candidate = null;
866
867        /* obtain the subset of recently seen networks */
868        List<WifiConfiguration> list = mWifiConfigStore.getRecentConfiguredNetworks(3000, false);
869        if (list == null) {
870            if (VDBG)  logDbg("attemptAutoJoin nothing");
871            return;
872        }
873
874        /* find the currently connected network: ask the supplicant directly */
875        String val = mWifiNative.status();
876        String status[] = val.split("\\r?\\n");
877        if (VDBG) {
878            logDbg("attemptAutoJoin() status=" + val + " split="
879                    + Integer.toString(status.length));
880        }
881
882        int supplicantNetId = -1;
883        for (String key : status) {
884            if (key.regionMatches(0, "id=", 0, 3)) {
885                int idx = 3;
886                supplicantNetId = 0;
887                while (idx < key.length()) {
888                    char c = key.charAt(idx);
889
890                    if ((c >= 0x30) && (c <= 0x39)) {
891                        supplicantNetId *= 10;
892                        supplicantNetId += c - 0x30;
893                        idx++;
894                    } else {
895                        break;
896                    }
897                }
898            }
899        }
900        if (DBG) {
901            logDbg("attemptAutoJoin() num recent config " + Integer.toString(list.size())
902                    + " ---> suppId=" + Integer.toString(supplicantNetId));
903        }
904
905        if (currentConfiguration != null) {
906            if (supplicantNetId != currentConfiguration.networkId) {
907                logDbg("attemptAutoJoin() ERROR wpa_supplicant out of sync nid="
908                        + Integer.toString(supplicantNetId) + " WifiStateMachine="
909                        + Integer.toString(currentConfiguration.networkId));
910                mWifiStateMachine.disconnectCommand();
911                return;
912            } else {
913                mCurrentConfigurationKey = currentConfiguration.configKey();
914            }
915        }
916
917        int currentNetId = -1;
918        if (currentConfiguration != null) {
919            // if we are associated to a configuration, it will
920            // be compared thru the compareNetwork function
921            currentNetId = currentConfiguration.networkId;
922        }
923
924        /* run thru all visible configurations without looking at the one we
925         * are currently associated to
926         * select Best Network candidate from known WifiConfigurations
927         * */
928        for (WifiConfiguration config : list) {
929            if ((config.status == WifiConfiguration.Status.DISABLED)
930                    && (config.disableReason == WifiConfiguration.DISABLED_AUTH_FAILURE)) {
931                if (DBG) {
932                    logDbg("attemptAutoJoin skip candidate due to auth failure: "
933                            + config.configKey(true));
934                }
935                continue;
936            }
937
938            if (config.SSID == null) {
939                continue;
940            }
941
942            if (config.autoJoinStatus >=
943                    WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE) {
944                //avoid temporarily disabled networks altogether
945                //TODO: implement a better logic which will re-enable the network after some time
946                if (DBG) {
947                    logDbg("attemptAutoJoin skip candidate due to auto join status "
948                            + Integer.toString(config.autoJoinStatus) + " key "
949                            + config.configKey(true));
950                }
951                continue;
952            }
953
954            //try to unblacklist based on elapsed time
955            if (config.blackListTimestamp > 0) {
956                long now = System.currentTimeMillis();
957                if (now < config.blackListTimestamp) {
958                    //looks like there was a change in the system clock since we black listed, and
959                    //timestamp is not meaningful anymore, hence lose it.
960                    //this event should be rare enough so that we still want to lose the black list
961                    config.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED);
962                } else {
963                    if ((now - config.blackListTimestamp) > loseBlackListHardMilli) {
964                        //reenable it after 18 hours, i.e. next day
965                        config.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED);
966                    } else if ((now - config.blackListTimestamp) > loseBlackListSoftMilli) {
967                        //lose blacklisting due to bad link
968                        config.setAutoJoinStatus(config.autoJoinStatus - 8);
969                    }
970                }
971            }
972
973            //try to unblacklist based on good visibility
974            if (config.visibility.rssi5 < WifiConfiguration.UNBLACKLIST_THRESHOLD_5_SOFT
975                    && config.visibility.rssi24 < WifiConfiguration.UNBLACKLIST_THRESHOLD_24_SOFT) {
976                if (DBG) {
977                    logDbg("attemptAutoJoin skip candidate due to auto join status "
978                            + config.autoJoinStatus
979                            + " key " + config.configKey(true)
980                            + " rssi=(" + config.visibility.rssi24
981                            + "," + config.visibility.rssi5
982                            + ") num=(" + config.visibility.num24
983                            + "," + config.visibility.num5 + ")");
984                }
985            } else if (config.visibility.rssi5 < WifiConfiguration.UNBLACKLIST_THRESHOLD_5_HARD
986                    && config.visibility.rssi24 < WifiConfiguration.UNBLACKLIST_THRESHOLD_24_HARD) {
987                // if the network is simply temporary disabled, don't allow reconnect until
988                // rssi becomes good enough
989                config.setAutoJoinStatus(config.autoJoinStatus - 1);
990                if (DBG) {
991                    logDbg("attemptAutoJoin good candidate seen, bumped soft -> status="
992                            + config.autoJoinStatus
993                            + " key " + config.configKey(true) + " rssi=("
994                            + config.visibility.rssi24 + "," + config.visibility.rssi5
995                            + ") num=(" + config.visibility.num24
996                            + "," + config.visibility.num5 + ")");
997                }
998            } else {
999                config.setAutoJoinStatus(config.autoJoinStatus - 3);
1000                if (DBG) {
1001                    logDbg("attemptAutoJoin good candidate seen, bumped hard -> status="
1002                            + config.autoJoinStatus
1003                            + " key " + config.configKey(true) + " rssi=("
1004                            + config.visibility.rssi24 + "," + config.visibility.rssi5
1005                            + ") num=(" + config.visibility.num24
1006                            + "," + config.visibility.num5 + ")");
1007                }
1008            }
1009
1010            if (config.autoJoinStatus >=
1011                    WifiConfiguration.AUTO_JOIN_TEMPORARY_DISABLED) {
1012                //network is blacklisted, skip
1013                if (DBG) {
1014                    logDbg("attemptAutoJoin skip blacklisted -> status="
1015                            + config.autoJoinStatus
1016                            + " key " + config.configKey(true) + " rssi=("
1017                            + config.visibility.rssi24 + "," + config.visibility.rssi5
1018                            + ") num=(" + config.visibility.num24
1019                            + "," + config.visibility.num5 + ")");
1020                }
1021                continue;
1022            }
1023            if (config.networkId == currentNetId) {
1024                if (DBG) {
1025                    logDbg("attemptAutoJoin skip current candidate  "
1026                            + Integer.toString(currentNetId)
1027                            + " key " + config.configKey(true));
1028                }
1029                continue;
1030            }
1031
1032            if (lastSelectedConfiguration == null ||
1033                    !config.configKey().equals(lastSelectedConfiguration)) {
1034                //don't try to autojoin a network that is too far
1035                if (config.visibility == null) {
1036                    continue;
1037                }
1038                if (config.visibility.rssi5 < WifiConfiguration.INITIAL_AUTO_JOIN_ATTEMPT_MIN_5
1039                        && config.visibility.rssi24
1040                        < WifiConfiguration.INITIAL_AUTO_JOIN_ATTEMPT_MIN_24) {
1041                    if (DBG) {
1042                        logDbg("attemptAutoJoin gskip due to low visibility -> status="
1043                                + config.autoJoinStatus
1044                                + " key " + config.configKey(true) + " rssi="
1045                                + config.visibility.rssi24 + ", " + config.visibility.rssi5
1046                                + " num=" + config.visibility.num24
1047                                + ", " + config.visibility.num5);
1048                    }
1049                    continue;
1050                }
1051            }
1052
1053            if (DBG) {
1054                logDbg("attemptAutoJoin trying candidate id="
1055                        + Integer.toString(config.networkId) + " "
1056                        + config.SSID + " key " + config.configKey(true)
1057                        + " status=" + config.autoJoinStatus);
1058            }
1059
1060            if (candidate == null) {
1061                candidate = config;
1062            } else {
1063                if (VDBG)  {
1064                    logDbg("attemptAutoJoin will compare candidate  " + candidate.configKey()
1065                            + " with " + config.configKey());
1066                }
1067
1068                int order = compareWifiConfigurations(candidate, config);
1069                if (order > 0) {
1070                    //ascending : candidate < config
1071                    candidate = config;
1072                }
1073            }
1074        }
1075
1076        /* now, go thru scan result to try finding a better Herrevad network */
1077        if (mNetworkScoreCache != null) {
1078            int rssi5 = WifiConfiguration.INVALID_RSSI;
1079            int rssi24 = WifiConfiguration.INVALID_RSSI;
1080            WifiConfiguration.Visibility visibility;
1081            if (candidate != null) {
1082                rssi5 = candidate.visibility.rssi5;
1083                rssi24 = candidate.visibility.rssi24;
1084            }
1085
1086            //get current date
1087            long now_ms = System.currentTimeMillis();
1088
1089            if (rssi5 < -60 && rssi24 < -70) {
1090                for (ScanResult result : scanResultCache.values()) {
1091                    if ((now_ms - result.seen) < 3000) {
1092                        int score = mNetworkScoreCache.getNetworkScore(result);
1093                        if (score > 0) {
1094                            // try any arbitrary formula for now, adding apple and oranges,
1095                            // i.e. adding network score and "dBm over noise"
1096                           if (result.is24GHz()) {
1097                                if ((result.level + score) > (rssi24 -40)) {
1098                                    // force it as open, TBD should we otherwise verify that this
1099                                    // BSSID only supports open??
1100                                    result.capabilities = "";
1101
1102                                    //switch to this scan result
1103                                    candidate =
1104                                          mWifiConfigStore.wifiConfigurationFromScanResult(result);
1105                                    candidate.ephemeral = true;
1106                                }
1107                           } else {
1108                                if ((result.level + score) > (rssi5 -30)) {
1109                                    // force it as open, TBD should we otherwise verify that this
1110                                    // BSSID only supports open??
1111                                    result.capabilities = "";
1112
1113                                    //switch to this scan result
1114                                    candidate =
1115                                          mWifiConfigStore.wifiConfigurationFromScanResult(result);
1116                                    candidate.ephemeral = true;
1117                                }
1118                           }
1119                        }
1120                    }
1121                }
1122            }
1123        }
1124
1125        /* if candidate is found, check the state of the connection so as
1126            to decide if we should be acting on this candidate and switching over */
1127        int networkDelta = compareNetwork(candidate);
1128        if (DBG && candidate != null) {
1129            logDbg("attemptAutoJoin compare SSID candidate : delta="
1130                    + Integer.toString(networkDelta) + " "
1131                    + candidate.configKey()
1132                    + " linked=" + (currentConfiguration != null
1133                    && currentConfiguration.isLinked(candidate)));
1134        }
1135
1136        /* ASK WifiStateMachine permission to switch:
1137            for instance,
1138            if user is currently streaming voice traffic,
1139            then don’t switch regardless of the delta
1140            */
1141        if (mWifiStateMachine.shouldSwitchNetwork(networkDelta)) {
1142            if (mStaStaSupported) {
1143                logDbg("mStaStaSupported --> error do nothing now ");
1144            } else {
1145                if (currentConfiguration != null && currentConfiguration.isLinked(candidate)) {
1146                    networkSwitchType = AUTO_JOIN_EXTENDED_ROAMING;
1147                } else {
1148                    networkSwitchType = AUTO_JOIN_OUT_OF_NETWORK_ROAMING;
1149                }
1150                if (DBG) {
1151                    logDbg("AutoJoin auto connect with netId "
1152                            + Integer.toString(candidate.networkId)
1153                            + " to " + candidate.configKey());
1154                }
1155                mWifiStateMachine.sendMessage(WifiStateMachine.CMD_AUTO_CONNECT,
1156                        candidate.networkId, networkSwitchType, candidate);
1157            }
1158        }
1159
1160        if (networkSwitchType == AUTO_JOIN_IDLE) {
1161            //attempt same WifiConfiguration roaming
1162            ScanResult roamCandidate = attemptRoam(currentConfiguration, 3000);
1163            if (roamCandidate != null) {
1164                if (DBG) {
1165                    logDbg("AutoJoin auto roam with netId "
1166                            + Integer.toString(currentConfiguration.networkId)
1167                            + " " + currentConfiguration.configKey() + " to BSSID="
1168                            + roamCandidate.BSSID + " freq=" + roamCandidate.frequency
1169                            + " RSSI=" + roamCandidate.frequency);
1170                }
1171                networkSwitchType = AUTO_JOIN_ROAMING;
1172                mWifiStateMachine.sendMessage(WifiStateMachine.CMD_AUTO_ROAM,
1173                            currentConfiguration.networkId, 1, roamCandidate);
1174            }
1175        }
1176        if (VDBG) logDbg("Done attemptAutoJoin status=" + Integer.toString(networkSwitchType));
1177    }
1178}
1179
1180