WifiAutoJoinController.java revision a6b7a402041bd4bc0749331b92c3c1e5225927a0
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.util.Log;
30
31import java.util.ArrayList;
32import java.util.Collection;
33import java.util.Iterator;
34import java.util.HashMap;
35import java.util.List;
36import java.util.Date;
37
38/**
39 * AutoJoin controller is responsible for WiFi Connect decision
40 *
41 * It runs in the thread context of WifiStateMachine
42 *
43 */
44public class WifiAutoJoinController {
45
46    private Context mContext;
47    private WifiStateMachine mWifiStateMachine;
48    private WifiConfigStore mWifiConfigStore;
49    private WifiTrafficPoller mWifiTrafficPoller;
50    private WifiNative mWifiNative;
51
52    private NetworkScoreManager scoreManager;
53    private WifiNetworkScoreCache mNetworkScoreCache;
54
55    private static final String TAG = "WifiAutoJoinController ";
56    private static boolean DBG = false;
57    private static boolean VDBG = false;
58    private static final boolean mStaStaSupported = false;
59    private static final int SCAN_RESULT_CACHE_SIZE = 80;
60
61    private String mCurrentConfigurationKey = null; //used by autojoin
62
63    private HashMap<String, ScanResult> scanResultCache =
64            new HashMap<String, ScanResult>();
65
66    WifiAutoJoinController(Context c, WifiStateMachine w, WifiConfigStore s,
67                           WifiTrafficPoller t, WifiNative n) {
68        mContext = c;
69        mWifiStateMachine = w;
70        mWifiConfigStore = s;
71        mWifiTrafficPoller = t;
72        mWifiNative = n;
73        mNetworkScoreCache = null;
74        scoreManager =
75                (NetworkScoreManager) mContext.getSystemService(Context.NETWORK_SCORE_SERVICE);
76        if (scoreManager == null)
77            logDbg("Registered scoreManager NULL " + " service " + Context.NETWORK_SCORE_SERVICE);
78
79        if (scoreManager != null) {
80            mNetworkScoreCache = new WifiNetworkScoreCache(mContext);
81            scoreManager.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
82        } else {
83            logDbg("No network score service: Couldnt register as a WiFi score Manager, type="
84                    + Integer.toString(NetworkKey.TYPE_WIFI)
85                    + " service " + Context.NETWORK_SCORE_SERVICE);
86            mNetworkScoreCache = null;
87        }
88    }
89
90    void enableVerboseLogging(int verbose) {
91        if (verbose > 0 ) {
92            DBG = true;
93            VDBG = true;
94        } else {
95            DBG = false;
96            VDBG = false;
97        }
98    }
99
100    int mScanResultMaximumAge = 30000; /* milliseconds unit */
101
102    /*
103     * flush out scan results older than mScanResultMaximumAge
104     *
105     * */
106    private void ageScanResultsOut(int delay) {
107        if (delay <= 0) {
108            delay = mScanResultMaximumAge; //something sane
109        }
110        Date now = new Date();
111        long milli = now.getTime();
112        if (VDBG) {
113            logDbg("ageScanResultsOut delay " + Integer.valueOf(delay) + " size "
114                    + Integer.valueOf(scanResultCache.size()) + " now " + Long.valueOf(milli));
115        }
116
117        Iterator<HashMap.Entry<String,ScanResult>> iter = scanResultCache.entrySet().iterator();
118        while (iter.hasNext()) {
119            HashMap.Entry<String,ScanResult> entry = iter.next();
120            ScanResult result = entry.getValue();
121
122            if ((result.seen + delay) < milli) {
123                iter.remove();
124            }
125        }
126    }
127
128    void addToScanCache(List<ScanResult> scanList) {
129        WifiConfiguration associatedConfig;
130
131        ArrayList<NetworkKey> unknownScanResults = new ArrayList<NetworkKey>();
132
133        for(ScanResult result: scanList) {
134            if (result.SSID == null) continue;
135            result.seen = System.currentTimeMillis();
136
137            ScanResult sr = scanResultCache.get(result.BSSID);
138            if (sr != null) {
139                // if there was a previous cache result for this BSSID, average the RSSI values
140
141                int previous_rssi = sr.level;
142                long previously_seen_milli = sr.seen;
143
144                /* average RSSI with previously seen instances of this scan result */
145                int avg_rssi = result.level;
146
147                if ((previously_seen_milli > 0)
148                        && (previously_seen_milli < mScanResultMaximumAge/2)) {
149                    /*
150                    *
151                    * previously_seen_milli = 0 => RSSI = 0.5 * previous_seen_rssi + 0.5 * new_rssi
152                    *
153                    * If previously_seen_milli is 15+ seconds old:
154                    *      previously_seen_milli = 15000 => RSSI = new_rssi
155                    *
156                    */
157
158                    double alpha = 0.5 - (double)previously_seen_milli
159                            / (double)mScanResultMaximumAge;
160
161                    avg_rssi = (int)((double)avg_rssi * (1-alpha) + (double)previous_rssi * alpha);
162                }
163                result.level = avg_rssi;
164
165                //remove the previous Scan Result
166                scanResultCache.remove(result.BSSID);
167            } else {
168                if (!mNetworkScoreCache.isScoredNetwork(result)) {
169                    WifiKey wkey;
170                    //TODO : find out how we can get there without a valid UTF-8 encoded SSID
171                    //TODO: which will cause WifiKey constructor to fail
172                    try {
173                        wkey = new WifiKey("\"" + result.SSID + "\"", result.BSSID);
174                    } catch (IllegalArgumentException e) {
175                        logDbg("AutoJoinController: received badly encoded SSID=[" + result.SSID +
176                                "] ->skipping this network");
177                        wkey = null;
178                    }
179                    if (wkey != null) {
180                        NetworkKey nkey = new NetworkKey(wkey);
181                        //if we don't know this scan result then request a score to Herrevad
182                        unknownScanResults.add(nkey);
183                    }
184                }
185            }
186
187            scanResultCache.put(result.BSSID, new ScanResult(result));
188
189            ScanResult srn = scanResultCache.get(result.BSSID);
190
191            //add this BSSID to the scanResultCache of the relevant WifiConfiguration
192            associatedConfig = mWifiConfigStore.updateSavedNetworkHistory(result);
193
194            //try to associate this BSSID to an existing Saved WifiConfiguration
195            if (associatedConfig == null) {
196                associatedConfig = mWifiConfigStore.associateWithConfiguration(result);
197                if (associatedConfig != null && associatedConfig.SSID != null) {
198                    if (VDBG) {
199                        logDbg("addToScanCache save associated config "
200                                + associatedConfig.SSID + " with " + associatedConfig.SSID);
201                    }
202                    mWifiStateMachine.sendMessage(WifiManager.SAVE_NETWORK, associatedConfig);
203                }
204            }
205        }
206
207        if (unknownScanResults.size() != 0) {
208            NetworkKey[] newKeys =
209                    unknownScanResults.toArray(new NetworkKey[unknownScanResults.size()]);
210                //kick the score manager, we will get updated scores asynchronously
211            scoreManager.requestScores(newKeys);
212        }
213    }
214
215    void logDbg(String message) {
216        logDbg(message, false);
217    }
218
219    void logDbg(String message, boolean stackTrace) {
220        long now = SystemClock.elapsedRealtimeNanos();
221        String ts = String.format("[%,d us] ", now / 1000);
222        if (stackTrace) {
223            Log.e(TAG, ts + message + " stack:"
224                    + Thread.currentThread().getStackTrace()[2].getMethodName() + " - "
225                    + Thread.currentThread().getStackTrace()[3].getMethodName() + " - "
226                    + Thread.currentThread().getStackTrace()[4].getMethodName() + " - "
227                    + Thread.currentThread().getStackTrace()[5].getMethodName());
228        } else {
229            Log.e(TAG, ts + message);
230        }
231    }
232
233    /* called directly from WifiStateMachine  */
234    void newSupplicantResults() {
235        List<ScanResult> scanList = mWifiStateMachine.syncGetScanResultsList();
236        addToScanCache(scanList);
237        ageScanResultsOut(mScanResultMaximumAge);
238        if (DBG)
239           logDbg("newSupplicantResults size=" + Integer.valueOf(scanResultCache.size()) );
240
241        attemptAutoJoin();
242        mWifiConfigStore.writeKnownNetworkHistory();
243
244    }
245
246
247    /* not used at the moment
248     * should be a call back from WifiScanner HAL ??
249     * this function is not hooked and working yet, it will receive scan results from WifiScanners
250     * with the list of IEs,then populate the capabilities by parsing the IEs and inject the scan
251     * results as normal.
252     */
253    void newHalScanResults() {
254        List<ScanResult> scanList = null;//mWifiScanner.syncGetScanResultsList();
255        String akm = WifiParser.parse_akm(null, null);
256        logDbg(akm);
257        addToScanCache(scanList);
258        ageScanResultsOut(0);
259        attemptAutoJoin();
260        mWifiConfigStore.writeKnownNetworkHistory();
261    }
262
263    /* network link quality changed, called directly from WifiTrafficPoller,
264    or by listening to Link Quality intent */
265    void linkQualitySignificantChange() {
266        attemptAutoJoin();
267    }
268
269    /*
270     * compare a WifiConfiguration against the current network, return a delta score
271     * If not associated, and the candidate will always be better
272     * For instance if the candidate is a home network versus an unknown public wifi,
273     * the delta will be infinite, else compare Kepler scores etc…
274     ***/
275    private int compareNetwork(WifiConfiguration candidate) {
276        WifiConfiguration currentNetwork = mWifiStateMachine.getCurrentWifiConfiguration();
277        if (currentNetwork == null)
278            return 1000;
279
280        if (candidate.configKey(true).equals(currentNetwork.configKey(true))) {
281            return -1;
282        }
283
284        int order = compareWifiConfigurations(currentNetwork, candidate);
285
286        if (order > 0) {
287            //ascending: currentNetwork < candidate
288            return 10; //will try switch over to the candidate
289        }
290
291        return 0;
292    }
293
294    /**
295     * update the network history fields fo that configuration
296     * - if userTriggered, we mark the configuration as "non selfAdded" since the user has seen it
297     * and took over management
298     * - if it is a "connect", remember which network were there at the point of the connect, so
299     * as those networks get a relative lower score than the selected configuration
300     *
301     * @param netId
302     * @param userTriggered : if the update come from WiFiManager
303     * @param connect : if the update includes a connect
304     **/
305    public void updateConfigurationHistory(int netId, boolean userTriggered, boolean connect) {
306        WifiConfiguration selected = mWifiConfigStore.getWifiConfiguration(netId);
307        if (selected == null) {
308            return;
309        }
310
311        if (selected.SSID == null) {
312            return;
313        }
314
315        if (userTriggered) {
316            // reenable autojoin for this network,
317            // since the user want to connect to this configuration
318            selected.autoJoinStatus = WifiConfiguration.AUTO_JOIN_ENABLED;
319            selected.selfAdded = false;
320        }
321
322        if (DBG && userTriggered) {
323            if (selected.connectChoices != null) {
324                logDbg("updateConfigurationHistory will update "
325                        + Integer.toString(netId) + " now: "
326                        + Integer.toString(selected.connectChoices.size())
327                        + " uid=" + Integer.toString(selected.creatorUid), true);
328            } else {
329                logDbg("updateConfigurationHistory will update "
330                        + Integer.toString(netId)
331                        + " uid=" + Integer.toString(selected.creatorUid), true);
332            }
333        }
334
335        if (connect && userTriggered) {
336            boolean found = false;
337            List<WifiConfiguration> networks =
338                    mWifiConfigStore.getRecentConfiguredNetworks(12000, false);
339            if (networks != null) {
340                for (WifiConfiguration config : networks) {
341                    if (DBG) {
342                        logDbg("updateConfigurationHistory got " + config.SSID + " nid="
343                                + Integer.toString(config.networkId));
344                    }
345
346                    if (selected.configKey(true).equals(config.configKey(true))) {
347                        found = true;
348                        continue;
349                    }
350
351                    int rssi = WifiConfiguration.INVALID_RSSI;
352                    if (config.visibility != null) {
353                        rssi = config.visibility.rssi5;
354                        if (config.visibility.rssi24 > rssi)
355                            rssi = config.visibility.rssi24;
356                    }
357                    if (rssi < -80) {
358                        continue;
359                    }
360
361                    //the selected configuration was preferred over a recently seen config
362                    //hence remember the user's choice:
363                    //add the recently seen config to the selected's connectChoices array
364
365                    if (selected.connectChoices == null) {
366                        selected.connectChoices = new HashMap<String, Integer>();
367                    }
368
369                    logDbg("updateConfigurationHistory add a choice " + selected.configKey(true)
370                            + " over " + config.configKey(true)
371                            + " RSSI " + Integer.toString(rssi));
372                    //add the visible config to the selected's connect choice list
373                    selected.connectChoices.put(config.configKey(true), rssi);
374
375                    if (config.connectChoices != null) {
376                        if (VDBG) {
377                            logDbg("updateConfigurationHistory will remove "
378                                    + selected.configKey(true) + " from " + config.configKey(true));
379                        }
380                        //remove the selected from the recently seen config's connectChoice list
381                        config.connectChoices.remove(selected.configKey(true));
382
383                        if (selected.linkedConfigurations != null) {
384                           //remove the selected's linked configuration from the
385                           //recently seen config's connectChoice list
386                           for (String key : selected.linkedConfigurations.keySet()) {
387                               config.connectChoices.remove(key);
388                           }
389                        }
390                    }
391                }
392                if (found == false) {
393                     // log an error for now but do something stringer later
394                     // we will need a new scan before attempting to connect to this
395                     // configuration anyhow and thus we can process the scan results then
396                     logDbg("updateConfigurationHistory try to connect to an old network!! : "
397                             + selected.configKey());
398                }
399
400                if (selected.connectChoices != null) {
401                    if (VDBG)
402                        logDbg("updateConfigurationHistory " + Integer.toString(netId)
403                                + " now: " + Integer.toString(selected.connectChoices.size()));
404                }
405
406            }
407        }
408
409        //TODO: write only if something changed
410        if (userTriggered || connect) {
411            mWifiConfigStore.writeKnownNetworkHistory();
412        }
413    }
414
415    void printChoices(WifiConfiguration config) {
416        int num = 0;
417        if (config.connectChoices!= null) {
418            num = config.connectChoices.size();
419        }
420
421        logDbg("printChoices " + config.SSID + " num choices: " + Integer.toString(num));
422        if (config.connectChoices!= null) {
423            for (String key : config.connectChoices.keySet()) {
424                logDbg("                 " + key);
425            }
426        }
427    }
428
429    boolean hasConnectChoice(WifiConfiguration source, WifiConfiguration target) {
430        boolean found = false;
431        if (source == null)
432            return false;
433        if (target == null)
434            return false;
435
436        if (source.connectChoices != null) {
437            if ( source.connectChoices.get(target.configKey(true)) != null) {
438                found = true;
439            }
440        }
441
442        if (source.linkedConfigurations != null) {
443            for (String key : source.linkedConfigurations.keySet()) {
444                WifiConfiguration config = mWifiConfigStore.getWifiConfiguration(key);
445                if (config != null) {
446                    if (config.connectChoices != null) {
447                        if (config.connectChoices.get(target.configKey(true)) != null) {
448                            found = true;
449                        }
450                    }
451                }
452            }
453        }
454        return found;
455    }
456
457    int compareWifiConfigurationsRSSI(WifiConfiguration a, WifiConfiguration b) {
458        int order = 0;
459        int boost5 = 25;
460
461        WifiConfiguration.Visibility astatus = a.visibility;
462        WifiConfiguration.Visibility bstatus = b.visibility;
463        if (astatus == null || bstatus == null) {
464            //error -> cant happen, need to throw en exception
465            logDbg("compareWifiConfigurations NULL band status!");
466            return 0;
467        }
468        if ((astatus.rssi5 > -70) && (bstatus.rssi5 == WifiConfiguration.INVALID_RSSI)
469                && ((astatus.rssi5+boost5) > (bstatus.rssi24))) {
470            //a is seen on 5GHz with good RSSI, greater rssi than b
471            //a is of higher priority - descending
472            order = -1;
473        } else if ((bstatus.rssi5 > -70) && (astatus.rssi5 == WifiConfiguration.INVALID_RSSI)
474                && ((bstatus.rssi5+boost5) > (bstatus.rssi24))) {
475            //b is seen on 5GHz with good RSSI, greater rssi than a
476            //a is of lower priority - ascending
477            order = 1;
478        }
479        return order;
480    }
481
482    int compareWifiConfigurations(WifiConfiguration a, WifiConfiguration b) {
483        int order = 0;
484        String lastSelectedConfiguration = mWifiConfigStore.getLastSelectedConfiguration();
485        boolean linked = false;
486
487        if ((a.linkedConfigurations != null) && (b.linkedConfigurations != null)
488                && (a.autoJoinStatus == WifiConfiguration.AUTO_JOIN_ENABLED)
489                && (b.autoJoinStatus == WifiConfiguration.AUTO_JOIN_ENABLED)) {
490            if ((a.linkedConfigurations.get(b.configKey(true))!= null)
491                    && (b.linkedConfigurations.get(a.configKey(true))!= null)) {
492                linked = true;
493            }
494        }
495
496        if (a.ephemeral && b.ephemeral == false) {
497            if (VDBG) {
498                logDbg("compareWifiConfigurations ephemeral and prefers " + b.configKey()
499                        + " over " + a.configKey());
500            }
501            return 1; //b is of higher priority - ascending
502        }
503        if (b.ephemeral && a.ephemeral == false) {
504            if (VDBG) {
505                logDbg("compareWifiConfigurations ephemeral and prefers " +a.configKey()
506                        + " over " + b.configKey());
507            }
508            return -1; //a is of higher priority - descending
509        }
510
511        int boost5 = 25;
512        //apply Hysteresis: boost the RSSI value of the currently connected configuration
513        int aRssiBoost = 0;
514        int bRssiBoost = 0;
515        if (null != mCurrentConfigurationKey) {
516            if (a.configKey().equals(mCurrentConfigurationKey)) {
517                aRssiBoost += 10;
518            } else if (b.configKey().equals(mCurrentConfigurationKey)) {
519                bRssiBoost += 10;
520            }
521        }
522        if (linked) {
523            // then we try prefer 5GHz, and try to ignore user's choice
524            WifiConfiguration.Visibility astatus = a.visibility;
525            WifiConfiguration.Visibility bstatus = b.visibility;
526            if (astatus == null || bstatus == null) {
527                //error
528                logDbg("compareWifiConfigurations NULL band status!");
529                return 0;
530            }
531
532            if (VDBG)  {
533                logDbg("compareWifiConfigurations linked: " + Integer.toString(astatus.rssi5)
534                        + "," + Integer.toString(astatus.rssi24) + "   "
535                        + Integer.toString(bstatus.rssi5) + ","
536                        + Integer.toString(bstatus.rssi24));
537            }
538
539            if ((astatus.rssi5 > -70) && (bstatus.rssi5 <= WifiConfiguration.INVALID_RSSI)
540                    && (astatus.rssi5+boost5+aRssiBoost) > (bstatus.rssi24+bRssiBoost)) {
541                    //in this case: a has 5GHz and b doesn't have 5GHz
542                    //compare a's 5GHz RSSI to b's 5GHz RSSI
543
544                    //a is seen on 5GHz with good RSSI, greater rssi than b
545                    //a is of higher priority - descending
546                    order = -10;
547
548                if (VDBG) {
549                    logDbg("compareWifiConfigurations linked and prefers " + a.configKey()
550                            + " over " + b.configKey()
551                            + " due to 5GHz RSSI " + Integer.toString(astatus.rssi5)
552                            + " over: 5=" + Integer.toString(bstatus.rssi5)
553                            + ", 2.4=" + Integer.toString(bstatus.rssi5));
554                }
555            } else if ((bstatus.rssi5 > -70) && (astatus.rssi5 <= WifiConfiguration.INVALID_RSSI)
556                    && ((bstatus.rssi5+boost5+bRssiBoost) > (astatus.rssi24+aRssiBoost))) {
557                    //in this case: b has 5GHz and a doesn't have 5GHz
558
559                    //b is seen on 5GHz with good RSSI, greater rssi than a
560                    //a is of lower priority - ascending
561                if (VDBG)   {
562                    logDbg("compareWifiConfigurations linked and prefers " + b.configKey()
563                            + " over " + a.configKey() + " due to 5GHz RSSI "
564                            + Integer.toString(astatus.rssi5) + " over: 5="
565                            + Integer.toString(bstatus.rssi5) + ", 2.4="
566                            + Integer.toString(bstatus.rssi5));
567                }
568                order = 10;
569            } else {
570                //TODO: handle cases where configurations are dual band
571            }
572        }
573
574        //compare by user's choice.
575        if (hasConnectChoice(a, b)) {
576            //a is of higher priority - descending
577            order = order -2;
578            if (VDBG)   {
579                logDbg("compareWifiConfigurations prefers -2 " + a.configKey()
580                        + " over " + b.configKey()
581                        + " due to user choice order -> " + Integer.toString(order));
582            }
583        }
584
585        if (hasConnectChoice(b, a)) {
586            //a is of lower priority - ascending
587            order = order + 2;
588            if (VDBG)   {
589                logDbg("compareWifiConfigurations prefers +2 " + b.configKey() + " over "
590                        + a.configKey() + " due to user choice order ->" + Integer.toString(order));
591            }
592        }
593
594        //TODO count the number of association rejection
595        // and use this to adjust the order by more than +/- 3
596        if ((a.status == WifiConfiguration.Status.DISABLED)
597                && (a.disableReason == WifiConfiguration.DISABLED_ASSOCIATION_REJECT)) {
598            //a is of lower priority - ascending
599            //lower the comparison score a bit
600            order = order +3;
601        }
602        if ((b.status == WifiConfiguration.Status.DISABLED)
603                && (b.disableReason == WifiConfiguration.DISABLED_ASSOCIATION_REJECT)) {
604            //a is of higher priority - descending
605            //lower the comparison score a bit
606            order = order -3;
607        }
608
609        if ((lastSelectedConfiguration != null)
610                && a.configKey().equals(lastSelectedConfiguration)) {
611            // a is the last selected configuration, so keep it above connect choices
612            //by giving a -4 (whereas connect choice preference gives +2)
613            order = order - 4;
614            if (VDBG)   {
615                logDbg("compareWifiConfigurations prefers -4 " + a.configKey()
616                        + " over " + b.configKey() + " because a is the last selected -> "
617                        + Integer.toString(order));
618            }
619        } else if ((lastSelectedConfiguration != null)
620                && b.configKey().equals(lastSelectedConfiguration)) {
621            // b is the last selected configuration, so keep it above connect choices
622            //by giving a +4 (whereas connect choice preference gives -2)
623            order = order + 4;
624            if (VDBG)   {
625                logDbg("compareWifiConfigurations prefers +4 " + a.configKey()
626                        + " over " + b.configKey() + " because b is the last selected -> "
627                        + Integer.toString(order));
628            }
629        }
630
631        if (order == 0) {
632            //we don't know anything - pick the last seen i.e. K behavior
633            //we should do this only for recently picked configurations
634            if (a.priority > b.priority) {
635                //a is of higher priority - descending
636                if (VDBG)   {
637                    logDbg("compareWifiConfigurations prefers -1 " + a.configKey() + " over "
638                            + b.configKey() + " due to priority");
639                }
640
641                order = -1;
642            } else if (a.priority < b.priority) {
643                //a is of lower priority - ascending
644                if (VDBG)  {
645                    logDbg("compareWifiConfigurations prefers +1 " + b.configKey() + " over "
646                            + a.configKey() + " due to priority");
647                }
648
649              order = 1;
650            } else {
651                //maybe just look at RSSI or band
652                if (VDBG)  {
653                    logDbg("compareWifiConfigurations prefers +1 " + b.configKey() + " over "
654                            + a.configKey() + " due to nothing");
655                }
656
657                order = compareWifiConfigurationsRSSI(a, b); //compare RSSI
658            }
659        }
660
661        String sorder = " == ";
662        if (order > 0)
663            sorder = " < ";
664        if (order < 0)
665            sorder = " > ";
666
667        if (VDBG)   {
668            logDbg("compareWifiConfigurations Done: " + a.configKey() + sorder
669                    + b.configKey() + " order " + Integer.toString(order));
670        }
671
672        return order;
673    }
674
675    /* attemptAutoJoin function implement the core of the a network switching algorithm */
676    void attemptAutoJoin() {
677        String lastSelectedConfiguration = mWifiConfigStore.getLastSelectedConfiguration();
678
679        //reset the currentConfiguration Key, and set it only if WifiStateMachine and
680        // supplicant agree
681        mCurrentConfigurationKey = null;
682        WifiConfiguration currentConfiguration = mWifiStateMachine.getCurrentWifiConfiguration();
683
684        WifiConfiguration candidate = null;
685
686        /* obtain the subset of recently seen networks */
687        List<WifiConfiguration> list = mWifiConfigStore.getRecentConfiguredNetworks(3000, true);
688        if (list == null) {
689            if (VDBG)  logDbg("attemptAutoJoin nothing");
690            return;
691        }
692
693        /* find the currently connected network: ask the supplicant directly */
694        String val = mWifiNative.status();
695        String status[] = val.split("\\r?\\n");
696        if (VDBG) {
697            logDbg("attemptAutoJoin() status=" + val + " split="
698                    + Integer.toString(status.length));
699        }
700
701        int currentNetId = -1;
702        for (String key : status) {
703            if (key.regionMatches(0, "id=", 0, 3)) {
704                int idx = 3;
705                currentNetId = 0;
706                while (idx < key.length()) {
707                    char c = key.charAt(idx);
708
709                    if ((c >= 0x30) && (c <= 0x39)) {
710                        currentNetId *= 10;
711                        currentNetId += c - 0x30;
712                        idx++;
713                    } else {
714                        break;
715                    }
716                }
717            }
718        }
719        if (DBG) {
720            logDbg("attemptAutoJoin() num recent config " + Integer.toString(list.size())
721                    + " ---> currentId=" + Integer.toString(currentNetId));
722        }
723
724        if (currentConfiguration != null) {
725            if (currentNetId != currentConfiguration.networkId) {
726                logDbg("attemptAutoJoin() ERROR wpa_supplicant out of sync nid="
727                        + Integer.toString(currentNetId) + " WifiStateMachine="
728                        + Integer.toString(currentConfiguration.networkId));
729                //I think this can happen due do race conditions, now what to do??
730                // -> throw an exception, or,
731                // -> dont use the current configuration at all for autojoin
732                //and hope that autojoining will kick us out of this state.
733                currentConfiguration = null;
734            } else {
735                mCurrentConfigurationKey = currentConfiguration.configKey();
736            }
737        }
738
739        /* select Best Network candidate from known WifiConfigurations */
740        for (WifiConfiguration config : list) {
741            if ((config.status == WifiConfiguration.Status.DISABLED)
742                    && (config.disableReason == WifiConfiguration.DISABLED_AUTH_FAILURE)) {
743                if (DBG) {
744                    logDbg("attemptAutoJoin skip candidate due to auth failure key "
745                            + config.configKey(true));
746                }
747                continue;
748            }
749
750            if (config.SSID == null) {
751                return;
752            }
753
754            if (config.autoJoinStatus >= WifiConfiguration.AUTO_JOIN_TEMPORARY_DISABLED) {
755                //avoid temporarily disabled networks altogether
756                //TODO: implement a better logic which will reenable the network after some time
757                if (DBG) {
758                    logDbg("attemptAutoJoin skip candidate due to auto join status "
759                            + Integer.toString(config.autoJoinStatus) + " key "
760                            + config.configKey(true));
761                }
762                continue;
763            }
764
765            if (config.networkId == currentNetId) {
766                if (DBG) {
767                    logDbg("attemptAutoJoin skip current candidate  "
768                            + Integer.toString(currentNetId)
769                            + " key " + config.configKey(true));
770                }
771                continue;
772            }
773
774            if (lastSelectedConfiguration == null ||
775                    !config.configKey().equals(lastSelectedConfiguration)) {
776                //don't try to autojoin a network that is too far
777                if (config.visibility == null) {
778                    continue;
779                }
780                if (config.visibility.rssi5 < -70 && config.visibility.rssi24 < -80) {
781                    continue;
782                }
783            }
784
785            if (DBG) {
786                logDbg("attemptAutoJoin trying candidate id=" + config.networkId + " "
787                        + config.SSID + " key " + config.configKey(true));
788            }
789
790            if (candidate == null) {
791                candidate = config;
792            } else {
793                if (VDBG)  {
794                    logDbg("attemptAutoJoin will compare candidate  " + candidate.configKey()
795                            + " with " + config.configKey() + " key " + config.configKey(true));
796                }
797
798                int order = compareWifiConfigurations(candidate, config);
799                if (order > 0) {
800                    //ascending : candidate < config
801                    candidate = config;
802                }
803            }
804        }
805
806        /* now, go thru scan result to try finding a better Herrevad network */
807        if (mNetworkScoreCache != null) {
808            int rssi5 = WifiConfiguration.INVALID_RSSI;
809            int rssi24 = WifiConfiguration.INVALID_RSSI;
810            WifiConfiguration.Visibility visibility;
811            if (candidate != null) {
812                rssi5 = candidate.visibility.rssi5;
813                rssi24 = candidate.visibility.rssi24;
814            }
815
816            //get current date
817            Date now = new Date();
818            long now_ms = now.getTime();
819
820            if (rssi5 < -60 && rssi24 < -70) {
821                for (ScanResult result : scanResultCache.values()) {
822                    if ((now_ms - result.seen) < 3000) {
823                        int score = mNetworkScoreCache.getNetworkScore(result);
824                        if (score > 0) {
825                            // try any arbitrary formula for now, adding apple and oranges,
826                            // i.e. adding network score and "dBm over noise"
827                           if (result.frequency < 4000) {
828                                if ((result.level + score) > (rssi24 -40)) {
829                                    // force it as open, TBD should we otherwise verify that this
830                                    // BSSID only supports open??
831                                    result.capabilities = "";
832
833                                    //switch to this scan result
834                                    candidate =
835                                           mWifiConfigStore.wifiConfigurationFromScanResult(result);
836                                    candidate.ephemeral = true;
837                                }
838                           } else {
839                                if ((result.level + score) > (rssi5 -30)) {
840                                    // force it as open, TBD should we otherwise verify that this
841                                    // BSSID only supports open??
842                                    result.capabilities = "";
843
844                                    //switch to this scan result
845                                    candidate =
846                                           mWifiConfigStore.wifiConfigurationFromScanResult(result);
847                                    candidate.ephemeral = true;
848                                }
849                           }
850                        }
851                    }
852                }
853            }
854        }
855        if (candidate != null) {
856        /* if candidate is found, check the state of the connection so as
857        to decide if we should be acting on this candidate and switching over */
858            int networkDelta = compareNetwork(candidate);
859            if (DBG && (networkDelta > 0)) {
860                logDbg("attemptAutoJoin did find candidate " + candidate.configKey()
861                        + " for delta " + Integer.toString(networkDelta));
862            }
863            /* ASK traffic poller permission to switch:
864                for instance,
865                if user is currently streaming voice traffic,
866                then don’t switch regardless of the delta */
867
868            if (mWifiTrafficPoller.shouldSwitchNetwork(networkDelta)) {
869                if (mStaStaSupported) {
870                    logDbg("mStaStaSupported --> error do nothing now ");
871                } else {
872                    if (DBG) {
873                        logDbg("AutoJoin auto connect with netId "
874                                + Integer.toString(candidate.networkId)
875                                + " to " + candidate.configKey());
876                    }
877
878                    mWifiStateMachine.sendMessage(WifiStateMachine.CMD_AUTO_CONNECT,
879                            candidate.networkId);
880                    //mWifiConfigStore.enableNetworkWithoutBroadcast(candidate.networkId, true);
881
882                    //we would do the below only if we want to persist the new choice
883                    //mWifiConfigStore.selectNetwork(candidate.networkId);
884
885                }
886            }
887        }
888        if (VDBG) logDbg("Done attemptAutoJoin");
889    }
890}
891
892