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