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

WifiWatchdogStateMachine

public class WifiWatchdogStateMachine extends com.android.internal.util.StateMachine
WifiWatchdogStateMachine monitors the connection to a WiFi network. When WiFi connects at L2 layer, the beacons from access point reach the device and it can maintain a connection, but the application connectivity can be flaky (due to bigger packet size exchange).

We now monitor the quality of the last hop on WiFi using packet loss ratio as an indicator to decide if the link is good enough to switch to Wi-Fi as the uplink.

When WiFi is connected, the WiFi watchdog keeps sampling the RSSI and the instant packet loss, and record it as per-AP loss-to-rssi statistics. When the instant packet loss is higher than a threshold, the WiFi watchdog sends a poor link notification to avoid WiFi connection temporarily.

While WiFi is being avoided, the WiFi watchdog keep watching the RSSI to bring the WiFi connection back. Once the RSSI is high enough to achieve a lower packet loss, a good link detection is sent such that the WiFi connection become available again.

BSSID roaming has been taken into account. When user is moving across multiple APs, the WiFi watchdog will detect that and keep watching the currently connected AP.

Power impact should be minimal since much of the measurement relies on passive statistics already being tracked at the driver and the polling is done when screen is turned on and the RSSI is in a certain range.

hide

Fields Summary
private static final boolean
DBG
private static final int
BASE
private static final int
EVENT_WATCHDOG_TOGGLED
private static final int
EVENT_NETWORK_STATE_CHANGE
private static final int
EVENT_RSSI_CHANGE
private static final int
EVENT_SUPPLICANT_STATE_CHANGE
private static final int
EVENT_WIFI_RADIO_STATE_CHANGE
private static final int
EVENT_WATCHDOG_SETTINGS_CHANGE
private static final int
EVENT_BSSID_CHANGE
private static final int
EVENT_SCREEN_ON
private static final int
EVENT_SCREEN_OFF
private static final int
CMD_RSSI_FETCH
static final int
POOR_LINK_DETECTED
static final int
GOOD_LINK_DETECTED
private static final int
LINK_MONITOR_LEVEL_THRESHOLD
WiFi link statistics is monitored and recorded actively below this threshold.

Larger threshold is more adaptive but increases sampling cost.

private static final int
BSSID_STAT_CACHE_SIZE
Remember packet loss statistics of how many BSSIDs.

Larger size is usually better but requires more space.

private static final int
BSSID_STAT_RANGE_LOW_DBM
RSSI range of a BSSID statistics. Within the range, (RSSI -> packet loss %) mappings are stored.

Larger range is usually better but requires more space.

private static final int
BSSID_STAT_RANGE_HIGH_DBM
See {@link #BSSID_STAT_RANGE_LOW_DBM}.
private static final int
BSSID_STAT_EMPTY_COUNT
How many consecutive empty data point to trigger a empty-cache detection. In this case, a preset/default loss value (function on RSSI) is used.

In normal uses, some RSSI values may never be seen due to channel randomness. However, the size of such empty RSSI chunk in normal use is generally 1~2.

private static final long
LINK_SAMPLING_INTERVAL_MS
Sample interval for packet loss statistics, in msec.

Smaller interval is more accurate but increases sampling cost (battery consumption).

private static final double
EXP_COEFFICIENT_RECORD
Coefficients (alpha) for moving average for packet loss tracking. Must be within (0.0, 1.0).

Equivalent number of samples: N = 2 / alpha - 1 . We want the historic loss to base on more data points to be statistically reliable. We want the current instant loss to base on less data points to be responsive.

private static final double
EXP_COEFFICIENT_MONITOR
See {@link #EXP_COEFFICIENT_RECORD}.
private static final double
POOR_LINK_LOSS_THRESHOLD
Thresholds for sending good/poor link notifications, in packet loss %. Good threshold must be smaller than poor threshold. Use smaller poor threshold to avoid WiFi more aggressively. Use smaller good threshold to bring back WiFi more conservatively.

When approaching the boundary, loss ratio jumps significantly within a few dBs. 50% loss threshold is a good balance between accuracy and reponsiveness. <=10% good threshold is a safe value to avoid jumping back to WiFi too easily.

private static final double
GOOD_LINK_LOSS_THRESHOLD
See {@link #POOR_LINK_LOSS_THRESHOLD}.
private static final int
POOR_LINK_SAMPLE_COUNT
Number of samples to confirm before sending a poor link notification. Response time = confirm_count * sample_interval .

A smaller threshold improves response speed but may suffer from randomness. According to experiments, 3~5 are good values to achieve a balance. These parameters should be tuned along with {@link #LINK_SAMPLING_INTERVAL_MS}.

private static final double
POOR_LINK_MIN_VOLUME
Minimum volume (converted from pkt/sec) to detect a poor link, to avoid randomness.

According to experiments, 1pkt/sec is too sensitive but 3pkt/sec is slightly unresponsive.

private static final int
GOOD_LINK_RSSI_RANGE_MIN
When a poor link is detected, we scan over this range (based on current poor link RSSI) for a target RSSI that satisfies a target packet loss. Refer to {@link #GOOD_LINK_TARGET}.

We want range_min not too small to avoid jumping back to WiFi too easily.

private static final int
GOOD_LINK_RSSI_RANGE_MAX
See {@link #GOOD_LINK_RSSI_RANGE_MIN}.
private static final GoodLinkTarget[]
GOOD_LINK_TARGET
Adaptive good link target to avoid flapping. When a poor link is detected, a good link target is calculated as follows:

targetRSSI = min { rssi | loss(rssi) < GOOD_LINK_LOSS_THRESHOLD } + rssi_adj[i], where rssi is within the above GOOD_LINK_RSSI_RANGE. targetCount = sample_count[i] .

While WiFi is being avoided, we keep monitoring its signal strength. Good link notification is sent when we see current RSSI >= targetRSSI for targetCount consecutive times.

Index i is incremented each time after a poor link detection. Index i is decreased to at most k if the last poor link was at lease reduce_time[k] ago.

Intuitively, larger index i makes it more difficult to get back to WiFi, avoiding flapping. In experiments, (+9 dB / 30 counts) makes it quite difficult to achieve. Avoid using it unless flapping is really bad (say, last poor link is < 1 min ago).

private static final MaxAvoidTime[]
MAX_AVOID_TIME
The max time to avoid a BSSID, to prevent avoiding forever. If current RSSI is at least min_rssi[i], the max avoidance time is at most max_time[i]

It is unusual to experience high packet loss at high RSSI. Something unusual must be happening (e.g. strong interference). For higher signal strengths, we set the avoidance time to be low to allow for quick turn around from temporary interference.

See {@link BssidStatistics#poorLinkDetected}.

private android.content.Context
mContext
private android.content.ContentResolver
mContentResolver
private android.net.wifi.WifiManager
mWifiManager
private android.content.IntentFilter
mIntentFilter
private android.content.BroadcastReceiver
mBroadcastReceiver
private com.android.internal.util.AsyncChannel
mWsmChannel
private android.net.wifi.WifiInfo
mWifiInfo
private android.net.LinkProperties
mLinkProperties
private static boolean
sWifiOnly
private boolean
mPoorNetworkDetectionEnabled
private android.util.LruCache
mBssidCache
private int
mRssiFetchToken
private int
mCurrentSignalLevel
private BssidStatistics
mCurrentBssid
private VolumeWeightedEMA
mCurrentLoss
private boolean
mIsScreenOn
private static double[]
sPresetLoss
private DefaultState
mDefaultState
private WatchdogDisabledState
mWatchdogDisabledState
private WatchdogEnabledState
mWatchdogEnabledState
private NotConnectedState
mNotConnectedState
private VerifyingLinkState
mVerifyingLinkState
private ConnectedState
mConnectedState
private OnlineWatchState
mOnlineWatchState
private LinkMonitoringState
mLinkMonitoringState
private OnlineState
mOnlineState
Constructors Summary
private WifiWatchdogStateMachine(android.content.Context context, android.os.Messenger dstMessenger)
STATE MAP Default / \ Disabled Enabled / \ \ NotConnected Verifying Connected /---------\ (all other states)


                                                                                                                          
         
        super("WifiWatchdogStateMachine");
        mContext = context;
        mContentResolver = context.getContentResolver();
        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);

        mWsmChannel.connectSync(mContext, getHandler(), dstMessenger);

        setupNetworkReceiver();

        // the content observer to listen needs a handler
        registerForSettingsChanges();
        registerForWatchdogToggle();
        addState(mDefaultState);
            addState(mWatchdogDisabledState, mDefaultState);
            addState(mWatchdogEnabledState, mDefaultState);
                addState(mNotConnectedState, mWatchdogEnabledState);
                addState(mVerifyingLinkState, mWatchdogEnabledState);
                addState(mConnectedState, mWatchdogEnabledState);
                    addState(mOnlineWatchState, mConnectedState);
                    addState(mLinkMonitoringState, mConnectedState);
                    addState(mOnlineState, mConnectedState);

        if (isWatchdogEnabled()) {
            setInitialState(mNotConnectedState);
        } else {
            setInitialState(mWatchdogDisabledState);
        }
        setLogRecSize(25);
        setLogOnlyTransitions(true);
        updateSettings();
    
Methods Summary
private intcalculateSignalLevel(int rssi)

        int signalLevel = WifiManager.calculateSignalLevel(rssi, WifiManager.RSSI_LEVELS);
        if (DBG)
            logd("RSSI current: " + mCurrentSignalLevel + " new: " + rssi + ", " + signalLevel);
        return signalLevel;
    
public voiddump(java.io.FileDescriptor fd, java.io.PrintWriter pw, java.lang.String[] args)

        super.dump(fd, pw, args);
        pw.println("mWifiInfo: [" + mWifiInfo + "]");
        pw.println("mLinkProperties: [" + mLinkProperties + "]");
        pw.println("mCurrentSignalLevel: [" + mCurrentSignalLevel + "]");
        pw.println("mPoorNetworkDetectionEnabled: [" + mPoorNetworkDetectionEnabled + "]");
    
private static booleangetSettingsGlobalBoolean(android.content.ContentResolver cr, java.lang.String name, boolean def)
Convenience function for retrieving a single secure settings value as a boolean. Note that internally setting values are always stored as strings; this function converts the string to a boolean for you. The default value will be returned if the setting is not defined or not a valid boolean.

param
cr The ContentResolver to access.
param
name The name of the setting to retrieve.
param
def Value to return if the setting is not defined.
return
The setting's current value, or 'def' if it is not defined or not a valid boolean.

        return Settings.Global.getInt(cr, name, def ? 1 : 0) == 1;
    
private booleanisWatchdogEnabled()

        boolean ret = getSettingsGlobalBoolean(
                mContentResolver, Settings.Global.WIFI_WATCHDOG_ON, true);
        if (DBG) logd("Watchdog enabled " + ret);
        return ret;
    
public static com.android.server.wifi.WifiWatchdogStateMachinemakeWifiWatchdogStateMachine(android.content.Context context, android.os.Messenger dstMessenger)

        ContentResolver contentResolver = context.getContentResolver();

        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(
                Context.CONNECTIVITY_SERVICE);
        sWifiOnly = (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false);

        // Watchdog is always enabled. Poor network detection can be seperately turned on/off
        // TODO: Remove this setting & clean up state machine since we always have
        // watchdog in an enabled state
        putSettingsGlobalBoolean(contentResolver, Settings.Global.WIFI_WATCHDOG_ON, true);

        WifiWatchdogStateMachine wwsm = new WifiWatchdogStateMachine(context, dstMessenger);
        wwsm.start();
        return wwsm;
    
private static booleanputSettingsGlobalBoolean(android.content.ContentResolver cr, java.lang.String name, boolean value)
Convenience function for updating a single settings value as an integer. This will either create a new entry in the table if the given name does not exist, or modify the value of the existing row with that name. Note that internally setting values are always stored as strings, so this function converts the given value to a string before storing it.

param
cr The ContentResolver to access.
param
name The name of the setting to modify.
param
value The new value for the setting.
return
true if the value was set, false on database errors

        return Settings.Global.putInt(cr, name, value ? 1 : 0);
    
private voidregisterForSettingsChanges()
Observes watchdogs secure setting changes.

        ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
            @Override
            public void onChange(boolean selfChange) {
                sendMessage(EVENT_WATCHDOG_SETTINGS_CHANGE);
            }
        };

        mContext.getContentResolver().registerContentObserver(
                Settings.Global.getUriFor(Settings.Global.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED),
                false, contentObserver);
    
private voidregisterForWatchdogToggle()
Observes the watchdog on/off setting, and takes action when changed.

        ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
            @Override
            public void onChange(boolean selfChange) {
                sendMessage(EVENT_WATCHDOG_TOGGLED);
            }
        };

        mContext.getContentResolver().registerContentObserver(
                Settings.Global.getUriFor(Settings.Global.WIFI_WATCHDOG_ON),
                false, contentObserver);
    
private voidsendLinkStatusNotification(boolean isGood)

        if (DBG) logd("########################################");
        if (isGood) {
            mWsmChannel.sendMessage(GOOD_LINK_DETECTED);
            if (mCurrentBssid != null) {
                mCurrentBssid.mLastTimeGood = SystemClock.elapsedRealtime();
            }
            if (DBG) logd("Good link notification is sent");
        } else {
            mWsmChannel.sendMessage(POOR_LINK_DETECTED);
            if (mCurrentBssid != null) {
                mCurrentBssid.mLastTimePoor = SystemClock.elapsedRealtime();
            }
            logd("Poor link notification is sent");
        }
    
private voidsetupNetworkReceiver()

        mBroadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
                    obtainMessage(EVENT_RSSI_CHANGE,
                            intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200), 0).sendToTarget();
                } else if (action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)) {
                    sendMessage(EVENT_SUPPLICANT_STATE_CHANGE, intent);
                } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
                    sendMessage(EVENT_NETWORK_STATE_CHANGE, intent);
                } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
                    sendMessage(EVENT_SCREEN_ON);
                } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
                    sendMessage(EVENT_SCREEN_OFF);
                } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
                    sendMessage(EVENT_WIFI_RADIO_STATE_CHANGE,intent.getIntExtra(
                            WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN));
                }
            }
        };

        mIntentFilter = new IntentFilter();
        mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
        mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
        mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
        mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
        mIntentFilter.addAction(Intent.ACTION_SCREEN_ON);
        mIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
        mContext.registerReceiver(mBroadcastReceiver, mIntentFilter);
    
private voidupdateCurrentBssid(java.lang.String bssid)

        if (DBG) logd("Update current BSSID to " + (bssid != null ? bssid : "null"));

        // if currently not connected, then set current BSSID to null
        if (bssid == null) {
            if (mCurrentBssid == null) return;
            mCurrentBssid = null;
            if (DBG) logd("BSSID changed");
            sendMessage(EVENT_BSSID_CHANGE);
            return;
        }

        // if it is already the current BSSID, then done
        if (mCurrentBssid != null && bssid.equals(mCurrentBssid.mBssid)) return;

        // search for the new BSSID in the cache, add to cache if not found
        mCurrentBssid = mBssidCache.get(bssid);
        if (mCurrentBssid == null) {
            mCurrentBssid = new BssidStatistics(bssid);
            mBssidCache.put(bssid, mCurrentBssid);
        }

        // send BSSID change notification
        if (DBG) logd("BSSID changed");
        sendMessage(EVENT_BSSID_CHANGE);
    
private voidupdateSettings()

        if (DBG) logd("Updating secure settings");

        // Unconditionally disable poor network avoidance, since this mechanism is obsolete
        mPoorNetworkDetectionEnabled = false;