WifiWatchdogStateMachine.java revision e046975503e7c6ebd78e35afaad88e3fb1ebfb5a
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.ConnectivityManager;
30import android.net.DnsPinger;
31import android.net.NetworkInfo;
32import android.net.Uri;
33import android.os.Message;
34import android.os.SystemClock;
35import android.provider.Settings;
36import android.provider.Settings.Secure;
37import android.util.Slog;
38
39import com.android.internal.util.Protocol;
40import com.android.internal.util.State;
41import com.android.internal.util.StateMachine;
42
43import java.io.BufferedInputStream;
44import java.io.IOException;
45import java.io.InputStream;
46import java.io.PrintWriter;
47import java.net.HttpURLConnection;
48import java.net.URL;
49import java.util.HashSet;
50import java.util.List;
51import java.util.Scanner;
52import java.util.regex.Pattern;
53
54/**
55 * {@link WifiWatchdogStateMachine} monitors the initial connection to a Wi-Fi
56 * network with multiple access points. After the framework successfully
57 * connects to an access point, the watchdog verifies connectivity by 'pinging'
58 * the configured DNS server using {@link DnsPinger}.
59 * <p>
60 * On DNS check failure, the BSSID is blacklisted if it is reasonably likely
61 * that another AP might have internet access; otherwise the SSID is disabled.
62 * <p>
63 * On DNS success, the WatchdogService initiates a walled garden check via an
64 * http get. A browser window is activated if a walled garden is detected.
65 *
66 * @hide
67 */
68public class WifiWatchdogStateMachine extends StateMachine {
69
70
71    private static final boolean VDBG = false;
72    private static final boolean DBG = true;
73    private static final String WWSM_TAG = "WifiWatchdogStateMachine";
74
75    private static final int WIFI_SIGNAL_LEVELS = 4;
76    /**
77     * Low signal is defined as less than or equal to cut off
78     */
79    private static final int LOW_SIGNAL_CUTOFF = 1;
80
81    private static final long DEFAULT_DNS_CHECK_SHORT_INTERVAL_MS = 2 * 60 * 1000;
82    private static final long DEFAULT_DNS_CHECK_LONG_INTERVAL_MS = 10 * 60 * 1000;
83    private static final long DEFAULT_WALLED_GARDEN_INTERVAL_MS = 30 * 60 * 1000;
84
85    private static final int DEFAULT_MAX_SSID_BLACKLISTS = 7;
86    private static final int DEFAULT_NUM_DNS_PINGS = 5;
87    private static final int DEFAULT_MIN_DNS_RESPONSES = 3;
88    private static final long DNS_PING_INTERVAL_MS = 100;
89
90    private static final int DEFAULT_DNS_PING_TIMEOUT_MS = 1500;
91
92    private static final long DEFAULT_BLACKLIST_FOLLOWUP_INTERVAL_MS = 15 * 1000;
93
94    private static final String DEFAULT_WALLED_GARDEN_URL = "http://www.google.com/";
95    private static final String DEFAULT_WALLED_GARDEN_PATTERN = "<title>.*Google.*</title>";
96
97
98    private static final int BASE = Protocol.BASE_WIFI_WATCHDOG;
99
100    /**
101     * Indicates the enable setting of WWS may have changed
102     */
103    private static final int EVENT_WATCHDOG_TOGGLED = BASE + 1;
104
105    /**
106     * Indicates the wifi network state has changed. Passed w/ original intent
107     * which has a non-null networkInfo object
108     */
109    private static final int EVENT_NETWORK_STATE_CHANGE = BASE + 2;
110    /**
111     * Indicates the signal has changed. Passed with arg1
112     * {@link #mNetEventCounter} and arg2 [raw signal strength]
113     */
114    private static final int EVENT_RSSI_CHANGE = BASE + 3;
115    private static final int EVENT_SCAN_RESULTS_AVAILABLE = BASE + 4;
116    private static final int EVENT_WIFI_RADIO_STATE_CHANGE = BASE + 5;
117    private static final int EVENT_WATCHDOG_SETTINGS_CHANGE = BASE + 6;
118
119    private static final int MESSAGE_CHECK_STEP = BASE + 100;
120    private static final int MESSAGE_HANDLE_WALLED_GARDEN = BASE + 101;
121    private static final int MESSAGE_HANDLE_BAD_AP = BASE + 102;
122    /**
123     * arg1 == mOnlineWatchState.checkCount
124     */
125    private static final int MESSAGE_SINGLE_DNS_CHECK = BASE + 103;
126    private static final int MESSAGE_NETWORK_FOLLOWUP = BASE + 104;
127
128    private Context mContext;
129    private ContentResolver mContentResolver;
130    private WifiManager mWifiManager;
131    private DnsPinger mDnsPinger;
132    private IntentFilter mIntentFilter;
133    private BroadcastReceiver mBroadcastReceiver;
134
135    private DefaultState mDefaultState = new DefaultState();
136    private WatchdogDisabledState mWatchdogDisabledState = new WatchdogDisabledState();
137    private WatchdogEnabledState mWatchdogEnabledState = new WatchdogEnabledState();
138    private NotConnectedState mNotConnectedState = new NotConnectedState();
139    private ConnectedState mConnectedState = new ConnectedState();
140    private DnsCheckingState mDnsCheckingState = new DnsCheckingState();
141    private OnlineWatchState mOnlineWatchState = new OnlineWatchState();
142    private DnsCheckFailureState mDnsCheckFailureState = new DnsCheckFailureState();
143    private WalledGardenState mWalledGardenState = new WalledGardenState();
144    private BlacklistedApState mBlacklistedApState = new BlacklistedApState();
145
146    private long mDnsCheckShortIntervalMs;
147    private long mDnsCheckLongIntervalMs;
148    private long mWalledGardenIntervalMs;
149    private int mMaxSsidBlacklists;
150    private int mNumDnsPings;
151    private int mMinDnsResponses;
152    private int mDnsPingTimeoutMs;
153    private long mBlacklistFollowupIntervalMs;
154    private boolean mWalledGardenTestEnabled;
155    private String mWalledGardenUrl;
156    private Pattern mWalledGardenPattern;
157
158    private boolean mShowDisabledNotification;
159    /**
160     * The {@link WifiInfo} object passed to WWSM on network broadcasts
161     */
162    private WifiInfo mInitialConnInfo;
163    private int mNetEventCounter = 0;
164
165    /**
166     * Currently maintained but not used, TODO
167     */
168    private HashSet<String> mBssids = new HashSet<String>();
169    private int mNumCheckFailures = 0;
170
171    private Long mLastWalledGardenCheckTime = null;
172
173    /**
174     * This is set by the blacklisted state and reset when connected to a new AP.
175     * It triggers a disableNetwork call if a DNS check fails.
176     */
177    public boolean mDisableAPNextFailure = false;
178
179    /**
180     * STATE MAP
181     *          Default
182     *         /       \
183     * Disabled     Enabled
184     *             /       \
185     * NotConnected      Connected
186     *                  /---------\
187     *               (all other states)
188     */
189    private WifiWatchdogStateMachine(Context context) {
190        super(WWSM_TAG);
191        mContext = context;
192        mContentResolver = context.getContentResolver();
193        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
194        mDnsPinger = new DnsPinger("WifiWatchdogServer.DnsPinger", context,
195                ConnectivityManager.TYPE_WIFI);
196
197        setupNetworkReceiver();
198
199        // The content observer to listen needs a handler
200        registerForSettingsChanges();
201        registerForWatchdogToggle();
202        addState(mDefaultState);
203            addState(mWatchdogDisabledState, mDefaultState);
204            addState(mWatchdogEnabledState, mDefaultState);
205                addState(mNotConnectedState, mWatchdogEnabledState);
206                addState(mConnectedState, mWatchdogEnabledState);
207                    addState(mDnsCheckingState, mConnectedState);
208                    addState(mDnsCheckFailureState, mConnectedState);
209                    addState(mWalledGardenState, mConnectedState);
210                    addState(mBlacklistedApState, mConnectedState);
211                    addState(mOnlineWatchState, mConnectedState);
212
213        setInitialState(mWatchdogDisabledState);
214        updateSettings();
215        mShowDisabledNotification = getSettingsBoolean(mContentResolver,
216                Settings.Secure.WIFI_WATCHDOG_SHOW_DISABLED_NETWORK_POPUP, true);
217    }
218
219    public static WifiWatchdogStateMachine makeWifiWatchdogStateMachine(Context context) {
220        WifiWatchdogStateMachine wwsm = new WifiWatchdogStateMachine(context);
221        wwsm.start();
222        wwsm.sendMessage(EVENT_WATCHDOG_TOGGLED);
223        return wwsm;
224    }
225
226    /**
227   *
228   */
229    private void setupNetworkReceiver() {
230        mBroadcastReceiver = new BroadcastReceiver() {
231            @Override
232            public void onReceive(Context context, Intent intent) {
233                String action = intent.getAction();
234                if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
235                    sendMessage(EVENT_NETWORK_STATE_CHANGE, intent);
236                } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
237                    obtainMessage(EVENT_RSSI_CHANGE, mNetEventCounter,
238                            intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200)).sendToTarget();
239                } else if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
240                    sendMessage(EVENT_SCAN_RESULTS_AVAILABLE);
241                } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
242                    sendMessage(EVENT_WIFI_RADIO_STATE_CHANGE,
243                            intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
244                                    WifiManager.WIFI_STATE_UNKNOWN));
245                }
246            }
247        };
248
249        mIntentFilter = new IntentFilter();
250        mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
251        mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
252        mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
253        mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
254    }
255
256    /**
257     * Observes the watchdog on/off setting, and takes action when changed.
258     */
259    private void registerForWatchdogToggle() {
260        ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
261            @Override
262            public void onChange(boolean selfChange) {
263                sendMessage(EVENT_WATCHDOG_TOGGLED);
264            }
265        };
266
267        mContext.getContentResolver().registerContentObserver(
268                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON),
269                false, contentObserver);
270    }
271
272    /**
273     * Observes watchdogs secure setting changes.
274     */
275    private void registerForSettingsChanges() {
276        ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
277            @Override
278            public void onChange(boolean selfChange) {
279                sendMessage(EVENT_WATCHDOG_SETTINGS_CHANGE);
280            }
281        };
282
283        mContext.getContentResolver().registerContentObserver(
284                Settings.Secure.getUriFor(
285                        Settings.Secure.WIFI_WATCHDOG_DNS_CHECK_SHORT_INTERVAL_MS),
286                        false, contentObserver);
287        mContext.getContentResolver().registerContentObserver(
288                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_DNS_CHECK_LONG_INTERVAL_MS),
289                false, contentObserver);
290        mContext.getContentResolver().registerContentObserver(
291                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS),
292                false, contentObserver);
293        mContext.getContentResolver().registerContentObserver(
294                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_MAX_SSID_BLACKLISTS),
295                false, contentObserver);
296        mContext.getContentResolver().registerContentObserver(
297                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_NUM_DNS_PINGS),
298                false, contentObserver);
299        mContext.getContentResolver().registerContentObserver(
300                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_MIN_DNS_RESPONSES),
301                false, contentObserver);
302        mContext.getContentResolver().registerContentObserver(
303                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_DNS_PING_TIMEOUT_MS),
304                false, contentObserver);
305        mContext.getContentResolver().registerContentObserver(
306                Settings.Secure.getUriFor(
307                        Settings.Secure.WIFI_WATCHDOG_BLACKLIST_FOLLOWUP_INTERVAL_MS),
308                        false, contentObserver);
309        mContext.getContentResolver().registerContentObserver(
310                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED),
311                false, contentObserver);
312        mContext.getContentResolver().registerContentObserver(
313                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL),
314                false, contentObserver);
315        mContext.getContentResolver().registerContentObserver(
316                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_PATTERN),
317                false, contentObserver);
318    }
319
320    /**
321     * DNS based detection techniques do not work at all hotspots. The one sure
322     * way to check a walled garden is to see if a URL fetch on a known address
323     * fetches the data we expect
324     */
325    private boolean isWalledGardenConnection() {
326        InputStream in = null;
327        HttpURLConnection urlConnection = null;
328        try {
329            URL url = new URL(mWalledGardenUrl);
330            urlConnection = (HttpURLConnection) url.openConnection();
331            in = new BufferedInputStream(urlConnection.getInputStream());
332            Scanner scanner = new Scanner(in);
333            if (scanner.findInLine(mWalledGardenPattern) != null) {
334                return false;
335            } else {
336                return true;
337            }
338        } catch (IOException e) {
339            return false;
340        } finally {
341            if (in != null) {
342                try {
343                    in.close();
344                } catch (IOException e) {
345                }
346            }
347            if (urlConnection != null)
348                urlConnection.disconnect();
349        }
350    }
351
352    private boolean rssiStrengthAboveCutoff(int rssi) {
353        return WifiManager.calculateSignalLevel(rssi, WIFI_SIGNAL_LEVELS) > LOW_SIGNAL_CUTOFF;
354    }
355
356    public void dump(PrintWriter pw) {
357        pw.print("WatchdogStatus: ");
358        pw.print("State " + getCurrentState());
359        pw.println(", network [" + mInitialConnInfo + "]");
360        pw.print("checkFailures   " + mNumCheckFailures);
361        pw.println(", bssids: " + mBssids);
362        pw.println("lastSingleCheck: " + mOnlineWatchState.lastCheckTime);
363    }
364
365    private boolean isWatchdogEnabled() {
366        return getSettingsBoolean(mContentResolver, Settings.Secure.WIFI_WATCHDOG_ON, true);
367    }
368
369    private void updateSettings() {
370        mDnsCheckShortIntervalMs = Secure.getLong(mContentResolver,
371                Secure.WIFI_WATCHDOG_DNS_CHECK_SHORT_INTERVAL_MS,
372                DEFAULT_DNS_CHECK_SHORT_INTERVAL_MS);
373        mDnsCheckLongIntervalMs = Secure.getLong(mContentResolver,
374                Secure.WIFI_WATCHDOG_DNS_CHECK_LONG_INTERVAL_MS,
375                DEFAULT_DNS_CHECK_LONG_INTERVAL_MS);
376        mMaxSsidBlacklists = Secure.getInt(mContentResolver,
377                Secure.WIFI_WATCHDOG_MAX_SSID_BLACKLISTS,
378                DEFAULT_MAX_SSID_BLACKLISTS);
379        mNumDnsPings = Secure.getInt(mContentResolver,
380                Secure.WIFI_WATCHDOG_NUM_DNS_PINGS,
381                DEFAULT_NUM_DNS_PINGS);
382        mMinDnsResponses = Secure.getInt(mContentResolver,
383                Secure.WIFI_WATCHDOG_MIN_DNS_RESPONSES,
384                DEFAULT_MIN_DNS_RESPONSES);
385        mDnsPingTimeoutMs = Secure.getInt(mContentResolver,
386                Secure.WIFI_WATCHDOG_DNS_PING_TIMEOUT_MS,
387                DEFAULT_DNS_PING_TIMEOUT_MS);
388        mBlacklistFollowupIntervalMs = Secure.getLong(mContentResolver,
389                Settings.Secure.WIFI_WATCHDOG_BLACKLIST_FOLLOWUP_INTERVAL_MS,
390                DEFAULT_BLACKLIST_FOLLOWUP_INTERVAL_MS);
391        mWalledGardenTestEnabled = getSettingsBoolean(mContentResolver,
392                Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, true);
393        mWalledGardenUrl = getSettingsStr(mContentResolver,
394                Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL,
395                DEFAULT_WALLED_GARDEN_URL);
396        mWalledGardenPattern = Pattern.compile(getSettingsStr(mContentResolver,
397                Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_PATTERN,
398                DEFAULT_WALLED_GARDEN_PATTERN));
399        mWalledGardenIntervalMs = Secure.getLong(mContentResolver,
400                Secure.WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS,
401                DEFAULT_WALLED_GARDEN_INTERVAL_MS);
402    }
403
404    /**
405     * Helper to return wait time left given a min interval and last run
406     *
407     * @param interval minimum wait interval
408     * @param lastTime last time action was performed in
409     *            SystemClock.elapsedRealtime(). Null if never.
410     * @return non negative time to wait
411     */
412    private static long waitTime(long interval, Long lastTime) {
413        if (lastTime == null)
414            return 0;
415        long wait = interval + lastTime - SystemClock.elapsedRealtime();
416        return wait > 0 ? wait : 0;
417    }
418
419    private static String wifiInfoToStr(WifiInfo wifiInfo) {
420        if (wifiInfo == null)
421            return "null";
422        return "(" + wifiInfo.getSSID() + ", " + wifiInfo.getBSSID() + ")";
423    }
424
425    /**
426   *
427   */
428    private void resetWatchdogState() {
429        mInitialConnInfo = null;
430        mDisableAPNextFailure = false;
431        mLastWalledGardenCheckTime = null;
432        mNumCheckFailures = 0;
433        mBssids.clear();
434    }
435
436    private void popUpBrowser() {
437        Uri uri = Uri.parse("http://www.google.com");
438        Intent intent = new Intent(Intent.ACTION_VIEW, uri);
439        intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
440                Intent.FLAG_ACTIVITY_NEW_TASK);
441        mContext.startActivity(intent);
442    }
443
444    private void displayDisabledNetworkNotification() {
445        Resources r = Resources.getSystem();
446        CharSequence title =
447                r.getText(com.android.internal.R.string.wifi_watchdog_network_disabled);
448        CharSequence msg =
449                r.getText(com.android.internal.R.string.wifi_watchdog_network_disabled_detailed);
450
451        Notification wifiDisabledWarning = new Notification.Builder(mContext)
452            .setSmallIcon(com.android.internal.R.drawable.stat_sys_warning)
453            .setDefaults(Notification.DEFAULT_ALL)
454            .setTicker(title)
455            .setContentTitle(title)
456            .setContentText(msg)
457            .setContentIntent(PendingIntent.getActivity(mContext, 0,
458                    new Intent(Settings.ACTION_WIFI_IP_SETTINGS)
459                        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0))
460            .setWhen(System.currentTimeMillis())
461            .setAutoCancel(true)
462            .getNotification();
463
464        NotificationManager notificationManager = (NotificationManager) mContext
465                .getSystemService(Context.NOTIFICATION_SERVICE);
466
467        notificationManager.notify("WifiWatchdog", wifiDisabledWarning.icon, wifiDisabledWarning);
468    }
469
470    class DefaultState extends State {
471        @Override
472        public boolean processMessage(Message msg) {
473            switch (msg.what) {
474                case EVENT_WATCHDOG_SETTINGS_CHANGE:
475                    updateSettings();
476                    if (VDBG) {
477                        Slog.d(WWSM_TAG, "Updating wifi-watchdog secure settings");
478                    }
479                    return HANDLED;
480            }
481            if (VDBG) {
482                Slog.v(WWSM_TAG, "Caught message " + msg.what + " in state " +
483                        getCurrentState().getName());
484            }
485            return HANDLED;
486        }
487    }
488
489    class WatchdogDisabledState extends State {
490        @Override
491        public boolean processMessage(Message msg) {
492            switch (msg.what) {
493                case EVENT_WATCHDOG_TOGGLED:
494                    if (isWatchdogEnabled())
495                        transitionTo(mNotConnectedState);
496                    return HANDLED;
497            }
498            return NOT_HANDLED;
499        }
500    }
501
502    class WatchdogEnabledState extends State {
503        @Override
504        public void enter() {
505            resetWatchdogState();
506            mContext.registerReceiver(mBroadcastReceiver, mIntentFilter);
507            Slog.i(WWSM_TAG, "WifiWatchdogService enabled");
508        }
509
510        @Override
511        public boolean processMessage(Message msg) {
512            switch (msg.what) {
513                case EVENT_WATCHDOG_TOGGLED:
514                    if (!isWatchdogEnabled())
515                        transitionTo(mWatchdogDisabledState);
516                    return HANDLED;
517                case EVENT_NETWORK_STATE_CHANGE:
518                    Intent stateChangeIntent = (Intent) msg.obj;
519                    NetworkInfo networkInfo = (NetworkInfo)
520                            stateChangeIntent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
521
522                    switch (networkInfo.getState()) {
523                        case CONNECTED:
524                            // WifiInfo wifiInfo = (WifiInfo)
525                            //         stateChangeIntent
526                            //                 .getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
527                            // TODO : Replace with above code when API is changed
528                            WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
529                            if (wifiInfo == null) {
530                                Slog.e(WWSM_TAG, "Connected --> WifiInfo object null!");
531                                return HANDLED;
532                            }
533
534                            if (wifiInfo.getSSID() == null || wifiInfo.getBSSID() == null) {
535                                Slog.e(WWSM_TAG, "Received wifiInfo object with null elts: "
536                                        + wifiInfoToStr(wifiInfo));
537                                return HANDLED;
538                            }
539
540                            initConnection(wifiInfo);
541                            transitionTo(mDnsCheckingState);
542                            mNetEventCounter++;
543                            return HANDLED;
544                        case DISCONNECTED:
545                        case DISCONNECTING:
546                            mNetEventCounter++;
547                            transitionTo(mNotConnectedState);
548                            return HANDLED;
549                    }
550                    return HANDLED;
551                case EVENT_WIFI_RADIO_STATE_CHANGE:
552                    if ((Integer) msg.obj == WifiManager.WIFI_STATE_DISABLING) {
553                        Slog.i(WWSM_TAG, "WifiStateDisabling -- Resetting WatchdogState");
554                        resetWatchdogState();
555                        mNetEventCounter++;
556                        transitionTo(mNotConnectedState);
557                    }
558                    return HANDLED;
559            }
560
561            return NOT_HANDLED;
562        }
563
564        /**
565         * @param wifiInfo Info object with non-null ssid and bssid
566         */
567        private void initConnection(WifiInfo wifiInfo) {
568            if (VDBG) {
569                Slog.v(WWSM_TAG, "Connected:: old " + wifiInfoToStr(mInitialConnInfo) +
570                        " ==> new " + wifiInfoToStr(wifiInfo));
571            }
572
573            if (mInitialConnInfo == null || !wifiInfo.getSSID().equals(mInitialConnInfo.getSSID())) {
574                resetWatchdogState();
575            } else if (!wifiInfo.getBSSID().equals(mInitialConnInfo.getBSSID())) {
576                mDisableAPNextFailure = false;
577            }
578            mInitialConnInfo = wifiInfo;
579        }
580
581        @Override
582        public void exit() {
583            mContext.unregisterReceiver(mBroadcastReceiver);
584            Slog.i(WWSM_TAG, "WifiWatchdogService disabled");
585        }
586    }
587
588    class NotConnectedState extends State {
589    }
590
591    class ConnectedState extends State {
592        @Override
593        public boolean processMessage(Message msg) {
594            switch (msg.what) {
595                case EVENT_SCAN_RESULTS_AVAILABLE:
596                    String curSsid = mInitialConnInfo.getSSID();
597                    List<ScanResult> results = mWifiManager.getScanResults();
598                    int oldNumBssids = mBssids.size();
599
600                    if (results == null) {
601                        if (DBG) {
602                            Slog.d(WWSM_TAG, "updateBssids: Got null scan results!");
603                        }
604                        return HANDLED;
605                    }
606
607                    for (ScanResult result : results) {
608                        if (result == null || result.SSID == null) {
609                            if (VDBG) {
610                                Slog.v(WWSM_TAG, "Received invalid scan result: " + result);
611                            }
612                            continue;
613                        }
614                        if (curSsid.equals(result.SSID))
615                            mBssids.add(result.BSSID);
616                    }
617                    return HANDLED;
618                case EVENT_WATCHDOG_SETTINGS_CHANGE:
619                    // Stop current checks, but let state update
620                    transitionTo(mOnlineWatchState);
621                    return NOT_HANDLED;
622            }
623            return NOT_HANDLED;
624        }
625
626    }
627
628    class DnsCheckingState extends State {
629        int dnsCheckTries = 0;
630        int dnsCheckSuccesses = 0;
631        String dnsCheckLogStr = "";
632
633        @Override
634        public void enter() {
635            dnsCheckSuccesses = 0;
636            dnsCheckTries = 0;
637            if (DBG) {
638                Slog.d(WWSM_TAG, "Starting DNS pings at " + SystemClock.elapsedRealtime());
639                dnsCheckLogStr = String.format("Pinging %s on ssid [%s]: ",
640                        mDnsPinger.getDns(), mInitialConnInfo.getSSID());
641            }
642
643            sendCheckStepMessage(0);
644        }
645
646        @Override
647        public boolean processMessage(Message msg) {
648            if (msg.what != MESSAGE_CHECK_STEP) {
649                return NOT_HANDLED;
650            }
651            if (msg.arg1 != mNetEventCounter) {
652                Slog.d(WWSM_TAG, "Check step out of sync, ignoring...");
653                return HANDLED;
654            }
655
656            long pingResponseTime = mDnsPinger.pingDns(mDnsPinger.getDns(),
657                    mDnsPingTimeoutMs);
658
659            dnsCheckTries++;
660            if (pingResponseTime >= 0)
661                dnsCheckSuccesses++;
662
663            if (DBG) {
664                if (pingResponseTime >= 0) {
665                    dnsCheckLogStr += "|" + pingResponseTime;
666                } else {
667                    dnsCheckLogStr += "|x";
668                }
669            }
670
671            if (VDBG) {
672                Slog.v(WWSM_TAG, dnsCheckLogStr);
673            }
674
675            /**
676             * After a full ping count, if we have more responses than this
677             * cutoff, the outcome is success; else it is 'failure'.
678             */
679
680            /**
681             * Our final success count will be at least this big, so we're
682             * guaranteed to succeed.
683             */
684            if (dnsCheckSuccesses >= mMinDnsResponses) {
685                // DNS CHECKS OK, NOW WALLED GARDEN
686                if (DBG) {
687                    Slog.d(WWSM_TAG, dnsCheckLogStr + "|  SUCCESS");
688                }
689
690                if (!shouldCheckWalledGarden()) {
691                    transitionTo(mOnlineWatchState);
692                    return HANDLED;
693                }
694
695                mLastWalledGardenCheckTime = SystemClock.elapsedRealtime();
696                if (isWalledGardenConnection()) {
697                    if (DBG)
698                        Slog.d(WWSM_TAG,
699                                "Walled garden test complete - walled garden detected");
700                    transitionTo(mWalledGardenState);
701                } else {
702                    if (DBG)
703                        Slog.d(WWSM_TAG, "Walled garden test complete - online");
704                    transitionTo(mOnlineWatchState);
705                }
706                return HANDLED;
707            }
708
709            /**
710             * Our final count will be at most the current count plus the
711             * remaining pings - we're guaranteed to fail.
712             */
713            int remainingChecks = mNumDnsPings - dnsCheckTries;
714            if (remainingChecks + dnsCheckSuccesses < mMinDnsResponses) {
715                if (DBG) {
716                    Slog.d(WWSM_TAG, dnsCheckLogStr + "|  FAILURE");
717                }
718                transitionTo(mDnsCheckFailureState);
719                return HANDLED;
720            }
721
722            // Still in dns check step
723            sendCheckStepMessage(DNS_PING_INTERVAL_MS);
724            return HANDLED;
725        }
726
727        private boolean shouldCheckWalledGarden() {
728            if (!mWalledGardenTestEnabled) {
729                if (VDBG)
730                    Slog.v(WWSM_TAG, "Skipping walled garden check - disabled");
731                return false;
732            }
733            long waitTime = waitTime(mWalledGardenIntervalMs,
734                    mLastWalledGardenCheckTime);
735            if (waitTime > 0) {
736                if (DBG) {
737                    Slog.d(WWSM_TAG, "Skipping walled garden check - wait " +
738                            waitTime + " ms.");
739                }
740                return false;
741            }
742            return true;
743        }
744
745        private void sendCheckStepMessage(long delay) {
746            sendMessageDelayed(obtainMessage(MESSAGE_CHECK_STEP, mNetEventCounter, 0), delay);
747        }
748
749    }
750
751    class OnlineWatchState extends State {
752        /**
753         * Signals a short-wait message is enqueued for the current 'guard' counter
754         */
755        boolean unstableSignalChecks = false;
756
757        /**
758         * The signal is unstable.  We should enqueue a short-wait check, if one is enqueued
759         * already
760         */
761        boolean signalUnstable = false;
762
763        /**
764         * A monotonic counter to ensure that at most one check message will be processed from any
765         * set of check messages currently enqueued.  Avoids duplicate checks when a low-signal
766         * event is observed.
767         */
768        int checkGuard = 0;
769        Long lastCheckTime = null;
770
771        @Override
772        public void enter() {
773            lastCheckTime = SystemClock.elapsedRealtime();
774            signalUnstable = false;
775            checkGuard++;
776            unstableSignalChecks = false;
777            triggerSingleDnsCheck();
778        }
779
780        @Override
781        public boolean processMessage(Message msg) {
782            switch (msg.what) {
783                case EVENT_RSSI_CHANGE:
784                    if (msg.arg1 != mNetEventCounter) {
785                        if (DBG) {
786                            Slog.d(WWSM_TAG, "Rssi change message out of sync, ignoring");
787                        }
788                        return HANDLED;
789                    }
790                    int newRssi = msg.arg2;
791                    signalUnstable = !rssiStrengthAboveCutoff(newRssi);
792                    if (VDBG) {
793                        Slog.v(WWSM_TAG, "OnlineWatchState:: new rssi " + newRssi + " --> level " +
794                                WifiManager.calculateSignalLevel(newRssi, WIFI_SIGNAL_LEVELS));
795                    }
796
797                    if (signalUnstable && !unstableSignalChecks) {
798                        if (VDBG) {
799                            Slog.v(WWSM_TAG, "Sending triggered check msg");
800                        }
801                        triggerSingleDnsCheck();
802                    }
803                    return HANDLED;
804                case MESSAGE_SINGLE_DNS_CHECK:
805                    if (msg.arg1 != checkGuard) {
806                        if (VDBG) {
807                            Slog.v(WWSM_TAG, "Single check msg out of sync, ignoring.");
808                        }
809                        return HANDLED;
810                    }
811                    lastCheckTime = SystemClock.elapsedRealtime();
812                    long responseTime = mDnsPinger.pingDns(mDnsPinger.getDns(),
813                            mDnsPingTimeoutMs);
814                    if (responseTime >= 0) {
815                        if (VDBG) {
816                            Slog.v(WWSM_TAG, "Ran a single DNS ping. Response time: "
817                                    + responseTime);
818                        }
819
820                        checkGuard++;
821                        unstableSignalChecks = false;
822                        triggerSingleDnsCheck();
823                    } else {
824                        if (DBG) {
825                            Slog.d(WWSM_TAG, "Single dns ping failure. Starting full checks.");
826                        }
827                        transitionTo(mDnsCheckingState);
828                    }
829                    return HANDLED;
830            }
831            return NOT_HANDLED;
832        }
833
834        /**
835         * Times a dns check with an interval based on {@link #signalUnstable}
836         */
837        private void triggerSingleDnsCheck() {
838            long waitInterval;
839            if (signalUnstable) {
840                waitInterval = mDnsCheckShortIntervalMs;
841                unstableSignalChecks = true;
842            } else {
843                waitInterval = mDnsCheckLongIntervalMs;
844            }
845            sendMessageDelayed(obtainMessage(MESSAGE_SINGLE_DNS_CHECK, checkGuard, 0),
846                    waitTime(waitInterval, lastCheckTime));
847        }
848    }
849
850    class DnsCheckFailureState extends State {
851        @Override
852        public void enter() {
853            mNumCheckFailures++;
854            obtainMessage(MESSAGE_HANDLE_BAD_AP, mNetEventCounter, 0).sendToTarget();
855        }
856
857        @Override
858        public boolean processMessage(Message msg) {
859            if (msg.what != MESSAGE_HANDLE_BAD_AP) {
860                return NOT_HANDLED;
861            }
862
863            if (msg.arg1 != mNetEventCounter) {
864                if (VDBG) {
865                    Slog.v(WWSM_TAG, "Msg out of sync, ignoring...");
866                }
867                return HANDLED;
868            }
869
870            if (mDisableAPNextFailure || mNumCheckFailures >= mMaxSsidBlacklists) {
871                // TODO : Unban networks if they had low signal ?
872                Slog.i(WWSM_TAG, "Disabling current SSID " + wifiInfoToStr(mInitialConnInfo)
873                        + ".  " + "numCheckFailures " + mNumCheckFailures
874                        + ", numAPs " + mBssids.size());
875                mWifiManager.disableNetwork(mInitialConnInfo.getNetworkId());
876                if (mShowDisabledNotification) {
877                    displayDisabledNetworkNotification();
878                    mShowDisabledNotification = false;
879                    putSettingsBoolean(mContentResolver,
880                            Settings.Secure.WIFI_WATCHDOG_SHOW_DISABLED_NETWORK_POPUP, false);
881                }
882                transitionTo(mNotConnectedState);
883            } else {
884                Slog.i(WWSM_TAG, "Blacklisting current BSSID.  " + wifiInfoToStr(mInitialConnInfo)
885                       + "numCheckFailures " + mNumCheckFailures + ", numAPs " + mBssids.size());
886
887                mWifiManager.addToBlacklist(mInitialConnInfo.getBSSID());
888                mWifiManager.reassociate();
889                transitionTo(mBlacklistedApState);
890            }
891            return HANDLED;
892        }
893    }
894
895    class WalledGardenState extends State {
896        @Override
897        public void enter() {
898            obtainMessage(MESSAGE_HANDLE_WALLED_GARDEN, mNetEventCounter, 0).sendToTarget();
899        }
900
901        @Override
902        public boolean processMessage(Message msg) {
903            if (msg.what != MESSAGE_HANDLE_WALLED_GARDEN) {
904                return NOT_HANDLED;
905            }
906
907            if (msg.arg1 != mNetEventCounter) {
908                if (VDBG) {
909                    Slog.v(WWSM_TAG, "WalledGardenState::Msg out of sync, ignoring...");
910                }
911                return HANDLED;
912            }
913            popUpBrowser();
914            transitionTo(mOnlineWatchState);
915            return HANDLED;
916        }
917    }
918
919    class BlacklistedApState extends State {
920        @Override
921        public void enter() {
922            mDisableAPNextFailure = true;
923            sendMessageDelayed(obtainMessage(MESSAGE_NETWORK_FOLLOWUP, mNetEventCounter, 0),
924                    mBlacklistFollowupIntervalMs);
925        }
926
927        @Override
928        public boolean processMessage(Message msg) {
929            if (msg.what != MESSAGE_NETWORK_FOLLOWUP) {
930                return NOT_HANDLED;
931            }
932
933            if (msg.arg1 != mNetEventCounter) {
934                if (VDBG) {
935                    Slog.v(WWSM_TAG, "BlacklistedApState::Msg out of sync, ignoring...");
936                }
937                return HANDLED;
938            }
939
940            transitionTo(mDnsCheckingState);
941            return HANDLED;
942        }
943    }
944
945
946    /**
947     * Convenience function for retrieving a single secure settings value
948     * as a string with a default value.
949     *
950     * @param cr The ContentResolver to access.
951     * @param name The name of the setting to retrieve.
952     * @param def Value to return if the setting is not defined.
953     *
954     * @return The setting's current value, or 'def' if it is not defined
955     */
956    private static String getSettingsStr(ContentResolver cr, String name, String def) {
957        String v = Settings.Secure.getString(cr, name);
958        return v != null ? v : def;
959    }
960
961    /**
962     * Convenience function for retrieving a single secure settings value
963     * as a boolean.  Note that internally setting values are always
964     * stored as strings; this function converts the string to a boolean
965     * for you.  The default value will be returned if the setting is
966     * not defined or not a valid boolean.
967     *
968     * @param cr The ContentResolver to access.
969     * @param name The name of the setting to retrieve.
970     * @param def Value to return if the setting is not defined.
971     *
972     * @return The setting's current value, or 'def' if it is not defined
973     * or not a valid boolean.
974     */
975    private static boolean getSettingsBoolean(ContentResolver cr, String name, boolean def) {
976        return Settings.Secure.getInt(cr, name, def ? 1 : 0) == 1;
977    }
978
979    /**
980     * Convenience function for updating a single settings value as an
981     * integer. This will either create a new entry in the table if the
982     * given name does not exist, or modify the value of the existing row
983     * with that name.  Note that internally setting values are always
984     * stored as strings, so this function converts the given value to a
985     * string before storing it.
986     *
987     * @param cr The ContentResolver to access.
988     * @param name The name of the setting to modify.
989     * @param value The new value for the setting.
990     * @return true if the value was set, false on database errors
991     */
992    private static boolean putSettingsBoolean(ContentResolver cr, String name, boolean value) {
993        return Settings.Secure.putInt(cr, name, value ? 1 : 0);
994    }
995
996
997}
998