WifiWatchdogStateMachine.java revision 90d57dfac3113247e2d38a2235254fc35d12856a
1/*
2 * Copyright (C) 2011 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 android.net.wifi;
18
19import android.app.Notification;
20import android.app.NotificationManager;
21import android.app.PendingIntent;
22import android.content.BroadcastReceiver;
23import android.content.ContentResolver;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.content.res.Resources;
28import android.database.ContentObserver;
29import android.net.arp.ArpPeer;
30import android.net.ConnectivityManager;
31import android.net.LinkAddress;
32import android.net.LinkProperties;
33import android.net.NetworkInfo;
34import android.net.RouteInfo;
35import android.net.Uri;
36import android.os.Message;
37import android.os.SystemClock;
38import android.os.SystemProperties;
39import android.provider.Settings;
40import android.provider.Settings.Secure;
41import android.util.Log;
42
43import com.android.internal.R;
44import com.android.internal.util.AsyncChannel;
45import com.android.internal.util.Protocol;
46import com.android.internal.util.State;
47import com.android.internal.util.StateMachine;
48
49import java.io.IOException;
50import java.io.PrintWriter;
51import java.net.HttpURLConnection;
52import java.net.InetAddress;
53import java.net.SocketException;
54import java.net.URL;
55
56/**
57 * WifiWatchdogStateMachine monitors the connection to a Wi-Fi
58 * network. After the framework notifies that it has connected to an
59 * acccess point and is waiting for link to be verified, the watchdog
60 * takes over and verifies if the link is good by doing ARP pings to
61 * the gateway using {@link ArpPeer}.
62 *
63 * Upon successful verification, the watchdog notifies and continues
64 * to monitor the link afterwards when the RSSI level falls below
65 * a certain threshold.
66
67 * When Wi-fi connects at L2 layer, the beacons from access point reach
68 * the device and it can maintain a connection, but the application
69 * connectivity can be flaky (due to bigger packet size exchange).
70 *
71 * We now monitor the quality of the last hop on
72 * Wi-Fi using signal strength and ARP connectivity as indicators
73 * to decide if the link is good enough to switch to Wi-Fi as the uplink.
74 *
75 * ARP pings are useful for link validation but can still get through
76 * when the application traffic fails to go through and are thus not
77 * the best indicator of real packet loss since they are tiny packets
78 * (28 bytes) and have a much low chance of packet corruption than the
79 * regular data packets.
80 *
81 * When signal strength and ARP are used together, it ends up working well in tests.
82 * The goal is to switch to Wi-Fi after validating ARP transfer
83 * and RSSI and then switching out of Wi-Fi when we hit a low
84 * signal strength threshold and then waiting until the signal strength
85 * improves and validating ARP transfer.
86 *
87 * @hide
88 */
89public class WifiWatchdogStateMachine extends StateMachine {
90
91    /* STOPSHIP: Keep this configurable for debugging until ship */
92    private static boolean DBG = false;
93    private static final String TAG = "WifiWatchdogStateMachine";
94    private static final String WALLED_GARDEN_NOTIFICATION_ID = "WifiWatchdog.walledgarden";
95
96    /* RSSI Levels as used by notification icon
97       Level 4  -55 <= RSSI
98       Level 3  -66 <= RSSI < -55
99       Level 2  -77 <= RSSI < -67
100       Level 1  -88 <= RSSI < -78
101       Level 0         RSSI < -88 */
102
103    /* Wi-fi connection is considered poor below this
104       RSSI level threshold and the watchdog report it
105       to the WifiStateMachine */
106    private static final int RSSI_LEVEL_CUTOFF = 0;
107    /* Wi-fi connection is monitored actively below this
108       threshold */
109    private static final int RSSI_LEVEL_MONITOR = 1;
110    /* RSSI threshold during monitoring below which network is avoided */
111    private static final int RSSI_MONITOR_THRESHOLD = -84;
112    /* Number of times RSSI is measured to be low before being avoided */
113    private static final int RSSI_MONITOR_COUNT = 5;
114    private int mRssiMonitorCount = 0;
115
116    /* Avoid flapping. The interval is changed over time as long as we continue to avoid
117     * under the max interval after which we reset the interval again */
118    private static final int MIN_INTERVAL_AVOID_BSSID_MS[] = {0, 30 * 1000, 60 * 1000,
119            5 * 60 * 1000, 30 * 60 * 1000};
120    /* Index into the interval array MIN_INTERVAL_AVOID_BSSID_MS */
121    private int mMinIntervalArrayIndex = 0;
122
123    private long mLastBssidAvoidedTime;
124
125    private int mCurrentSignalLevel;
126
127    private static final long DEFAULT_ARP_CHECK_INTERVAL_MS = 2 * 60 * 1000;
128    private static final long DEFAULT_RSSI_FETCH_INTERVAL_MS = 1000;
129    private static final long DEFAULT_WALLED_GARDEN_INTERVAL_MS = 30 * 60 * 1000;
130
131    private static final int DEFAULT_NUM_ARP_PINGS = 5;
132    private static final int DEFAULT_MIN_ARP_RESPONSES = 1;
133
134    private static final int DEFAULT_ARP_PING_TIMEOUT_MS = 100;
135
136    // See http://go/clientsdns for usage approval
137    private static final String DEFAULT_WALLED_GARDEN_URL =
138            "http://clients3.google.com/generate_204";
139    private static final int WALLED_GARDEN_SOCKET_TIMEOUT_MS = 10000;
140
141    /* Some carrier apps might have support captive portal handling. Add some delay to allow
142        app authentication to be done before our test.
143       TODO: This should go away once we provide an API to apps to disable walled garden test
144       for certain SSIDs
145     */
146    private static final int WALLED_GARDEN_START_DELAY_MS = 3000;
147
148    private static final int BASE = Protocol.BASE_WIFI_WATCHDOG;
149
150    /**
151     * Indicates the enable setting of WWS may have changed
152     */
153    private static final int EVENT_WATCHDOG_TOGGLED                 = BASE + 1;
154
155    /**
156     * Indicates the wifi network state has changed. Passed w/ original intent
157     * which has a non-null networkInfo object
158     */
159    private static final int EVENT_NETWORK_STATE_CHANGE             = BASE + 2;
160    /* Passed with RSSI information */
161    private static final int EVENT_RSSI_CHANGE                      = BASE + 3;
162    private static final int EVENT_WIFI_RADIO_STATE_CHANGE          = BASE + 5;
163    private static final int EVENT_WATCHDOG_SETTINGS_CHANGE         = BASE + 6;
164
165    /* Internal messages */
166    private static final int CMD_ARP_CHECK                          = BASE + 11;
167    private static final int CMD_DELAYED_WALLED_GARDEN_CHECK        = BASE + 12;
168    private static final int CMD_RSSI_FETCH                         = BASE + 13;
169
170    /* Notifications to WifiStateMachine */
171    static final int POOR_LINK_DETECTED                             = BASE + 21;
172    static final int GOOD_LINK_DETECTED                             = BASE + 22;
173    static final int RSSI_FETCH                                     = BASE + 23;
174    static final int RSSI_FETCH_SUCCEEDED                           = BASE + 24;
175    static final int RSSI_FETCH_FAILED                              = BASE + 25;
176
177    private static final int SINGLE_ARP_CHECK = 0;
178    private static final int FULL_ARP_CHECK   = 1;
179
180    private Context mContext;
181    private ContentResolver mContentResolver;
182    private WifiManager mWifiManager;
183    private IntentFilter mIntentFilter;
184    private BroadcastReceiver mBroadcastReceiver;
185    private AsyncChannel mWsmChannel = new AsyncChannel();;
186
187    private DefaultState mDefaultState = new DefaultState();
188    private WatchdogDisabledState mWatchdogDisabledState = new WatchdogDisabledState();
189    private WatchdogEnabledState mWatchdogEnabledState = new WatchdogEnabledState();
190    private NotConnectedState mNotConnectedState = new NotConnectedState();
191    private VerifyingLinkState mVerifyingLinkState = new VerifyingLinkState();
192    private ConnectedState mConnectedState = new ConnectedState();
193    private WalledGardenCheckState mWalledGardenCheckState = new WalledGardenCheckState();
194    /* Online and watching link connectivity */
195    private OnlineWatchState mOnlineWatchState = new OnlineWatchState();
196    /* RSSI level is at RSSI_LEVEL_MONITOR and needs close monitoring */
197    private RssiMonitoringState mRssiMonitoringState = new RssiMonitoringState();
198    /* Online and doing nothing */
199    private OnlineState mOnlineState = new OnlineState();
200
201    private int mArpToken = 0;
202    private long mArpCheckIntervalMs;
203    private int mRssiFetchToken = 0;
204    private long mRssiFetchIntervalMs;
205    private long mWalledGardenIntervalMs;
206    private int mNumArpPings;
207    private int mMinArpResponses;
208    private int mArpPingTimeoutMs;
209    private boolean mPoorNetworkDetectionEnabled;
210    private boolean mWalledGardenTestEnabled;
211    private String mWalledGardenUrl;
212
213    private WifiInfo mWifiInfo;
214    private LinkProperties mLinkProperties;
215
216    private long mLastWalledGardenCheckTime = 0;
217
218    private static boolean sWifiOnly = false;
219    private boolean mWalledGardenNotificationShown;
220
221    /**
222     * STATE MAP
223     *          Default
224     *         /       \
225     * Disabled      Enabled
226     *             /     \     \
227     * NotConnected  Verifying  Connected
228     *                         /---------\
229     *                       (all other states)
230     */
231    private WifiWatchdogStateMachine(Context context) {
232        super(TAG);
233        mContext = context;
234        mContentResolver = context.getContentResolver();
235        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
236        mWsmChannel.connectSync(mContext, getHandler(),
237                mWifiManager.getWifiStateMachineMessenger());
238
239        setupNetworkReceiver();
240
241        // The content observer to listen needs a handler
242        registerForSettingsChanges();
243        registerForWatchdogToggle();
244        addState(mDefaultState);
245            addState(mWatchdogDisabledState, mDefaultState);
246            addState(mWatchdogEnabledState, mDefaultState);
247                addState(mNotConnectedState, mWatchdogEnabledState);
248                addState(mVerifyingLinkState, mWatchdogEnabledState);
249                addState(mConnectedState, mWatchdogEnabledState);
250                    addState(mWalledGardenCheckState, mConnectedState);
251                    addState(mOnlineWatchState, mConnectedState);
252                    addState(mRssiMonitoringState, mOnlineWatchState);
253                    addState(mOnlineState, mConnectedState);
254
255        if (isWatchdogEnabled()) {
256            setInitialState(mNotConnectedState);
257        } else {
258            setInitialState(mWatchdogDisabledState);
259        }
260        updateSettings();
261    }
262
263    public static WifiWatchdogStateMachine makeWifiWatchdogStateMachine(Context context) {
264        ContentResolver contentResolver = context.getContentResolver();
265
266        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(
267                Context.CONNECTIVITY_SERVICE);
268        sWifiOnly = (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false);
269
270        // Disable for wifi only devices.
271        if (Settings.Secure.getString(contentResolver, Settings.Secure.WIFI_WATCHDOG_ON) == null
272                && sWifiOnly) {
273            log("Disabling watchog for wi-fi only device");
274            putSettingsBoolean(contentResolver, Settings.Secure.WIFI_WATCHDOG_ON, false);
275        }
276        WifiWatchdogStateMachine wwsm = new WifiWatchdogStateMachine(context);
277        wwsm.start();
278        return wwsm;
279    }
280
281    private void setupNetworkReceiver() {
282        mBroadcastReceiver = new BroadcastReceiver() {
283            @Override
284            public void onReceive(Context context, Intent intent) {
285                String action = intent.getAction();
286                if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
287                    sendMessage(EVENT_NETWORK_STATE_CHANGE, intent);
288                } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
289                    obtainMessage(EVENT_RSSI_CHANGE,
290                            intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200), 0).sendToTarget();
291                } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
292                    sendMessage(EVENT_WIFI_RADIO_STATE_CHANGE,
293                            intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
294                                    WifiManager.WIFI_STATE_UNKNOWN));
295                }
296            }
297        };
298
299        mIntentFilter = new IntentFilter();
300        mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
301        mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
302        mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
303        mContext.registerReceiver(mBroadcastReceiver, mIntentFilter);
304    }
305
306    /**
307     * Observes the watchdog on/off setting, and takes action when changed.
308     */
309    private void registerForWatchdogToggle() {
310        ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
311            @Override
312            public void onChange(boolean selfChange) {
313                sendMessage(EVENT_WATCHDOG_TOGGLED);
314            }
315        };
316
317        mContext.getContentResolver().registerContentObserver(
318                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON),
319                false, contentObserver);
320    }
321
322    /**
323     * Observes watchdogs secure setting changes.
324     */
325    private void registerForSettingsChanges() {
326        ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
327            @Override
328            public void onChange(boolean selfChange) {
329                sendMessage(EVENT_WATCHDOG_SETTINGS_CHANGE);
330            }
331        };
332
333        mContext.getContentResolver().registerContentObserver(
334                Settings.Secure.getUriFor(
335                        Settings.Secure.WIFI_WATCHDOG_ARP_CHECK_INTERVAL_MS),
336                        false, contentObserver);
337        mContext.getContentResolver().registerContentObserver(
338                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS),
339                false, contentObserver);
340        mContext.getContentResolver().registerContentObserver(
341                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_NUM_ARP_PINGS),
342                false, contentObserver);
343        mContext.getContentResolver().registerContentObserver(
344                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_MIN_ARP_RESPONSES),
345                false, contentObserver);
346        mContext.getContentResolver().registerContentObserver(
347                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ARP_PING_TIMEOUT_MS),
348                false, contentObserver);
349        mContext.getContentResolver().registerContentObserver(
350                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED),
351                false, contentObserver);
352        mContext.getContentResolver().registerContentObserver(
353                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED),
354                false, contentObserver);
355        mContext.getContentResolver().registerContentObserver(
356                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL),
357                false, contentObserver);
358    }
359
360    /**
361     * DNS based detection techniques do not work at all hotspots. The one sure
362     * way to check a walled garden is to see if a URL fetch on a known address
363     * fetches the data we expect
364     */
365    private boolean isWalledGardenConnection() {
366        HttpURLConnection urlConnection = null;
367        try {
368            URL url = new URL(mWalledGardenUrl);
369            urlConnection = (HttpURLConnection) url.openConnection();
370            urlConnection.setInstanceFollowRedirects(false);
371            urlConnection.setConnectTimeout(WALLED_GARDEN_SOCKET_TIMEOUT_MS);
372            urlConnection.setReadTimeout(WALLED_GARDEN_SOCKET_TIMEOUT_MS);
373            urlConnection.setUseCaches(false);
374            urlConnection.getInputStream();
375            // We got a valid response, but not from the real google
376            return urlConnection.getResponseCode() != 204;
377        } catch (IOException e) {
378            if (DBG) {
379                log("Walled garden check - probably not a portal: exception " + e);
380            }
381            return false;
382        } finally {
383            if (urlConnection != null) {
384                urlConnection.disconnect();
385            }
386        }
387    }
388
389    public void dump(PrintWriter pw) {
390        pw.print("WatchdogStatus: ");
391        pw.print("State: " + getCurrentState());
392        pw.println("mWifiInfo: [" + mWifiInfo + "]");
393        pw.println("mLinkProperties: [" + mLinkProperties + "]");
394        pw.println("mCurrentSignalLevel: [" + mCurrentSignalLevel + "]");
395        pw.println("mArpCheckIntervalMs: [" + mArpCheckIntervalMs+ "]");
396        pw.println("mRssiFetchIntervalMs: [" + mRssiFetchIntervalMs + "]");
397        pw.println("mWalledGardenIntervalMs: [" + mWalledGardenIntervalMs + "]");
398        pw.println("mNumArpPings: [" + mNumArpPings + "]");
399        pw.println("mMinArpResponses: [" + mMinArpResponses + "]");
400        pw.println("mArpPingTimeoutMs: [" + mArpPingTimeoutMs + "]");
401        pw.println("mPoorNetworkDetectionEnabled: [" + mPoorNetworkDetectionEnabled + "]");
402        pw.println("mWalledGardenTestEnabled: [" + mWalledGardenTestEnabled + "]");
403        pw.println("mWalledGardenUrl: [" + mWalledGardenUrl + "]");
404    }
405
406    private boolean isWatchdogEnabled() {
407        boolean ret = getSettingsBoolean(mContentResolver, Settings.Secure.WIFI_WATCHDOG_ON, true);
408        if (DBG) log("watchdog enabled " + ret);
409        return ret;
410    }
411
412    private void updateSettings() {
413        if (DBG) log("Updating secure settings");
414
415        mArpCheckIntervalMs = Secure.getLong(mContentResolver,
416                Secure.WIFI_WATCHDOG_ARP_CHECK_INTERVAL_MS,
417                DEFAULT_ARP_CHECK_INTERVAL_MS);
418        mRssiFetchIntervalMs = Secure.getLong(mContentResolver,
419                Secure.WIFI_WATCHDOG_RSSI_FETCH_INTERVAL_MS,
420                DEFAULT_RSSI_FETCH_INTERVAL_MS);
421        mNumArpPings = Secure.getInt(mContentResolver,
422                Secure.WIFI_WATCHDOG_NUM_ARP_PINGS,
423                DEFAULT_NUM_ARP_PINGS);
424        mMinArpResponses = Secure.getInt(mContentResolver,
425                Secure.WIFI_WATCHDOG_MIN_ARP_RESPONSES,
426                DEFAULT_MIN_ARP_RESPONSES);
427        mArpPingTimeoutMs = Secure.getInt(mContentResolver,
428                Secure.WIFI_WATCHDOG_ARP_PING_TIMEOUT_MS,
429                DEFAULT_ARP_PING_TIMEOUT_MS);
430        mPoorNetworkDetectionEnabled = getSettingsBoolean(mContentResolver,
431                Settings.Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED, true);
432        mWalledGardenTestEnabled = getSettingsBoolean(mContentResolver,
433                Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, true);
434        mWalledGardenUrl = getSettingsStr(mContentResolver,
435                Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL,
436                DEFAULT_WALLED_GARDEN_URL);
437        mWalledGardenIntervalMs = Secure.getLong(mContentResolver,
438                Secure.WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS,
439                DEFAULT_WALLED_GARDEN_INTERVAL_MS);
440    }
441
442    private void setWalledGardenNotificationVisible(boolean visible) {
443        // If it should be hidden and it is already hidden, then noop
444        if (!visible && !mWalledGardenNotificationShown) {
445            return;
446        }
447
448        Resources r = Resources.getSystem();
449        NotificationManager notificationManager = (NotificationManager) mContext
450            .getSystemService(Context.NOTIFICATION_SERVICE);
451
452        if (visible) {
453            Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(mWalledGardenUrl));
454            intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
455
456            CharSequence title = r.getString(R.string.wifi_available_sign_in, 0);
457            CharSequence details = r.getString(R.string.wifi_available_sign_in_detailed,
458                    mWifiInfo.getSSID());
459
460            Notification notification = new Notification();
461            notification.when = 0;
462            notification.icon = com.android.internal.R.drawable.stat_notify_wifi_in_range;
463            notification.flags = Notification.FLAG_AUTO_CANCEL;
464            notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
465            notification.tickerText = title;
466            notification.setLatestEventInfo(mContext, title, details, notification.contentIntent);
467
468            notificationManager.notify(WALLED_GARDEN_NOTIFICATION_ID, 1, notification);
469        } else {
470            notificationManager.cancel(WALLED_GARDEN_NOTIFICATION_ID, 1);
471        }
472        mWalledGardenNotificationShown = visible;
473    }
474
475    class DefaultState extends State {
476        @Override
477        public void enter() {
478            if (DBG) log(getName() + "\n");
479        }
480
481        @Override
482        public boolean processMessage(Message msg) {
483            switch (msg.what) {
484                case EVENT_WATCHDOG_SETTINGS_CHANGE:
485                    updateSettings();
486                    if (DBG) {
487                        log("Updating wifi-watchdog secure settings");
488                    }
489                    break;
490                case EVENT_RSSI_CHANGE:
491                    mCurrentSignalLevel = calculateSignalLevel(msg.arg1);
492                    break;
493                case EVENT_WIFI_RADIO_STATE_CHANGE:
494                case EVENT_NETWORK_STATE_CHANGE:
495                case CMD_ARP_CHECK:
496                case CMD_DELAYED_WALLED_GARDEN_CHECK:
497                case CMD_RSSI_FETCH:
498                case RSSI_FETCH_SUCCEEDED:
499                case RSSI_FETCH_FAILED:
500                    //ignore
501                    break;
502                default:
503                    log("Unhandled message " + msg + " in state " + getCurrentState().getName());
504                    break;
505            }
506            return HANDLED;
507        }
508    }
509
510    class WatchdogDisabledState extends State {
511        @Override
512        public void enter() {
513            if (DBG) log(getName() + "\n");
514        }
515
516        @Override
517        public boolean processMessage(Message msg) {
518            switch (msg.what) {
519                case EVENT_WATCHDOG_TOGGLED:
520                    if (isWatchdogEnabled())
521                        transitionTo(mNotConnectedState);
522                    return HANDLED;
523                case EVENT_NETWORK_STATE_CHANGE:
524                    Intent intent = (Intent) msg.obj;
525                    NetworkInfo networkInfo = (NetworkInfo)
526                            intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
527
528                    switch (networkInfo.getDetailedState()) {
529                        case VERIFYING_POOR_LINK:
530                            if (DBG) log("Watchdog disabled, verify link");
531                            mWsmChannel.sendMessage(GOOD_LINK_DETECTED);
532                            break;
533                        default:
534                            break;
535                    }
536                    break;
537            }
538            return NOT_HANDLED;
539        }
540    }
541
542    class WatchdogEnabledState extends State {
543        @Override
544        public void enter() {
545            if (DBG) log("WifiWatchdogService enabled");
546        }
547
548        @Override
549        public boolean processMessage(Message msg) {
550            switch (msg.what) {
551                case EVENT_WATCHDOG_TOGGLED:
552                    if (!isWatchdogEnabled())
553                        transitionTo(mWatchdogDisabledState);
554                    break;
555                case EVENT_NETWORK_STATE_CHANGE:
556                    Intent intent = (Intent) msg.obj;
557                    NetworkInfo networkInfo = (NetworkInfo)
558                            intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
559
560                    if (DBG) log("network state change " + networkInfo.getDetailedState());
561
562                    switch (networkInfo.getDetailedState()) {
563                        case VERIFYING_POOR_LINK:
564                            mLinkProperties = (LinkProperties) intent.getParcelableExtra(
565                                    WifiManager.EXTRA_LINK_PROPERTIES);
566                            mWifiInfo = (WifiInfo) intent.getParcelableExtra(
567                                    WifiManager.EXTRA_WIFI_INFO);
568                            if (mPoorNetworkDetectionEnabled) {
569                                if (mWifiInfo == null) {
570                                    log("Ignoring link verification, mWifiInfo is NULL");
571                                    mWsmChannel.sendMessage(GOOD_LINK_DETECTED);
572                                } else {
573                                    transitionTo(mVerifyingLinkState);
574                                }
575                            } else {
576                                mWsmChannel.sendMessage(GOOD_LINK_DETECTED);
577                            }
578                            break;
579                        case CONNECTED:
580                            if (shouldCheckWalledGarden()) {
581                                transitionTo(mWalledGardenCheckState);
582                            } else {
583                                transitionTo(mOnlineWatchState);
584                            }
585                            break;
586                        default:
587                            transitionTo(mNotConnectedState);
588                            break;
589                    }
590                    break;
591                case EVENT_WIFI_RADIO_STATE_CHANGE:
592                    if ((Integer) msg.obj == WifiManager.WIFI_STATE_DISABLING) {
593                        if (DBG) log("WifiStateDisabling -- Resetting WatchdogState");
594                        transitionTo(mNotConnectedState);
595                    }
596                    break;
597                default:
598                    return NOT_HANDLED;
599            }
600
601            setWalledGardenNotificationVisible(false);
602            return HANDLED;
603        }
604
605        @Override
606        public void exit() {
607            if (DBG) log("WifiWatchdogService disabled");
608        }
609    }
610
611    class NotConnectedState extends State {
612        @Override
613        public void enter() {
614            if (DBG) log(getName() + "\n");
615        }
616    }
617
618    class VerifyingLinkState extends State {
619        @Override
620        public void enter() {
621            if (DBG) log(getName() + "\n");
622            //Treat entry as an rssi change
623            handleRssiChange();
624        }
625
626        private void handleRssiChange() {
627            if (mCurrentSignalLevel <= RSSI_LEVEL_MONITOR) {
628                //stay here
629                if (DBG) log("enter VerifyingLinkState, stay level: " + mCurrentSignalLevel);
630            } else {
631                if (DBG) log("enter VerifyingLinkState, arp check level: " + mCurrentSignalLevel);
632                sendMessage(obtainMessage(CMD_ARP_CHECK, ++mArpToken, 0));
633            }
634        }
635
636        @Override
637        public boolean processMessage(Message msg) {
638            switch (msg.what) {
639                case EVENT_WATCHDOG_SETTINGS_CHANGE:
640                    updateSettings();
641                    if (!mPoorNetworkDetectionEnabled) {
642                        mWsmChannel.sendMessage(GOOD_LINK_DETECTED);
643                    }
644                    break;
645                case EVENT_RSSI_CHANGE:
646                    mCurrentSignalLevel = calculateSignalLevel(msg.arg1);
647                    handleRssiChange();
648                    break;
649                case CMD_ARP_CHECK:
650                    if (msg.arg1 == mArpToken) {
651                        if (doArpTest(FULL_ARP_CHECK) == true) {
652                            if (DBG) log("Notify link is good " + mCurrentSignalLevel);
653                            mWsmChannel.sendMessage(GOOD_LINK_DETECTED);
654                        } else {
655                            if (DBG) log("Continue ARP check, rssi level: " + mCurrentSignalLevel);
656                            sendMessageDelayed(obtainMessage(CMD_ARP_CHECK, ++mArpToken, 0),
657                                    mArpCheckIntervalMs);
658                        }
659                    }
660                    break;
661                default:
662                    return NOT_HANDLED;
663            }
664            return HANDLED;
665        }
666    }
667
668    class ConnectedState extends State {
669        @Override
670        public void enter() {
671            if (DBG) log(getName() + "\n");
672        }
673        @Override
674        public boolean processMessage(Message msg) {
675            switch (msg.what) {
676                case EVENT_WATCHDOG_SETTINGS_CHANGE:
677                    updateSettings();
678                    //STOPSHIP: Remove this at ship
679                    DBG = true;
680                    if (DBG) log("Updated secure settings and turned debug on");
681
682                    if (mPoorNetworkDetectionEnabled) {
683                        transitionTo(mOnlineWatchState);
684                    } else {
685                        transitionTo(mOnlineState);
686                    }
687                    return HANDLED;
688            }
689            return NOT_HANDLED;
690        }
691    }
692
693    class WalledGardenCheckState extends State {
694        private int mWalledGardenToken = 0;
695        @Override
696        public void enter() {
697            if (DBG) log(getName() + "\n");
698            sendMessageDelayed(obtainMessage(CMD_DELAYED_WALLED_GARDEN_CHECK,
699                    ++mWalledGardenToken, 0), WALLED_GARDEN_START_DELAY_MS);
700        }
701
702        @Override
703        public boolean processMessage(Message msg) {
704            switch (msg.what) {
705                case CMD_DELAYED_WALLED_GARDEN_CHECK:
706                    if (msg.arg1 == mWalledGardenToken) {
707                        mLastWalledGardenCheckTime = SystemClock.elapsedRealtime();
708                        if (isWalledGardenConnection()) {
709                            if (DBG) log("Walled garden detected");
710                            setWalledGardenNotificationVisible(true);
711                        }
712                        transitionTo(mOnlineWatchState);
713                    }
714                    break;
715                default:
716                    return NOT_HANDLED;
717            }
718            return HANDLED;
719        }
720    }
721
722    class OnlineWatchState extends State {
723        public void enter() {
724            if (DBG) log(getName() + "\n");
725            if (mPoorNetworkDetectionEnabled) {
726                //Treat entry as an rssi change
727                handleRssiChange();
728            } else {
729                transitionTo(mOnlineState);
730            }
731        }
732
733        private void handleRssiChange() {
734            if (mCurrentSignalLevel <= RSSI_LEVEL_CUTOFF) {
735                sendPoorLinkDetected();
736            } else if (mCurrentSignalLevel <= RSSI_LEVEL_MONITOR) {
737                transitionTo(mRssiMonitoringState);
738            } else {
739                //stay here
740            }
741        }
742
743        @Override
744        public boolean processMessage(Message msg) {
745            switch (msg.what) {
746                case EVENT_RSSI_CHANGE:
747                    mCurrentSignalLevel = calculateSignalLevel(msg.arg1);
748                    //Ready to avoid bssid again ?
749                    long time = android.os.SystemClock.elapsedRealtime();
750                    if (time - mLastBssidAvoidedTime  > MIN_INTERVAL_AVOID_BSSID_MS[
751                            mMinIntervalArrayIndex]) {
752                        handleRssiChange();
753                    } else {
754                        if (DBG) log("Early to avoid " + mWifiInfo + " time: " + time +
755                                " last avoided: " + mLastBssidAvoidedTime +
756                                " mMinIntervalArrayIndex: " + mMinIntervalArrayIndex);
757                    }
758                    break;
759                default:
760                    return NOT_HANDLED;
761            }
762            return HANDLED;
763        }
764    }
765
766    class RssiMonitoringState extends State {
767        public void enter() {
768            if (DBG) log(getName() + "\n");
769            sendMessage(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0));
770        }
771
772        public boolean processMessage(Message msg) {
773            switch (msg.what) {
774                case EVENT_RSSI_CHANGE:
775                    mCurrentSignalLevel = calculateSignalLevel(msg.arg1);
776                    if (mCurrentSignalLevel <= RSSI_LEVEL_CUTOFF) {
777                        sendPoorLinkDetected();
778                    } else if (mCurrentSignalLevel <= RSSI_LEVEL_MONITOR) {
779                        //stay here;
780                    } else {
781                        //We dont need frequent RSSI monitoring any more
782                        transitionTo(mOnlineWatchState);
783                    }
784                    break;
785                case CMD_RSSI_FETCH:
786                    if (msg.arg1 == mRssiFetchToken) {
787                        mWsmChannel.sendMessage(RSSI_FETCH);
788                        sendMessageDelayed(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0),
789                                mRssiFetchIntervalMs);
790                    }
791                    break;
792                case RSSI_FETCH_SUCCEEDED:
793                    int rssi = msg.arg1;
794                    if (DBG) log("RSSI_FETCH_SUCCEEDED: " + rssi);
795                    if (msg.arg1 < RSSI_MONITOR_THRESHOLD) {
796                        mRssiMonitorCount++;
797                    } else {
798                        mRssiMonitorCount = 0;
799                    }
800
801                    if (mRssiMonitorCount > RSSI_MONITOR_COUNT) {
802                        sendPoorLinkDetected();
803                        ++mRssiFetchToken;
804                    }
805                    break;
806                case RSSI_FETCH_FAILED:
807                    //can happen if we are waiting to get a disconnect notification
808                    if (DBG) log("RSSI_FETCH_FAILED");
809                    break;
810                default:
811                    return NOT_HANDLED;
812            }
813            return HANDLED;
814        }
815   }
816
817    /* Child state of ConnectedState indicating that we are online
818     * and there is nothing to do
819     */
820    class OnlineState extends State {
821        @Override
822        public void enter() {
823            if (DBG) log(getName() + "\n");
824        }
825    }
826
827    private boolean shouldCheckWalledGarden() {
828        if (!mWalledGardenTestEnabled) {
829            if (DBG) log("Skipping walled garden check - disabled");
830            return false;
831        }
832
833        long waitTime = (mWalledGardenIntervalMs + mLastWalledGardenCheckTime)
834            - SystemClock.elapsedRealtime();
835
836        if (mLastWalledGardenCheckTime != 0 && waitTime > 0) {
837            if (DBG) {
838                log("Skipping walled garden check - wait " +
839                        waitTime + " ms.");
840            }
841            return false;
842        }
843        return true;
844    }
845
846    private boolean doArpTest(int type) {
847        boolean success;
848
849        String iface = mLinkProperties.getInterfaceName();
850        String mac = mWifiInfo.getMacAddress();
851        InetAddress inetAddress = null;
852        InetAddress gateway = null;
853
854        for (LinkAddress la : mLinkProperties.getLinkAddresses()) {
855            inetAddress = la.getAddress();
856            break;
857        }
858
859        for (RouteInfo route : mLinkProperties.getRoutes()) {
860            gateway = route.getGateway();
861            break;
862        }
863
864        if (DBG) log("ARP " + iface + "addr: " + inetAddress + "mac: " + mac + "gw: " + gateway);
865
866        try {
867            ArpPeer peer = new ArpPeer(iface, inetAddress, mac, gateway);
868            if (type == SINGLE_ARP_CHECK) {
869                success = (peer.doArp(mArpPingTimeoutMs) != null);
870                if (DBG) log("single ARP test result: " + success);
871            } else {
872                int responses = 0;
873                for (int i=0; i < mNumArpPings; i++) {
874                    if(peer.doArp(mArpPingTimeoutMs) != null) responses++;
875                }
876                if (DBG) log("full ARP test result: " + responses + "/" + mNumArpPings);
877                success = (responses >= mMinArpResponses);
878            }
879            peer.close();
880        } catch (SocketException se) {
881            //Consider an Arp socket creation issue as a successful Arp
882            //test to avoid any wifi connectivity issues
883            loge("ARP test initiation failure: " + se);
884            success = true;
885        }
886
887        return success;
888    }
889
890    private int calculateSignalLevel(int rssi) {
891        int signalLevel = WifiManager.calculateSignalLevel(rssi,
892                WifiManager.RSSI_LEVELS);
893        if (DBG) log("RSSI current: " + mCurrentSignalLevel + "new: " + rssi + ", " + signalLevel);
894        return signalLevel;
895    }
896
897    private void sendPoorLinkDetected() {
898        if (DBG) log("send POOR_LINK_DETECTED " + mWifiInfo);
899        mWsmChannel.sendMessage(POOR_LINK_DETECTED);
900
901        long time = android.os.SystemClock.elapsedRealtime();
902        if (time - mLastBssidAvoidedTime  > MIN_INTERVAL_AVOID_BSSID_MS[
903                MIN_INTERVAL_AVOID_BSSID_MS.length - 1]) {
904            mMinIntervalArrayIndex = 1;
905            if (DBG) log("set mMinIntervalArrayIndex to 1");
906        } else {
907
908            if (mMinIntervalArrayIndex < MIN_INTERVAL_AVOID_BSSID_MS.length - 1) {
909                mMinIntervalArrayIndex++;
910            }
911            if (DBG) log("mMinIntervalArrayIndex: " + mMinIntervalArrayIndex);
912        }
913
914        mLastBssidAvoidedTime = android.os.SystemClock.elapsedRealtime();
915    }
916
917    /**
918     * Convenience function for retrieving a single secure settings value
919     * as a string with a default value.
920     *
921     * @param cr The ContentResolver to access.
922     * @param name The name of the setting to retrieve.
923     * @param def Value to return if the setting is not defined.
924     *
925     * @return The setting's current value, or 'def' if it is not defined
926     */
927    private static String getSettingsStr(ContentResolver cr, String name, String def) {
928        String v = Settings.Secure.getString(cr, name);
929        return v != null ? v : def;
930    }
931
932    /**
933     * Convenience function for retrieving a single secure settings value
934     * as a boolean.  Note that internally setting values are always
935     * stored as strings; this function converts the string to a boolean
936     * for you.  The default value will be returned if the setting is
937     * not defined or not a valid boolean.
938     *
939     * @param cr The ContentResolver to access.
940     * @param name The name of the setting to retrieve.
941     * @param def Value to return if the setting is not defined.
942     *
943     * @return The setting's current value, or 'def' if it is not defined
944     * or not a valid boolean.
945     */
946    private static boolean getSettingsBoolean(ContentResolver cr, String name, boolean def) {
947        return Settings.Secure.getInt(cr, name, def ? 1 : 0) == 1;
948    }
949
950    /**
951     * Convenience function for updating a single settings value as an
952     * integer. This will either create a new entry in the table if the
953     * given name does not exist, or modify the value of the existing row
954     * with that name.  Note that internally setting values are always
955     * stored as strings, so this function converts the given value to a
956     * string before storing it.
957     *
958     * @param cr The ContentResolver to access.
959     * @param name The name of the setting to modify.
960     * @param value The new value for the setting.
961     * @return true if the value was set, false on database errors
962     */
963    private static boolean putSettingsBoolean(ContentResolver cr, String name, boolean value) {
964        return Settings.Secure.putInt(cr, name, value ? 1 : 0);
965    }
966
967    private static void log(String s) {
968        Log.d(TAG, s);
969    }
970
971    private static void loge(String s) {
972        Log.e(TAG, s);
973    }
974}
975