FileDocCategorySizeDatePackage
WifiAutoJoinController.javaAPI DocAndroid 5.1 API84326Thu Mar 12 22:22:52 GMT 2015com.android.server.wifi

WifiAutoJoinController.java

/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.wifi;

import android.content.Context;
import android.net.NetworkKey;
import android.net.NetworkScoreManager;
import android.net.WifiKey;
import android.net.wifi.*;
import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.os.SystemClock;
import android.provider.Settings;
import android.os.Process;
import android.text.TextUtils;
import android.util.Log;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.HashMap;
import java.util.List;

/**
 * AutoJoin controller is responsible for WiFi Connect decision
 *
 * It runs in the thread context of WifiStateMachine
 *
 */
public class WifiAutoJoinController {

    private Context mContext;
    private WifiStateMachine mWifiStateMachine;
    private WifiConfigStore mWifiConfigStore;
    private WifiNative mWifiNative;

    private NetworkScoreManager scoreManager;
    private WifiNetworkScoreCache mNetworkScoreCache;

    private static final String TAG = "WifiAutoJoinController ";
    private static boolean DBG = false;
    private static boolean VDBG = false;
    private static final boolean mStaStaSupported = false;

    public static int mScanResultMaximumAge = 40000; /* milliseconds unit */
    public static int mScanResultAutoJoinAge = 5000; /* milliseconds unit */

    private String mCurrentConfigurationKey = null; //used by autojoin

    private HashMap<String, ScanResult> scanResultCache =
            new HashMap<String, ScanResult>();

    private WifiConnectionStatistics mWifiConnectionStatistics;

    /** Whether to allow connections to untrusted networks. */
    private boolean mAllowUntrustedConnections = false;

    /* For debug purpose only: if the scored override a score */
    boolean didOverride = false;

    // Lose the non-auth failure blacklisting after 8 hours
    private final static long loseBlackListHardMilli = 1000 * 60 * 60 * 8;
    // Lose some temporary blacklisting after 30 minutes
    private final static long loseBlackListSoftMilli = 1000 * 60 * 30;

    /** @see android.provider.Settings.Global#WIFI_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS */
    private static final long DEFAULT_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS = 1000 * 60; // 1 minute

    public static final int AUTO_JOIN_IDLE = 0;
    public static final int AUTO_JOIN_ROAMING = 1;
    public static final int AUTO_JOIN_EXTENDED_ROAMING = 2;
    public static final int AUTO_JOIN_OUT_OF_NETWORK_ROAMING = 3;

    public static final int HIGH_THRESHOLD_MODIFIER = 5;

    // Below are AutoJoin wide parameters indicating if we should be aggressive before joining
    // weak network. Note that we cannot join weak network that are going to be marked as unanted by
    // ConnectivityService because this will trigger link flapping.
    /**
     * There was a non-blacklisted configuration that we bailed from because of a weak signal
     */
    boolean didBailDueToWeakRssi = false;
    /**
     * number of time we consecutively bailed out of an eligible network because its signal
     * was too weak
     */
    int weakRssiBailCount = 0;

    WifiAutoJoinController(Context c, WifiStateMachine w, WifiConfigStore s,
                           WifiConnectionStatistics st, WifiNative n) {
        mContext = c;
        mWifiStateMachine = w;
        mWifiConfigStore = s;
        mWifiNative = n;
        mNetworkScoreCache = null;
        mWifiConnectionStatistics = st;
        scoreManager =
                (NetworkScoreManager) mContext.getSystemService(Context.NETWORK_SCORE_SERVICE);
        if (scoreManager == null)
            logDbg("Registered scoreManager NULL " + " service " + Context.NETWORK_SCORE_SERVICE);

        if (scoreManager != null) {
            mNetworkScoreCache = new WifiNetworkScoreCache(mContext);
            scoreManager.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
        } else {
            logDbg("No network score service: Couldnt register as a WiFi score Manager, type="
                    + Integer.toString(NetworkKey.TYPE_WIFI)
                    + " service " + Context.NETWORK_SCORE_SERVICE);
            mNetworkScoreCache = null;
        }
    }

    void enableVerboseLogging(int verbose) {
        if (verbose > 0 ) {
            DBG = true;
            VDBG = true;
        } else {
            DBG = false;
            VDBG = false;
        }
    }

    /**
     * Flush out scan results older than mScanResultMaximumAge
     *
     */
    private void ageScanResultsOut(int delay) {
        if (delay <= 0) {
            delay = mScanResultMaximumAge; // Something sane
        }
        long milli = System.currentTimeMillis();
        if (VDBG) {
            logDbg("ageScanResultsOut delay " + Integer.valueOf(delay) + " size "
                    + Integer.valueOf(scanResultCache.size()) + " now " + Long.valueOf(milli));
        }

        Iterator<HashMap.Entry<String,ScanResult>> iter = scanResultCache.entrySet().iterator();
        while (iter.hasNext()) {
            HashMap.Entry<String,ScanResult> entry = iter.next();
            ScanResult result = entry.getValue();

            if ((result.seen + delay) < milli) {
                iter.remove();
            }
        }
    }

    int addToScanCache(List<ScanResult> scanList) {
        int numScanResultsKnown = 0; // Record number of scan results we knew about
        WifiConfiguration associatedConfig = null;
        boolean didAssociate = false;
        long now = System.currentTimeMillis();

        ArrayList<NetworkKey> unknownScanResults = new ArrayList<NetworkKey>();

        for(ScanResult result: scanList) {
            if (result.SSID == null) continue;

            // Make sure we record the last time we saw this result
            result.seen = System.currentTimeMillis();

            // Fetch the previous instance for this result
            ScanResult sr = scanResultCache.get(result.BSSID);
            if (sr != null) {
                if (mWifiConfigStore.scanResultRssiLevelPatchUp != 0
                        && result.level == 0
                        && sr.level < -20) {
                    // A 'zero' RSSI reading is most likely a chip problem which returns
                    // an unknown RSSI, hence ignore it
                    result.level = sr.level;
                }

                // If there was a previous cache result for this BSSID, average the RSSI values
                result.averageRssi(sr.level, sr.seen, mScanResultMaximumAge);

                // Remove the previous Scan Result - this is not necessary
                scanResultCache.remove(result.BSSID);
            } else if (mWifiConfigStore.scanResultRssiLevelPatchUp != 0 && result.level == 0) {
                // A 'zero' RSSI reading is most likely a chip problem which returns
                // an unknown RSSI, hence initialize it to a sane value
                result.level = mWifiConfigStore.scanResultRssiLevelPatchUp;
            }

            if (!mNetworkScoreCache.isScoredNetwork(result)) {
                WifiKey wkey;
                // Quoted SSIDs are the only one valid at this stage
                try {
                    wkey = new WifiKey("\"" + result.SSID + "\"", result.BSSID);
                } catch (IllegalArgumentException e) {
                    logDbg("AutoJoinController: received badly encoded SSID=[" + result.SSID +
                            "] ->skipping this network");
                    wkey = null;
                }
                if (wkey != null) {
                    NetworkKey nkey = new NetworkKey(wkey);
                    //if we don't know this scan result then request a score from the scorer
                    unknownScanResults.add(nkey);
                }
                if (VDBG) {
                    String cap = "";
                    if (result.capabilities != null)
                        cap = result.capabilities;
                    logDbg(result.SSID + " " + result.BSSID + " rssi="
                            + result.level + " cap " + cap + " is not scored");
                }
            } else {
                if (VDBG) {
                    String cap = "";
                    if (result.capabilities != null)
                        cap = result.capabilities;
                    int score = mNetworkScoreCache.getNetworkScore(result);
                    logDbg(result.SSID + " " + result.BSSID + " rssi="
                            + result.level + " cap " + cap + " is scored : " + score);
                }
            }

            // scanResultCache.put(result.BSSID, new ScanResult(result));
            scanResultCache.put(result.BSSID, result);
            // Add this BSSID to the scanResultCache of a Saved WifiConfiguration
            didAssociate = mWifiConfigStore.updateSavedNetworkHistory(result);

            // If not successful, try to associate this BSSID to an existing Saved WifiConfiguration
            if (!didAssociate) {
                // We couldn't associate the scan result to a Saved WifiConfiguration
                // Hence it is untrusted
                result.untrusted = true;
                associatedConfig = mWifiConfigStore.associateWithConfiguration(result);
                if (associatedConfig != null && associatedConfig.SSID != null) {
                    if (VDBG) {
                        logDbg("addToScanCache save associated config "
                                + associatedConfig.SSID + " with " + result.SSID
                                + " status " + associatedConfig.autoJoinStatus
                                + " reason " + associatedConfig.disableReason
                                + " tsp " + associatedConfig.blackListTimestamp
                                + " was " + (now - associatedConfig.blackListTimestamp));
                    }
                    mWifiStateMachine.sendMessage(
                            WifiStateMachine.CMD_AUTO_SAVE_NETWORK, associatedConfig);
                    didAssociate = true;
                }
            } else {
                // If the scan result has been blacklisted fir 18 hours -> unblacklist
                if ((now - result.blackListTimestamp) > loseBlackListHardMilli) {
                    result.setAutoJoinStatus(ScanResult.ENABLED);
                }
            }
            if (didAssociate) {
                numScanResultsKnown++;
                result.isAutoJoinCandidate ++;
            } else {
                result.isAutoJoinCandidate = 0;
            }
        }

        if (unknownScanResults.size() != 0) {
            NetworkKey[] newKeys =
                    unknownScanResults.toArray(new NetworkKey[unknownScanResults.size()]);
            // Kick the score manager, we will get updated scores asynchronously
            scoreManager.requestScores(newKeys);
        }
        return numScanResultsKnown;
    }

    void logDbg(String message) {
        logDbg(message, false);
    }

    void logDbg(String message, boolean stackTrace) {
        if (stackTrace) {
            Log.e(TAG, message + " stack:"
                    + Thread.currentThread().getStackTrace()[2].getMethodName() + " - "
                    + Thread.currentThread().getStackTrace()[3].getMethodName() + " - "
                    + Thread.currentThread().getStackTrace()[4].getMethodName() + " - "
                    + Thread.currentThread().getStackTrace()[5].getMethodName());
        } else {
            Log.e(TAG, message);
        }
    }

    // Called directly from WifiStateMachine
    int newSupplicantResults(boolean doAutoJoin) {
        int numScanResultsKnown;
        List<ScanResult> scanList = mWifiStateMachine.getScanResultsListNoCopyUnsync();
        numScanResultsKnown = addToScanCache(scanList);
        ageScanResultsOut(mScanResultMaximumAge);
        if (DBG) {
            logDbg("newSupplicantResults size=" + Integer.valueOf(scanResultCache.size())
                        + " known=" + numScanResultsKnown + " "
                        + doAutoJoin);
        }
        if (doAutoJoin) {
            attemptAutoJoin();
        }
        mWifiConfigStore.writeKnownNetworkHistory(false);
        return numScanResultsKnown;
    }


    /**
     * Not used at the moment
     * should be a call back from WifiScanner HAL ??
     * this function is not hooked and working yet, it will receive scan results from WifiScanners
     * with the list of IEs,then populate the capabilities by parsing the IEs and inject the scan
     * results as normal.
     */
    void newHalScanResults() {
        List<ScanResult> scanList = null;//mWifiScanner.syncGetScanResultsList();
        String akm = WifiParser.parse_akm(null, null);
        logDbg(akm);
        addToScanCache(scanList);
        ageScanResultsOut(0);
        attemptAutoJoin();
        mWifiConfigStore.writeKnownNetworkHistory(false);
    }

    /**
     *  network link quality changed, called directly from WifiTrafficPoller,
     * or by listening to Link Quality intent
     */
    void linkQualitySignificantChange() {
        attemptAutoJoin();
    }

    /**
     * compare a WifiConfiguration against the current network, return a delta score
     * If not associated, and the candidate will always be better
     * For instance if the candidate is a home network versus an unknown public wifi,
     * the delta will be infinite, else compare Kepler scores etcâ