WifiWatchdogService - disable bad connections

Complete rewrite of WifiWatchdogService.java.  Checking for connectivity and managing wifi upon failure detection.

Change-Id: Ifcb8b5d7e0112cbc2f2282d76fdc93ea15527a44
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index e6f443a..f3b5060 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -131,8 +131,6 @@
      */
     private List mNetRequestersPids[];
 
-    private WifiWatchdogService mWifiWatchdogService;
-
     // priority order of the nettrackers
     // (excluding dynamically set mNetworkPreference)
     // TODO - move mNetworkTypePreference into this
@@ -432,10 +430,6 @@
                 wifiService.checkAndStartWifi();
                 mNetTrackers[ConnectivityManager.TYPE_WIFI] = wst;
                 wst.startMonitoring(context, mHandler);
-
-                //TODO: as part of WWS refactor, create only when needed
-                mWifiWatchdogService = new WifiWatchdogService(context);
-
                 break;
             case ConnectivityManager.TYPE_MOBILE:
                 mNetTrackers[netType] = new MobileDataStateTracker(netType,
diff --git a/services/java/com/android/server/DnsPinger.java b/services/java/com/android/server/DnsPinger.java
new file mode 100644
index 0000000..5cfda7e
--- /dev/null
+++ b/services/java/com/android/server/DnsPinger.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.SocketTimeoutException;
+import java.util.Collection;
+import java.util.Random;
+
+/**
+ * Performs a simple DNS "ping" by sending a "server status" query packet to the
+ * DNS server. As long as the server replies, we consider it a success.
+ * <p>
+ * We do not use a simple hostname lookup because that could be cached and the
+ * API may not differentiate between a time out and a failure lookup (which we
+ * really care about).
+ * <p>
+ * TODO : More general API.  Wifi is currently hard coded
+ * TODO : Choice of DNS query location - current looks up www.android.com
+ *
+ * @hide
+ */
+public final class DnsPinger {
+    private static final boolean V = true;
+
+    /** Number of bytes for the query */
+    private static final int DNS_QUERY_BASE_SIZE = 33;
+
+    /** The DNS port */
+    private static final int DNS_PORT = 53;
+
+    /** Used to generate IDs */
+    private static Random sRandom = new Random();
+
+    private ConnectivityManager mConnectivityManager = null;
+    private ContentResolver mContentResolver;
+    private Context mContext;
+
+    private String TAG;
+
+    public DnsPinger(String TAG, Context context) {
+        mContext = context;
+        mContentResolver = context.getContentResolver();
+        this.TAG = TAG;
+    }
+
+    /**
+     * Gets the first DNS of the current Wifi AP.
+     * @return The first DNS of the current AP.
+     */
+    public InetAddress getDns() {
+        if (mConnectivityManager == null) {
+            mConnectivityManager = (ConnectivityManager) mContext.getSystemService(
+                    Context.CONNECTIVITY_SERVICE);
+        }
+
+        LinkProperties linkProperties = mConnectivityManager.getLinkProperties(
+                ConnectivityManager.TYPE_WIFI);
+        if (linkProperties == null)
+            return null;
+
+        Collection<InetAddress> dnses = linkProperties.getDnses();
+        if (dnses == null || dnses.size() == 0)
+            return null;
+
+        return dnses.iterator().next();
+    }
+
+    /**
+     * @return time to response. Negative value on error.
+     */
+    public long pingDns(InetAddress dnsAddress, int timeout) {
+        DatagramSocket socket = null;
+        try {
+            socket = new DatagramSocket();
+
+            // Set some socket properties
+            socket.setSoTimeout(timeout);
+
+            byte[] buf = new byte[DNS_QUERY_BASE_SIZE];
+            fillQuery(buf);
+
+            // Send the DNS query
+
+            DatagramPacket packet = new DatagramPacket(buf,
+                    buf.length, dnsAddress, DNS_PORT);
+            long start = SystemClock.elapsedRealtime();
+            socket.send(packet);
+
+            // Wait for reply (blocks for the above timeout)
+            DatagramPacket replyPacket = new DatagramPacket(buf, buf.length);
+            socket.receive(replyPacket);
+
+            // If a timeout occurred, an exception would have been thrown. We
+            // got a reply!
+            return SystemClock.elapsedRealtime() - start;
+
+        } catch (SocketTimeoutException e) {
+            // Squelch this exception.
+            return -1;
+        } catch (Exception e) {
+            if (V) {
+                Slog.v(TAG, "DnsPinger.pingDns got socket exception: ", e);
+            }
+            return -2;
+        } finally {
+            if (socket != null) {
+                socket.close();
+            }
+        }
+
+    }
+
+    private static void fillQuery(byte[] buf) {
+
+        /*
+         * See RFC2929 (though the bit tables in there are misleading for us.
+         * For example, the recursion desired bit is the 0th bit for us, but
+         * looking there it would appear as the 7th bit of the byte
+         */
+
+        // Make sure it's all zeroed out
+        for (int i = 0; i < buf.length; i++)
+            buf[i] = 0;
+
+        // Form a query for www.android.com
+
+        // [0-1] bytes are an ID, generate random ID for this query
+        buf[0] = (byte) sRandom.nextInt(256);
+        buf[1] = (byte) sRandom.nextInt(256);
+
+        // [2-3] bytes are for flags.
+        buf[2] = 1; // Recursion desired
+
+        // [4-5] bytes are for the query count
+        buf[5] = 1; // One query
+
+        // [6-7] [8-9] [10-11] are all counts of other fields we don't use
+
+        // [12-15] for www
+        writeString(buf, 12, "www");
+
+        // [16-23] for android
+        writeString(buf, 16, "android");
+
+        // [24-27] for com
+        writeString(buf, 24, "com");
+
+        // [29-30] bytes are for QTYPE, set to 1
+        buf[30] = 1;
+
+        // [31-32] bytes are for QCLASS, set to 1
+        buf[32] = 1;
+    }
+
+    private static void writeString(byte[] buf, int startPos, String string) {
+        int pos = startPos;
+
+        // Write the length first
+        buf[pos++] = (byte) string.length();
+        for (int i = 0; i < string.length(); i++) {
+            buf[pos++] = (byte) string.charAt(i);
+        }
+    }
+}
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index cb55451..7725891 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -342,6 +342,7 @@
      * Protected by mWifiStateTracker lock.
      */
     private final WorkSource mTmpWorkSource = new WorkSource();
+    private WifiWatchdogService mWifiWatchdogService;
 
     WifiService(Context context) {
         mContext = context;
@@ -431,6 +432,9 @@
         Slog.i(TAG, "WifiService starting up with Wi-Fi " +
                 (wifiEnabled ? "enabled" : "disabled"));
         setWifiEnabled(wifiEnabled);
+
+        //TODO: as part of WWS refactor, create only when needed
+        mWifiWatchdogService = new WifiWatchdogService(mContext);
     }
 
     private boolean testAndClearWifiSavedState() {
@@ -1155,6 +1159,10 @@
         pw.println();
         pw.println("Locks held:");
         mLocks.dump(pw);
+
+        pw.println();
+        pw.println("WifiWatchdogService dump");
+        mWifiWatchdogService.dump(pw);
     }
 
     private class WifiLock extends DeathRecipient {
diff --git a/services/java/com/android/server/WifiWatchdogService.java b/services/java/com/android/server/WifiWatchdogService.java
index 56bfbe0..0b79478 100644
--- a/services/java/com/android/server/WifiWatchdogService.java
+++ b/services/java/com/android/server/WifiWatchdogService.java
@@ -22,1429 +22,743 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.database.ContentObserver;
-import android.net.ConnectivityManager;
-import android.net.LinkProperties;
 import android.net.NetworkInfo;
+import android.net.Uri;
 import android.net.wifi.ScanResult;
+import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.net.Uri;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
+import android.os.SystemClock;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Slog;
 
 import java.io.BufferedInputStream;
-import java.io.InputStream;
 import java.io.IOException;
-import java.net.DatagramPacket;
-import java.net.DatagramSocket;
+import java.io.InputStream;
+import java.io.PrintWriter;
 import java.net.HttpURLConnection;
-import java.net.InetAddress;
-import java.net.SocketException;
-import java.net.SocketTimeoutException;
-import java.net.UnknownHostException;
 import java.net.URL;
-import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
-import java.util.Random;
 import java.util.Scanner;
 
 /**
  * {@link WifiWatchdogService} monitors the initial connection to a Wi-Fi
  * network with multiple access points. After the framework successfully
- * connects to an access point, the watchdog verifies whether the DNS server is
- * reachable. If not, the watchdog blacklists the current access point, leading
- * to a connection on another access point within the same network.
+ * connects to an access point, the watchdog verifies connectivity by 'pinging'
+ * the configured DNS server using {@link DnsPinger}.
  * <p>
- * The watchdog has a few safeguards:
- * <ul>
- * <li>Only monitor networks with multiple access points
- * <li>Only check at most {@link #getMaxApChecks()} different access points
- * within the network before giving up
+ * On DNS check failure, the BSSID is blacklisted if it is reasonably likely
+ * that another AP might have internet access; otherwise the SSID is disabled.
  * <p>
- * The watchdog checks for connectivity on an access point by ICMP pinging the
- * DNS. There are settings that allow disabling the watchdog, or tweaking the
- * acceptable packet loss (and other various parameters).
- * <p>
- * The core logic of the watchdog is done on the main watchdog thread. Wi-Fi
- * callbacks can come in on other threads, so we must queue messages to the main
- * watchdog thread's handler. Most (if not all) state is only written to from
- * the main thread.
+ * On DNS success, the WatchdogService initiates a walled garden check via an
+ * http get. A browser windows is activated if a walled garden is detected.
  * 
- * {@hide}
+ * @hide
  */
 public class WifiWatchdogService {
-    private static final String TAG = "WifiWatchdogService";
-    private static final boolean V = false;
-    private static final boolean D = true;
+
+    private static final String WWS_TAG = "WifiWatchdogService";
+
+    private static final boolean VDBG = true;
+    private static final boolean DBG = true;
+
+    // Used for verbose logging
+    private String mDNSCheckLogStr;
 
     private Context mContext;
     private ContentResolver mContentResolver;
     private WifiManager mWifiManager;
-    private ConnectivityManager mConnectivityManager;
 
-    /**
-     * The main watchdog thread.
-     */
-    private WifiWatchdogThread mThread;
-    /**
-     * The handler for the main watchdog thread.
-     */
     private WifiWatchdogHandler mHandler;
 
-    private ContentObserver mContentObserver;
+    private DnsPinger mDnsPinger;
+
+    private IntentFilter mIntentFilter;
+    private BroadcastReceiver mBroadcastReceiver;
+    private boolean mBroadcastsEnabled;
+
+    private static final int WIFI_SIGNAL_LEVELS = 4;
 
     /**
-     * The current watchdog state. Only written from the main thread!
+     * Low signal is defined as less than or equal to cut off
      */
-    private WatchdogState mState = WatchdogState.IDLE;
-    /**
-     * The SSID of the network that the watchdog is currently monitoring. Only
-     * touched in the main thread!
-     */
-    private String mSsid;
-    /**
-     * The number of access points in the current network ({@link #mSsid}) that
-     * have been checked. Only touched in the main thread, using getter/setter methods.
-     */
-    private int mBssidCheckCount;
-    /** Whether the current AP check should be canceled. */
-    private boolean mShouldCancel;
+    private static final int LOW_SIGNAL_CUTOFF = 0;
+
+    private static final long MIN_LOW_SIGNAL_CHECK_INTERVAL = 2 * 60 * 1000;
+    private static final long MIN_SINGLE_DNS_CHECK_INTERVAL = 10 * 60 * 1000;
+    private static final long MIN_WALLED_GARDEN_INTERVAL = 15 * 60 * 1000;
+
+    private static final int MAX_CHECKS_PER_SSID = 7;
+    private static final int NUM_DNS_PINGS = 5;
+    private static double MIN_RESPONSE_RATE = 0.50;
+
+    // TODO : Adjust multiple DNS downward to 250 on repeated failure
+    // private static final int MULTI_DNS_PING_TIMEOUT_MS = 250;
+
+    private static final int DNS_PING_TIMEOUT_MS = 1000;
+    private static final long DNS_PING_INTERVAL = 250;
+
+    private static final long BLACKLIST_FOLLOWUP_INTERVAL = 15 * 1000;
+
+    private Status mStatus = new Status();
+
+    private static class Status {
+        String bssid = "";
+        String ssid = "";
+
+        HashSet<String> allBssids = new HashSet<String>();
+        int numFullDNSchecks = 0;
+
+        long lastSingleCheckTime = -24 * 60 * 60 * 1000;
+        long lastWalledGardenCheckTime = -24 * 60 * 60 * 1000;
+
+        WatchdogState state = WatchdogState.INACTIVE;
+
+        // Info for dns check
+        int dnsCheckTries = 0;
+        int dnsCheckSuccesses = 0;
+
+        public int signal = -200;
+
+    }
+
+    private enum WatchdogState {
+        /**
+         * Full DNS check in progress
+         */
+        DNS_FULL_CHECK,
+
+        /**
+         * Walled Garden detected, will pop up browser next round.
+         */
+        WALLED_GARDEN_DETECTED,
+
+        /**
+         * DNS failed, will blacklist/disable AP next round
+         */
+        DNS_CHECK_FAILURE,
+
+        /**
+         * Online or displaying walled garden auth page
+         */
+        CHECKS_COMPLETE,
+
+        /**
+         * Watchdog idle, network has been blacklisted or received disconnect
+         * msg
+         */
+        INACTIVE,
+
+        BLACKLISTED_AP
+    }
 
     WifiWatchdogService(Context context) {
         mContext = context;
         mContentResolver = context.getContentResolver();
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
-        createThread();
-        
-        // The content observer to listen needs a handler, which createThread creates
+        mDnsPinger = new DnsPinger("WifiWatchdogServer.DnsPinger", context);
+
+        HandlerThread handlerThread = new HandlerThread("WifiWatchdogServiceThread");
+        handlerThread.start();
+        mHandler = new WifiWatchdogHandler(handlerThread.getLooper());
+
+        setupNetworkReceiver();
+
+        // The content observer to listen needs a handler, which createThread
+        // creates
         registerForSettingsChanges();
+
+        // Start things off
         if (isWatchdogEnabled()) {
-            registerForWifiBroadcasts();
+            mHandler.sendEmptyMessage(WifiWatchdogHandler.MESSAGE_CONTEXT_EVENT);
         }
-        
-        if (V) {
-            myLogV("WifiWatchdogService: Created");
-        }
+    }
+
+    /**
+     *
+     */
+    private void setupNetworkReceiver() {
+        mBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+                    mHandler.sendMessage(mHandler.obtainMessage(
+                            WifiWatchdogHandler.MESSAGE_NETWORK_EVENT,
+                            intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO)
+                            ));
+                } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
+                    mHandler.sendEmptyMessage(WifiWatchdogHandler.RSSI_CHANGE_EVENT);
+                } else if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+                    mHandler.sendEmptyMessage(WifiWatchdogHandler.SCAN_RESULTS_AVAILABLE);
+                } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+                    mHandler.sendMessage(mHandler.obtainMessage(
+                            WifiWatchdogHandler.WIFI_STATE_CHANGE,
+                            intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 4)));
+                }
+            }
+        };
+
+        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.SCAN_RESULTS_AVAILABLE_ACTION);
     }
 
     /**
      * Observes the watchdog on/off setting, and takes action when changed.
      */
     private void registerForSettingsChanges() {
-        ContentResolver contentResolver = mContext.getContentResolver();
-        contentResolver.registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON), false,
-                mContentObserver = new ContentObserver(mHandler) {
+        ContentObserver contentObserver = new ContentObserver(mHandler) {
             @Override
             public void onChange(boolean selfChange) {
-                if (isWatchdogEnabled()) {
-                    registerForWifiBroadcasts();
-                } else {
-                    unregisterForWifiBroadcasts();
-                    if (mHandler != null) {
-                        mHandler.disableWatchdog();
+                mHandler.sendEmptyMessage((WifiWatchdogHandler.MESSAGE_CONTEXT_EVENT));
+            }
+        };
+
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON),
+                false, contentObserver);
+    }
+
+    private void handleNewConnection() {
+        WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+        String newSsid = wifiInfo.getSSID();
+        String newBssid = wifiInfo.getBSSID();
+
+        if (VDBG) {
+            Slog.v(WWS_TAG, String.format("handleConnected:: old (%s, %s) ==> new (%s, %s)",
+                    mStatus.ssid, mStatus.bssid, newSsid, newBssid));
+        }
+
+        if (TextUtils.isEmpty(newSsid) || TextUtils.isEmpty(newBssid)) {
+            return;
+        }
+
+        if (!TextUtils.equals(mStatus.ssid, newSsid)) {
+            mStatus = new Status();
+            mStatus.ssid = newSsid;
+        }
+
+        mStatus.bssid = newBssid;
+        mStatus.allBssids.add(newBssid);
+        mStatus.signal = WifiManager.calculateSignalLevel(wifiInfo.getRssi(), WIFI_SIGNAL_LEVELS);
+
+        initDnsFullCheck();
+    }
+
+    public void updateRssi() {
+        WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+        if (!TextUtils.equals(mStatus.ssid, wifiInfo.getSSID()) ||
+                !TextUtils.equals(mStatus.bssid, wifiInfo.getBSSID())) {
+            return;
+        }
+
+        mStatus.signal = WifiManager.calculateSignalLevel(wifiInfo.getRssi(), WIFI_SIGNAL_LEVELS);
+    }
+
+    /**
+     * Single step in state machine
+     */
+    private void handleStateStep() {
+        // Slog.v(WWS_TAG, "handleStateStep:: " + mStatus.state);
+
+        switch (mStatus.state) {
+            case DNS_FULL_CHECK:
+                if (VDBG) {
+                    Slog.v(WWS_TAG, "DNS_FULL_CHECK: " + mDNSCheckLogStr);
+                }
+
+                long pingResponseTime = mDnsPinger.pingDns(mDnsPinger.getDns(),
+                        DNS_PING_TIMEOUT_MS);
+
+                mStatus.dnsCheckTries++;
+                if (pingResponseTime >= 0)
+                    mStatus.dnsCheckSuccesses++;
+
+                if (DBG) {
+                    if (pingResponseTime >= 0) {
+                        mDNSCheckLogStr += " | " + pingResponseTime;
+                    } else {
+                        mDNSCheckLogStr += " | " + "x";
                     }
                 }
+
+                switch (currentDnsCheckStatus()) {
+                    case SUCCESS:
+                        if (DBG) {
+                            Slog.d(WWS_TAG, mDNSCheckLogStr + " -- Success");
+                        }
+                        doWalledGardenCheck();
+                        break;
+                    case FAILURE:
+                        if (DBG) {
+                            Slog.d(WWS_TAG, mDNSCheckLogStr + " -- Failure");
+                        }
+                        mStatus.state = WatchdogState.DNS_CHECK_FAILURE;
+                        break;
+                    case INCOMPLETE:
+                        // Taking no action
+                        break;
+                }
+                break;
+            case DNS_CHECK_FAILURE:
+                WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+                if (!mStatus.ssid.equals(wifiInfo.getSSID()) ||
+                        !mStatus.bssid.equals(wifiInfo.getBSSID())) {
+                    Slog.i(WWS_TAG, "handleState DNS_CHECK_FAILURE:: network has changed!");
+                    mStatus.state = WatchdogState.INACTIVE;
+                    break;
+                }
+
+                if (mStatus.numFullDNSchecks >= mStatus.allBssids.size() ||
+                        mStatus.numFullDNSchecks >= MAX_CHECKS_PER_SSID) {
+                    disableAP(wifiInfo);
+                } else {
+                    blacklistAP();
+                }
+                break;
+            case WALLED_GARDEN_DETECTED:
+                popUpBrowser();
+                mStatus.state = WatchdogState.CHECKS_COMPLETE;
+                break;
+            case BLACKLISTED_AP:
+                WifiInfo wifiInfo2 = mWifiManager.getConnectionInfo();
+                if (wifiInfo2.getSupplicantState() != SupplicantState.COMPLETED) {
+                    Slog.d(WWS_TAG,
+                            "handleState::BlacklistedAP - offline, but didn't get disconnect!");
+                    mStatus.state = WatchdogState.INACTIVE;
+                    break;
+                }
+                if (mStatus.bssid.equals(wifiInfo2.getBSSID())) {
+                    Slog.d(WWS_TAG, "handleState::BlacklistedAP - connected to same bssid");
+                    if (!handleSingleDnsCheck()) {
+                        disableAP(wifiInfo2);
+                        break;
+                    }
+                }
+
+                Slog.d(WWS_TAG, "handleState::BlacklistedAP - Simiulating a new connection");
+                handleNewConnection();
+                break;
+        }
+    }
+
+    private void doWalledGardenCheck() {
+        if (!isWalledGardenTestEnabled()) {
+            if (VDBG)
+                Slog.v(WWS_TAG, "Skipping walled garden check - disabled");
+            mStatus.state = WatchdogState.CHECKS_COMPLETE;
+            return;
+        }
+        long waitTime = waitTime(MIN_WALLED_GARDEN_INTERVAL,
+                mStatus.lastWalledGardenCheckTime);
+        if (waitTime > 0) {
+            if (VDBG) {
+                Slog.v(WWS_TAG, "Skipping walled garden check - wait " +
+                        waitTime + " ms.");
             }
-        });
+            mStatus.state = WatchdogState.CHECKS_COMPLETE;
+            return;
+        }
+
+        mStatus.lastWalledGardenCheckTime = SystemClock.elapsedRealtime();
+        if (isWalledGardenConnection()) {
+            if (DBG)
+                Slog.d(WWS_TAG,
+                        "Walled garden test complete - walled garden detected");
+            mStatus.state = WatchdogState.WALLED_GARDEN_DETECTED;
+        } else {
+            if (DBG)
+                Slog.d(WWS_TAG, "Walled garden test complete - online");
+            mStatus.state = WatchdogState.CHECKS_COMPLETE;
+        }
+    }
+
+    private boolean handleSingleDnsCheck() {
+        mStatus.lastSingleCheckTime = SystemClock.elapsedRealtime();
+        long responseTime = mDnsPinger.pingDns(mDnsPinger.getDns(),
+                DNS_PING_TIMEOUT_MS);
+        if (DBG) {
+            Slog.d(WWS_TAG, "Ran a single DNS ping. Response time: " + responseTime);
+        }
+        if (responseTime < 0) {
+            return false;
+        }
+        return true;
+
+    }
+
+    /**
+     * @return Delay in MS before next single DNS check can proceed.
+     */
+    private long timeToNextScheduledDNSCheck() {
+        if (mStatus.signal > LOW_SIGNAL_CUTOFF) {
+            return waitTime(MIN_SINGLE_DNS_CHECK_INTERVAL, mStatus.lastSingleCheckTime);
+        } else {
+            return waitTime(MIN_LOW_SIGNAL_CHECK_INTERVAL, mStatus.lastSingleCheckTime);
+        }
+    }
+
+    /**
+     * Helper to return wait time left given a min interval and last run
+     * 
+     * @param interval minimum wait interval
+     * @param lastTime last time action was performed in
+     *            SystemClock.elapsedRealtime()
+     * @return non negative time to wait
+     */
+    private static long waitTime(long interval, long lastTime) {
+        long wait = interval + lastTime - SystemClock.elapsedRealtime();
+        return wait > 0 ? wait : 0;
+    }
+
+    private void popUpBrowser() {
+        Uri uri = Uri.parse("http://www.google.com");
+        Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+        intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
+                Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+    }
+
+    private void disableAP(WifiInfo info) {
+        // TODO : Unban networks if they had low signal ?
+        Slog.i(WWS_TAG, String.format("Disabling current SSID, %s [bssid %s].  " +
+                "numChecks %d, numAPs %d", mStatus.ssid, mStatus.bssid,
+                mStatus.numFullDNSchecks, mStatus.allBssids.size()));
+        mWifiManager.disableNetwork(info.getNetworkId());
+        mStatus.state = WatchdogState.INACTIVE;
+    }
+
+    private void blacklistAP() {
+        Slog.i(WWS_TAG, String.format("Blacklisting current BSSID %s [ssid %s].  " +
+                "numChecks %d, numAPs %d", mStatus.bssid, mStatus.ssid,
+                mStatus.numFullDNSchecks, mStatus.allBssids.size()));
+
+        mWifiManager.addToBlacklist(mStatus.bssid);
+        mWifiManager.reassociate();
+        mStatus.state = WatchdogState.BLACKLISTED_AP;
+    }
+
+    /**
+     * Checks the scan for new BBIDs using current mSsid
+     */
+    private void updateBssids() {
+        String curSsid = mStatus.ssid;
+        HashSet<String> bssids = mStatus.allBssids;
+        List<ScanResult> results = mWifiManager.getScanResults();
+        int oldNumBssids = bssids.size();
+
+        if (results == null) {
+            if (VDBG) {
+                Slog.v(WWS_TAG, "updateBssids: Got null scan results!");
+            }
+            return;
+        }
+
+        for (ScanResult result : results) {
+            if (result != null && curSsid.equals(result.SSID))
+                bssids.add(result.BSSID);
+        }
+
+        // if (VDBG && bssids.size() - oldNumBssids > 0) {
+        // Slog.v(WWS_TAG,
+        // String.format("updateBssids:: Found %d new APs (total %d) on SSID %s",
+        // bssids.size() - oldNumBssids, bssids.size(), curSsid));
+        // }
+    }
+
+    enum DnsCheckStatus {
+        SUCCESS,
+        FAILURE,
+        INCOMPLETE
+    }
+
+    /**
+     * Computes the current results of the dns check, ends early if outcome is
+     * assured.
+     */
+    private DnsCheckStatus currentDnsCheckStatus() {
+        /**
+         * After a full ping count, if we have more responses than this cutoff,
+         * the outcome is success; else it is 'failure'.
+         */
+        double pingResponseCutoff = MIN_RESPONSE_RATE * NUM_DNS_PINGS;
+        int remainingChecks = NUM_DNS_PINGS - mStatus.dnsCheckTries;
+
+        /**
+         * Our final success count will be at least this big, so we're
+         * guaranteed to succeed.
+         */
+        if (mStatus.dnsCheckSuccesses >= pingResponseCutoff) {
+            return DnsCheckStatus.SUCCESS;
+        }
+
+        /**
+         * Our final count will be at most the current count plus the remaining
+         * pings - we're guaranteed to fail.
+         */
+        if (remainingChecks + mStatus.dnsCheckSuccesses < pingResponseCutoff) {
+            return DnsCheckStatus.FAILURE;
+        }
+
+        return DnsCheckStatus.INCOMPLETE;
+    }
+
+    private void initDnsFullCheck() {
+        if (DBG) {
+            Slog.d(WWS_TAG, "Starting DNS pings at " + SystemClock.elapsedRealtime());
+        }
+        mStatus.numFullDNSchecks++;
+        mStatus.dnsCheckSuccesses = 0;
+        mStatus.dnsCheckTries = 0;
+        mStatus.state = WatchdogState.DNS_FULL_CHECK;
+
+        if (DBG) {
+            mDNSCheckLogStr = String.format("Dns Check %d.  Pinging %s on ssid [%s]: ",
+                    mStatus.numFullDNSchecks, mDnsPinger.getDns().getHostAddress(),
+                    mStatus.ssid);
+        }
+    }
+
+    /**
+     * DNS based detection techniques do not work at all hotspots. The one sure
+     * way to check a walled garden is to see if a URL fetch on a known address
+     * fetches the data we expect
+     */
+    private boolean isWalledGardenConnection() {
+        InputStream in = null;
+        HttpURLConnection urlConnection = null;
+        try {
+            URL url = new URL(getWalledGardenUrl());
+            urlConnection = (HttpURLConnection) url.openConnection();
+            in = new BufferedInputStream(urlConnection.getInputStream());
+            Scanner scanner = new Scanner(in);
+            if (scanner.findInLine(getWalledGardenPattern()) != null) {
+                return false;
+            } else {
+                return true;
+            }
+        } catch (IOException e) {
+            return false;
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                }
+            }
+            if (urlConnection != null)
+                urlConnection.disconnect();
+        }
+    }
+
+    /**
+     * There is little logic inside this class, instead methods of the form
+     * "handle___" are called in the main {@link WifiWatchdogService}.
+     */
+    private class WifiWatchdogHandler extends Handler {
+        /**
+         * Major network event, object is NetworkInfo
+         */
+        static final int MESSAGE_NETWORK_EVENT = 1;
+        /**
+         * Change in settings, no object
+         */
+        static final int MESSAGE_CONTEXT_EVENT = 2;
+
+        /**
+         * Change in signal strength
+         */
+        static final int RSSI_CHANGE_EVENT = 3;
+        static final int SCAN_RESULTS_AVAILABLE = 4;
+
+        static final int WIFI_STATE_CHANGE = 5;
+
+        /**
+         * Single step of state machine. One DNS check, or one WalledGarden
+         * check, or one external action. We separate out external actions to
+         * increase chance of detecting that a check failure is caused by change
+         * in network status. Messages should have an arg1 which to sync status
+         * messages.
+         */
+        static final int CHECK_SEQUENCE_STEP = 10;
+        static final int SINGLE_DNS_CHECK = 11;
+
+        /**
+         * @param looper
+         */
+        public WifiWatchdogHandler(Looper looper) {
+            super(looper);
+        }
+
+        boolean singleCheckQueued = false;
+        long queuedSingleDnsCheckArrival;
+
+        /**
+         * Sends a singleDnsCheck message with shortest time - guards against
+         * multiple.
+         */
+        private boolean queueSingleDnsCheck() {
+            long delay = timeToNextScheduledDNSCheck();
+            long newArrival = delay + SystemClock.elapsedRealtime();
+            if (singleCheckQueued && queuedSingleDnsCheckArrival <= newArrival)
+                return true;
+            queuedSingleDnsCheckArrival = newArrival;
+            singleCheckQueued = true;
+            removeMessages(SINGLE_DNS_CHECK);
+            return sendMessageDelayed(obtainMessage(SINGLE_DNS_CHECK), delay);
+        }
+
+        boolean checkSequenceQueued = false;
+        long queuedCheckSequenceArrival;
+
+        /**
+         * Sends a state_machine_step message if the delay requested is lower
+         * than the current delay.
+         */
+        private boolean sendCheckSequenceStep(long delay) {
+            long newArrival = delay + SystemClock.elapsedRealtime();
+            if (checkSequenceQueued && queuedCheckSequenceArrival <= newArrival)
+                return true;
+            queuedCheckSequenceArrival = newArrival;
+            checkSequenceQueued = true;
+            removeMessages(CHECK_SEQUENCE_STEP);
+            return sendMessageDelayed(obtainMessage(CHECK_SEQUENCE_STEP), delay);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case CHECK_SEQUENCE_STEP:
+                    checkSequenceQueued = false;
+                    handleStateStep();
+                    if (mStatus.state == WatchdogState.CHECKS_COMPLETE) {
+                        queueSingleDnsCheck();
+                    } else if (mStatus.state == WatchdogState.DNS_FULL_CHECK) {
+                        sendCheckSequenceStep(DNS_PING_INTERVAL);
+                    } else if (mStatus.state == WatchdogState.BLACKLISTED_AP) {
+                        sendCheckSequenceStep(BLACKLIST_FOLLOWUP_INTERVAL);
+                    } else if (mStatus.state != WatchdogState.INACTIVE) {
+                        sendCheckSequenceStep(0);
+                    }
+                    return;
+                case MESSAGE_NETWORK_EVENT:
+                    if (!mBroadcastsEnabled) {
+                        Slog.e(WWS_TAG,
+                                "MessageNetworkEvent - WatchdogService not enabled... returning");
+                        return;
+                    }
+                    NetworkInfo info = (NetworkInfo) msg.obj;
+                    switch (info.getState()) {
+                        case DISCONNECTED:
+                            mStatus.state = WatchdogState.INACTIVE;
+                            return;
+                        case CONNECTED:
+                            handleNewConnection();
+                            sendCheckSequenceStep(0);
+                    }
+                    return;
+                case SINGLE_DNS_CHECK:
+                    singleCheckQueued = false;
+                    if (mStatus.state != WatchdogState.CHECKS_COMPLETE) {
+                        Slog.d(WWS_TAG, "Single check returning, curState: " + mStatus.state);
+                        break;
+                    }
+
+                    if (!handleSingleDnsCheck()) {
+                        initDnsFullCheck();
+                        sendCheckSequenceStep(0);
+                    } else {
+                        queueSingleDnsCheck();
+                    }
+
+                    break;
+                case RSSI_CHANGE_EVENT:
+                    updateRssi();
+                    if (mStatus.state == WatchdogState.CHECKS_COMPLETE)
+                        queueSingleDnsCheck();
+                    break;
+                case SCAN_RESULTS_AVAILABLE:
+                    updateBssids();
+                    break;
+                case WIFI_STATE_CHANGE:
+                    if ((Integer) msg.obj == WifiManager.WIFI_STATE_DISABLING) {
+                        Slog.i(WWS_TAG, "WifiStateDisabling -- Resetting WatchdogState");
+                        mStatus = new Status();
+                    }
+                    break;
+                case MESSAGE_CONTEXT_EVENT:
+                    if (isWatchdogEnabled() && !mBroadcastsEnabled) {
+                        mContext.registerReceiver(mBroadcastReceiver, mIntentFilter);
+                        mBroadcastsEnabled = true;
+                        Slog.i(WWS_TAG, "WifiWatchdogService enabled");
+                    } else if (!isWatchdogEnabled() && mBroadcastsEnabled) {
+                        mContext.unregisterReceiver(mBroadcastReceiver);
+                        removeMessages(SINGLE_DNS_CHECK);
+                        removeMessages(CHECK_SEQUENCE_STEP);
+                        mBroadcastsEnabled = false;
+                        Slog.i(WWS_TAG, "WifiWatchdogService disabled");
+                    }
+                    break;
+            }
+        }
+    }
+
+    public void dump(PrintWriter pw) {
+        pw.print("WatchdogStatus: ");
+        pw.print("State " + mStatus.state);
+        pw.println(", network [" + mStatus.ssid + ", " + mStatus.bssid + "]");
+        pw.print("checkCount " + mStatus.numFullDNSchecks);
+        pw.print(", bssids: " + mStatus.allBssids.size());
+        pw.print(", hasCheckMessages? " +
+                mHandler.hasMessages(WifiWatchdogHandler.CHECK_SEQUENCE_STEP));
+        pw.println(" hasSingleCheckMessages? " +
+                mHandler.hasMessages(WifiWatchdogHandler.SINGLE_DNS_CHECK));
+    }
+
+    /**
+     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED
+     */
+    private Boolean isWalledGardenTestEnabled() {
+        return Settings.Secure.getInt(mContentResolver,
+                Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, 1) == 1;
+    }
+
+    /**
+     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_URL
+     */
+    private String getWalledGardenUrl() {
+        String url = Settings.Secure.getString(mContentResolver,
+                Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL);
+        if (TextUtils.isEmpty(url))
+            return "http://www.google.com/";
+        return url;
+    }
+
+    /**
+     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_PATTERN
+     */
+    private String getWalledGardenPattern() {
+        String pattern = Settings.Secure.getString(mContentResolver,
+                Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_PATTERN);
+        if (TextUtils.isEmpty(pattern))
+            return "<title>.*Google.*</title>";
+        return pattern;
     }
 
     /**
      * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ON
      */
     private boolean isWatchdogEnabled() {
-        return Settings.Secure.getInt(mContentResolver, Settings.Secure.WIFI_WATCHDOG_ON, 1) == 1;
-    }
-    
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_AP_COUNT
-     */
-    private int getApCount() {
         return Settings.Secure.getInt(mContentResolver,
-            Settings.Secure.WIFI_WATCHDOG_AP_COUNT, 2);
-    }
-    
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT
-     */
-    private int getInitialIgnoredPingCount() {
-        return Settings.Secure.getInt(mContentResolver,
-            Settings.Secure.WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT , 2);
-    }
-
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_COUNT
-     */
-    private int getPingCount() {
-        return Settings.Secure.getInt(mContentResolver,
-            Settings.Secure.WIFI_WATCHDOG_PING_COUNT, 4);
-    }
-    
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_TIMEOUT_MS
-     */
-    private int getPingTimeoutMs() {
-        return Settings.Secure.getInt(mContentResolver,
-            Settings.Secure.WIFI_WATCHDOG_PING_TIMEOUT_MS, 500);
-    }
-    
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_DELAY_MS
-     */
-    private int getPingDelayMs() {
-        return Settings.Secure.getInt(mContentResolver,
-            Settings.Secure.WIFI_WATCHDOG_PING_DELAY_MS, 250);
-    }
-
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED
-     */
-     private Boolean isWalledGardenTestEnabled() {
-        return Settings.Secure.getInt(mContentResolver,
-                 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, 1) == 1;
-     }
-
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_URL
-     */
-     private String getWalledGardenUrl() {
-        String url = Settings.Secure.getString(mContentResolver,
-                 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL);
-        if (TextUtils.isEmpty(url)) return "http://www.google.com/";
-        return url;
-     }
-
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_PATTERN
-     */
-     private String getWalledGardenPattern() {
-        String pattern = Settings.Secure.getString(mContentResolver,
-                 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_PATTERN);
-        if (TextUtils.isEmpty(pattern)) return "<title>.*Google.*</title>";
-        return pattern;
-     }
-    
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE
-     */
-    private int getAcceptablePacketLossPercentage() {
-        return Settings.Secure.getInt(mContentResolver,
-            Settings.Secure.WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE, 25);
-    }
-    
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_MAX_AP_CHECKS
-     */
-    private int getMaxApChecks() {
-        return Settings.Secure.getInt(mContentResolver,
-            Settings.Secure.WIFI_WATCHDOG_MAX_AP_CHECKS, 7);
-    }
-    
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED
-     */
-    private boolean isBackgroundCheckEnabled() {
-        return Settings.Secure.getInt(mContentResolver,
-            Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED, 1) == 1;
-    }
-    
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS
-     */
-    private int getBackgroundCheckDelayMs() {
-        return Settings.Secure.getInt(mContentResolver,
-            Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS, 60000);
-    }
-    
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS
-     */
-    private int getBackgroundCheckTimeoutMs() {
-        return Settings.Secure.getInt(mContentResolver,
-            Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS, 1000);
-    }
-
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WATCH_LIST
-     * @return the comma-separated list of SSIDs
-     */
-    private String getWatchList() {
-        return Settings.Secure.getString(mContentResolver,
-                Settings.Secure.WIFI_WATCHDOG_WATCH_LIST);
-    }
-    
-    /**
-     * Registers to receive the necessary Wi-Fi broadcasts.
-     */
-    private void registerForWifiBroadcasts() {
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
-        intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
-        mContext.registerReceiver(mReceiver, intentFilter);
-    }
-
-    /**
-     * Unregisters from receiving the Wi-Fi broadcasts.
-     */
-    private void unregisterForWifiBroadcasts() {
-        mContext.unregisterReceiver(mReceiver);
-    }
-
-    /**
-     * Creates the main watchdog thread, including waiting for the handler to be
-     * created.
-     */
-    private void createThread() {
-        mThread = new WifiWatchdogThread();
-        mThread.start();
-        waitForHandlerCreation();
-    }
-
-    /**
-     * Unregister broadcasts and quit the watchdog thread
-     */
-    //TODO: Change back to running WWS when needed
-//    private void quit() {
-//        unregisterForWifiBroadcasts();
-//        mContext.getContentResolver().unregisterContentObserver(mContentObserver);
-//        mHandler.removeAllActions();
-//        mHandler.getLooper().quit();
-//    }
-
-    /**
-     * Waits for the main watchdog thread to create the handler.
-     */
-    private void waitForHandlerCreation() {
-        synchronized(this) {
-            while (mHandler == null) {
-                try {
-                    // Wait for the handler to be set by the other thread
-                    wait();
-                } catch (InterruptedException e) {
-                    Slog.e(TAG, "Interrupted while waiting on handler.");
-                }
-            }
-        }
-    }
-
-    // Utility methods
-    
-    /**
-     * Logs with the current thread.
-     */
-    private static void myLogV(String message) {
-        Slog.v(TAG, "(" + Thread.currentThread().getName() + ") " + message);
-    }
-    
-    private static void myLogD(String message) {
-        Slog.d(TAG, "(" + Thread.currentThread().getName() + ") " + message);
-    }
-    
-    /**
-     * Gets the first DNS of the current AP.
-     * 
-     * @return The first DNS of the current AP.
-     */
-    private InetAddress getDns() {
-        if (mConnectivityManager == null) {
-            mConnectivityManager = (ConnectivityManager)mContext.getSystemService(
-                    Context.CONNECTIVITY_SERVICE);
-        }
-
-        LinkProperties linkProperties = mConnectivityManager.getLinkProperties(
-                ConnectivityManager.TYPE_WIFI);
-        if (linkProperties == null) return null;
-
-        Collection<InetAddress> dnses = linkProperties.getDnses();
-        if (dnses == null || dnses.size() == 0) return null;
-
-        return dnses.iterator().next();
-    }
-
-    /**
-     * Checks whether the DNS can be reached using multiple attempts according
-     * to the current setting values.
-     * 
-     * @return Whether the DNS is reachable
-     */
-    private boolean checkDnsConnectivity() {
-        InetAddress dns = getDns();
-        if (dns == null) {
-            if (V) {
-                myLogV("checkDnsConnectivity: Invalid DNS, returning false");
-            }
-            return false;
-        }
-
-        if (V) {
-            myLogV("checkDnsConnectivity: Checking " + dns.getHostAddress() + " for connectivity");
-        }
-
-        int numInitialIgnoredPings = getInitialIgnoredPingCount();
-        int numPings = getPingCount();
-        int pingDelay = getPingDelayMs();
-        int acceptableLoss = getAcceptablePacketLossPercentage();
-
-        /** See {@link Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT} */
-        int ignoredPingCounter = 0;
-        int pingCounter = 0;
-        int successCounter = 0;
-
-        // No connectivity check needed
-        if (numPings == 0) {
-            return true;
-        }
-
-        // Do the initial pings that we ignore
-        for (; ignoredPingCounter < numInitialIgnoredPings; ignoredPingCounter++) {
-            if (shouldCancel()) return false;
-
-            boolean dnsAlive = DnsPinger.isDnsReachable(dns, getPingTimeoutMs());
-            if (dnsAlive) {
-                /*
-                 * Successful "ignored" pings are *not* ignored (they count in the total number
-                 * of pings), but failures are really ignored.
-                 */
-
-                // TODO: This is confusing logic and should be rewitten
-                // Here, successful 'ignored' pings are interpreted as a success in the below loop
-                pingCounter++;
-                successCounter++;
-            }
-
-            if (V) {
-                Slog.v(TAG, (dnsAlive ? "  +" : "  Ignored: -"));
-            }
-
-            if (shouldCancel()) return false;
-
-            try {
-                Thread.sleep(pingDelay);
-            } catch (InterruptedException e) {
-                Slog.w(TAG, "Interrupted while pausing between pings", e);
-            }
-        }
-
-        // Do the pings that we use to measure packet loss
-        for (; pingCounter < numPings; pingCounter++) {
-            if (shouldCancel()) return false;
-
-            if (DnsPinger.isDnsReachable(dns, getPingTimeoutMs())) {
-                successCounter++;
-                if (V) {
-                    Slog.v(TAG, "  +");
-                }
-            } else {
-                if (V) {
-                    Slog.v(TAG, "  -");
-                }
-            }
-
-            if (shouldCancel()) return false;
-
-            try {
-                Thread.sleep(pingDelay);
-            } catch (InterruptedException e) {
-                Slog.w(TAG, "Interrupted while pausing between pings", e);
-            }
-        }
-
-        //TODO: Integer division might cause problems down the road...
-        int packetLossPercentage = 100 * (numPings - successCounter) / numPings;
-        if (D) {
-            Slog.d(TAG, packetLossPercentage
-                    + "% packet loss (acceptable is " + acceptableLoss + "%)");
-        }
-
-        return !shouldCancel() && (packetLossPercentage <= acceptableLoss);
-    }
-
-    private boolean backgroundCheckDnsConnectivity() {
-        InetAddress dns = getDns();
-
-        if (dns == null) {
-            if (V) {
-                myLogV("backgroundCheckDnsConnectivity: DNS is empty, returning false");
-            }
-            return false;
-        }
-
-        if (V) {
-            myLogV("backgroundCheckDnsConnectivity: Background checking " +
-                    dns.getHostAddress() + " for connectivity");
-        }
-
-        return DnsPinger.isDnsReachable(dns, getBackgroundCheckTimeoutMs());
-    }
-
-    /**
-     * Signals the current action to cancel.
-     */
-    private void cancelCurrentAction() {
-        mShouldCancel = true;
-    }
-    
-    /**
-     * Helper to check whether to cancel. 
-     * 
-     * @return Whether to cancel processing the action.
-     */
-    private boolean shouldCancel() {
-        if (V && mShouldCancel) {
-            myLogV("shouldCancel: Cancelling");
-        }
-        
-        return mShouldCancel;
-    }
-    
-    // Wi-Fi initiated callbacks (could be executed in another thread)
-
-    /**
-     * Called when connected to an AP (this can be the next AP in line, or
-     * it can be a completely different network).
-     * 
-     * @param ssid The SSID of the access point.
-     * @param bssid The BSSID of the access point.
-     */
-    private void onConnected(String ssid, String bssid) {
-        if (V) {
-            myLogV("onConnected: SSID: " + ssid + ", BSSID: " + bssid);
-        }
-
-        /*
-         * The current action being processed by the main watchdog thread is now
-         * stale, so cancel it.
-         */
-        cancelCurrentAction();
-        
-        if ((mSsid == null) || !mSsid.equals(ssid)) {
-            /*
-             * This is a different network than what the main watchdog thread is
-             * processing, dispatch the network change message on the main thread.
-             */
-            mHandler.dispatchNetworkChanged(ssid);
-        }
-        
-        if (requiresWatchdog(ssid, bssid)) {
-            if (D) {
-                myLogD(ssid + " (" + bssid + ") requires the watchdog");
-            }
-
-            // This access point requires a watchdog, so queue the check on the main thread
-            mHandler.checkAp(new AccessPoint(ssid, bssid));
-            
-        } else {
-            if (D) {
-                myLogD(ssid + " (" + bssid + ") does not require the watchdog");
-            }
-
-            // This access point does not require a watchdog, so queue idle on the main thread
-            mHandler.idle();
-        }
-        if (isWalledGardenTestEnabled()) mHandler.checkWalledGarden(ssid);
-    }
-    
-    /**
-     * Called when Wi-Fi is enabled.
-     */
-    private void onEnabled() {
-        cancelCurrentAction();
-        // Queue a hard-reset of the state on the main thread
-        mHandler.reset();
-    }
-    
-    /**
-     * Called when disconnected (or some other event similar to being disconnected).
-     */
-    private void onDisconnected() {
-        if (V) {
-            myLogV("onDisconnected");
-        }
-        
-        /*
-         * Disconnected from an access point, the action being processed by the
-         * watchdog thread is now stale, so cancel it.
-         */
-        cancelCurrentAction();
-        // Dispatch the disconnected to the main watchdog thread
-        mHandler.dispatchDisconnected();
-        // Queue the action to go idle
-        mHandler.idle();
-    }
-
-    /**
-     * Checks whether an access point requires watchdog monitoring.
-     * 
-     * @param ssid The SSID of the access point.
-     * @param bssid The BSSID of the access point.
-     * @return Whether the access point/network should be monitored by the
-     *         watchdog.
-     */
-    private boolean requiresWatchdog(String ssid, String bssid) {
-        if (V) {
-            myLogV("requiresWatchdog: SSID: " + ssid + ", BSSID: " + bssid);
-        }
-        
-        WifiInfo info = null;
-        if (ssid == null) {
-            /*
-             * This is called from a Wi-Fi callback, so assume the WifiInfo does
-             * not have stale data.
-             */
-            info = mWifiManager.getConnectionInfo();
-            ssid = info.getSSID();
-            if (ssid == null) {
-                // It's still null, give up
-                if (V) {
-                    Slog.v(TAG, "  Invalid SSID, returning false");
-                }
-                return false;
-            }
-        }
-        
-        if (TextUtils.isEmpty(bssid)) {
-            // Similar as above
-            if (info == null) {
-                info = mWifiManager.getConnectionInfo();
-            }
-            bssid = info.getBSSID();
-            if (TextUtils.isEmpty(bssid)) {
-                // It's still null, give up
-                if (V) {
-                    Slog.v(TAG, "  Invalid BSSID, returning false");
-                }
-                return false;
-            }
-        }
-
-        if (!isOnWatchList(ssid)) {
-            if (V) {
-                Slog.v(TAG, "  SSID not on watch list, returning false");
-            }
-            return false;
-        }
-
-        // The watchdog only monitors networks with multiple APs
-        if (!hasRequiredNumberOfAps(ssid)) {
-            return false;
-        }
-
-        return true;
-    }
-
-    private boolean isOnWatchList(String ssid) {
-        String watchList;
-
-        if (ssid == null || (watchList = getWatchList()) == null) {
-            return false;
-        }
-
-        String[] list = watchList.split(" *, *");
-
-        for (String name : list) {
-            if (ssid.equals(name)) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-    
-    /**
-     * Checks if the current scan results have multiple access points with an SSID.
-     * 
-     * @param ssid The SSID to check.
-     * @return Whether the SSID has multiple access points.
-     */
-    private boolean hasRequiredNumberOfAps(String ssid) {
-        List<ScanResult> results = mWifiManager.getScanResults();
-        if (results == null) {
-            if (V) {
-                myLogV("hasRequiredNumberOfAps: Got null scan results, returning false");
-            }
-            return false;
-        }
-        
-        int numApsRequired = getApCount();
-        int numApsFound = 0;
-        int resultsSize = results.size();
-        for (int i = 0; i < resultsSize; i++) {
-            ScanResult result = results.get(i);
-            if (result == null) continue;
-            if (result.SSID == null) continue;
-            
-            if (result.SSID.equals(ssid)) {
-                numApsFound++;
-                
-                if (numApsFound >= numApsRequired) {
-                    if (V) {
-                        myLogV("hasRequiredNumberOfAps: SSID: " + ssid + ", returning true");
-                    }
-                    return true;
-                }
-            }
-        }
-        
-        if (V) {
-            myLogV("hasRequiredNumberOfAps: SSID: " + ssid + ", returning false");
-        }
-        return false;
-    }
-    
-    // Watchdog logic (assume all of these methods will be in our main thread)
-    
-    /**
-     * Handles a Wi-Fi network change (for example, from networkA to networkB).
-     */
-    private void handleNetworkChanged(String ssid) {
-        // Set the SSID being monitored to the new SSID 
-        mSsid = ssid;
-        // Set various state to that when being idle 
-        setIdleState(true);
-    }
-    
-    /**
-     * Handles checking whether an AP is a "good" AP.  If not, it will be blacklisted.
-     * 
-     * @param ap The access point to check.
-     */
-    private void handleCheckAp(AccessPoint ap) {
-        // Reset the cancel state since this is the entry point of this action
-        mShouldCancel = false;
-        
-        if (V) {
-            myLogV("handleCheckAp: AccessPoint: " + ap);
-        }
-        
-        // Make sure we are not sleeping
-        if (mState == WatchdogState.SLEEP) {
-            if (V) {
-                Slog.v(TAG, "  Sleeping (in " + mSsid + "), so returning");
-            }
-            return;
-        }
-        
-        mState = WatchdogState.CHECKING_AP;
-        
-        /*
-         * Checks to make sure we haven't exceeded the max number of checks
-         * we're allowed per network
-         */
-        incrementBssidCheckCount();
-        if (getBssidCheckCount() > getMaxApChecks()) {
-            if (V) {
-                Slog.v(TAG, "  Passed the max attempts (" + getMaxApChecks()
-                        + "), going to sleep for " + mSsid);
-            }
-            mHandler.sleep(mSsid);
-            return;
-        }
-
-        // Do the check
-        boolean isApAlive = checkDnsConnectivity();
-        
-        if (V) {
-            Slog.v(TAG, "  Is it alive: " + isApAlive);
-        }
-
-        // Take action based on results
-        if (isApAlive) {
-            handleApAlive(ap);
-        } else {
-            handleApUnresponsive(ap);
-        }
-    }
-
-    /**
-     * Handles the case when an access point is alive.
-     * 
-     * @param ap The access point.
-     */
-    private void handleApAlive(AccessPoint ap) {
-        // Check whether we are stale and should cancel
-        if (shouldCancel()) return;
-        // We're satisfied with this AP, so go idle
-        setIdleState(false);
-        
-        if (D) {
-            myLogD("AP is alive: " + ap.toString());
-        }
-        
-        // Queue the next action to be a background check
-        mHandler.backgroundCheckAp(ap);
-    }
-    
-    /**
-     * Handles an unresponsive AP by blacklisting it.
-     * 
-     * @param ap The access point.
-     */
-    private void handleApUnresponsive(AccessPoint ap) {
-        // Check whether we are stale and should cancel
-        if (shouldCancel()) return;
-        // This AP is "bad", switch to another
-        mState = WatchdogState.SWITCHING_AP;
-
-        if (D) {
-            myLogD("AP is dead: " + ap.toString());
-        }
-        
-        // Black list this "bad" AP, this will cause an attempt to connect to another
-        blacklistAp(ap.bssid);
-        // Initiate an association to an alternate AP
-        mWifiManager.reassociate();
-    }
-
-    private void blacklistAp(String bssid) {
-        if (TextUtils.isEmpty(bssid)) {
-            return;
-        }
-        
-        // Before taking action, make sure we should not cancel our processing
-        if (shouldCancel()) return;
-        
-        mWifiManager.addToBlacklist(bssid);
-
-        if (D) {
-            myLogD("Blacklisting " + bssid);
-        }
-    }
-
-    /**
-     * Handles a single background check. If it fails, it should trigger a
-     * normal check. If it succeeds, it should queue another background check.
-     * 
-     * @param ap The access point to do a background check for. If this is no
-     *        longer the current AP, it is okay to return without any
-     *        processing.
-     */
-    private void handleBackgroundCheckAp(AccessPoint ap) {
-        // Reset the cancel state since this is the entry point of this action
-        mShouldCancel = false;
-        
-        if (V) {
-            myLogV("handleBackgroundCheckAp: AccessPoint: " + ap);
-        }
-        
-        // Make sure we are not sleeping
-        if (mState == WatchdogState.SLEEP) {
-            if (V) {
-                Slog.v(TAG, "  handleBackgroundCheckAp: Sleeping (in " + mSsid + "), so returning");
-            }
-            return;
-        }
-        
-        // Make sure the AP we're supposed to be background checking is still the active one
-        WifiInfo info = mWifiManager.getConnectionInfo();
-        if (info.getSSID() == null || !info.getSSID().equals(ap.ssid)) {
-            if (V) {
-                myLogV("handleBackgroundCheckAp: We are no longer connected to "
-                        + ap + ", and instead are on " + info);
-            }
-            return;
-        }
-        
-        if (info.getBSSID() == null || !info.getBSSID().equals(ap.bssid)) {
-            if (V) {
-                myLogV("handleBackgroundCheckAp: We are no longer connected to "
-                        + ap + ", and instead are on " + info);
-            }
-            return;
-        }
-
-        // Do the check
-        boolean isApAlive = backgroundCheckDnsConnectivity();
-        
-        if (V && !isApAlive) {
-            Slog.v(TAG, "  handleBackgroundCheckAp: Is it alive: " + isApAlive);
-        }
-
-        if (shouldCancel()) {
-            return;
-        }
-        
-        // Take action based on results
-        if (isApAlive) {
-            // Queue another background check
-            mHandler.backgroundCheckAp(ap);
-            
-        } else {
-            if (D) {
-                myLogD("Background check failed for " + ap.toString());
-            }
-            
-            // Queue a normal check, so it can take proper action
-            mHandler.checkAp(ap);
-        }
-    }
-    
-    /**
-     * Handles going to sleep for this network. Going to sleep means we will not
-     * monitor this network anymore.
-     * 
-     * @param ssid The network that will not be monitored anymore.
-     */
-    private void handleSleep(String ssid) {
-        // Make sure the network we're trying to sleep in is still the current network
-        if (ssid != null && ssid.equals(mSsid)) {
-            mState = WatchdogState.SLEEP;
-
-            if (D) {
-                myLogD("Going to sleep for " + ssid);
-            }
-            
-            /*
-             * Before deciding to go to sleep, we may have checked a few APs
-             * (and blacklisted them). Clear the blacklist so the AP with best
-             * signal is chosen.
-             */
-            mWifiManager.clearBlacklist();
-            
-            if (V) {
-                myLogV("handleSleep: Set state to SLEEP and cleared blacklist");
-            }
-        }
-    }
-
-    /**
-     * Handles an access point disconnection.
-     */
-    private void handleDisconnected() {
-        /*
-         * We purposefully do not change mSsid to null. This is to handle
-         * disconnected followed by connected better (even if there is some
-         * duration in between). For example, if the watchdog went to sleep in a
-         * network, and then the phone goes to sleep, when the phone wakes up we
-         * still want to be in the sleeping state. When the phone went to sleep,
-         * we would have gotten a disconnected event which would then set mSsid
-         * = null. This is bad, since the following connect would cause us to do
-         * the "network is good?" check all over again. */
-        
-        /* 
-         * Set the state as if we were idle (don't come out of sleep, only
-         * hard reset and network changed should do that.
-         */
-        setIdleState(false);
-    }
-
-    /**
-     * Handles going idle. Idle means we are satisfied with the current state of
-     * things, but if a new connection occurs we'll re-evaluate.
-     */
-    private void handleIdle() {
-        // Reset the cancel state since this is the entry point for this action
-        mShouldCancel = false;
-        
-        if (V) {
-            myLogV("handleSwitchToIdle");
-        }
-        
-        // If we're sleeping, don't do anything
-        if (mState == WatchdogState.SLEEP) {
-            Slog.v(TAG, "  Sleeping (in " + mSsid + "), so returning");
-            return;
-        }
-        
-        // Set the idle state
-        setIdleState(false);
-        
-        if (V) {
-            Slog.v(TAG, "  Set state to IDLE");
-        }
-    }
-    
-    /**
-     * Sets the state as if we are going idle.
-     */
-    private void setIdleState(boolean forceIdleState) {
-        // Setting idle state does not kick us out of sleep unless the forceIdleState is set
-        if (forceIdleState || (mState != WatchdogState.SLEEP)) {
-            mState = WatchdogState.IDLE;
-        }
-        resetBssidCheckCount();
-    }
-
-    /**
-     * Handles a hard reset. A hard reset is rarely used, but when used it
-     * should revert anything done by the watchdog monitoring.
-     */
-    private void handleReset() {
-        mWifiManager.clearBlacklist();
-        setIdleState(true);
-    }
-    
-    // Inner classes
-
-    /**
-     * Possible states for the watchdog to be in.
-     */
-    private static enum WatchdogState {
-        /** The watchdog is currently idle, but it is still responsive to future AP checks in this network. */
-        IDLE,
-        /** The watchdog is sleeping, so it will not try any AP checks for the network. */
-        SLEEP,
-        /** The watchdog is currently checking an AP for connectivity. */
-        CHECKING_AP,
-        /** The watchdog is switching to another AP in the network. */
-        SWITCHING_AP
-    }
-
-    private int getBssidCheckCount() {
-        return mBssidCheckCount;
-    }
-
-    private void incrementBssidCheckCount() {
-        mBssidCheckCount++;
-    }
-
-    private void resetBssidCheckCount() {
-        this.mBssidCheckCount = 0;
-    }
-
-    /**
-     * The main thread for the watchdog monitoring. This will be turned into a
-     * {@link Looper} thread.
-     */
-    private class WifiWatchdogThread extends Thread {
-        WifiWatchdogThread() {
-            super("WifiWatchdogThread");
-        }
-        
-        @Override
-        public void run() {
-            // Set this thread up so the handler will work on it
-            Looper.prepare();
-            
-            synchronized(WifiWatchdogService.this) {
-                mHandler = new WifiWatchdogHandler();
-
-                // Notify that the handler has been created
-                WifiWatchdogService.this.notify();
-            }
-            
-            // Listen for messages to the handler
-            Looper.loop();
-        }
-    }
-
-    /**
-     * The main thread's handler. There are 'actions', and just general 
-     * 'messages'. There should only ever be one 'action' in the queue (aside
-     * from the one being processed, if any). There may be multiple messages in
-     * the queue. So, actions are replaced by more recent actions, where as
-     * messages will be executed for sure. Messages end up being used to just
-     * change some state, and not really take any action.
-     * <p>
-     * There is little logic inside this class, instead methods of the form
-     * "handle___" are called in the main {@link WifiWatchdogService}.
-     */
-    private class WifiWatchdogHandler extends Handler {
-        /** Check whether the AP is "good".  The object will be an {@link AccessPoint}. */
-        static final int ACTION_CHECK_AP = 1;
-        /** Go into the idle state. */
-        static final int ACTION_IDLE = 2;
-        /**
-         * Performs a periodic background check whether the AP is still "good".
-         * The object will be an {@link AccessPoint}.
-         */
-        static final int ACTION_BACKGROUND_CHECK_AP = 3;
-        /** Check whether the connection is a walled garden */
-        static final int ACTION_CHECK_WALLED_GARDEN = 4;
-
-        /**
-         * Go to sleep for the current network. We are conservative with making
-         * this a message rather than action. We want to make sure our main
-         * thread sees this message, but if it were an action it could be
-         * removed from the queue and replaced by another action. The main
-         * thread will ensure when it sees the message that the state is still
-         * valid for going to sleep.
-         * <p>
-         * For an explanation of sleep, see {@link android.provider.Settings.Secure#WIFI_WATCHDOG_MAX_AP_CHECKS}.
-         */
-        static final int MESSAGE_SLEEP = 101;
-        /** Disables the watchdog. */
-        static final int MESSAGE_DISABLE_WATCHDOG = 102;
-        /** The network has changed. */
-        static final int MESSAGE_NETWORK_CHANGED = 103;
-        /** The current access point has disconnected. */
-        static final int MESSAGE_DISCONNECTED = 104;
-        /** Performs a hard-reset on the watchdog state. */
-        static final int MESSAGE_RESET = 105;
-
-        /* Walled garden detection */
-        private String mLastSsid;
-        private long mLastTime;
-        private final long MIN_WALLED_GARDEN_TEST_INTERVAL = 15 * 60 * 1000; //15 minutes
-
-        void checkWalledGarden(String ssid) {
-            sendMessage(obtainMessage(ACTION_CHECK_WALLED_GARDEN, ssid));
-        }
-
-        void checkAp(AccessPoint ap) {
-            removeAllActions();
-            sendMessage(obtainMessage(ACTION_CHECK_AP, ap));
-        }
-        
-        void backgroundCheckAp(AccessPoint ap) {
-            if (!isBackgroundCheckEnabled()) return;
-            
-            removeAllActions();
-            sendMessageDelayed(obtainMessage(ACTION_BACKGROUND_CHECK_AP, ap),
-                    getBackgroundCheckDelayMs());
-        }
-        
-        void idle() {
-            removeAllActions();
-            sendMessage(obtainMessage(ACTION_IDLE));
-        }
-        
-        void sleep(String ssid) {
-            removeAllActions();
-            sendMessage(obtainMessage(MESSAGE_SLEEP, ssid));
-        }
-        
-        void disableWatchdog() {
-            removeAllActions();
-            sendMessage(obtainMessage(MESSAGE_DISABLE_WATCHDOG));
-        }
-        
-        void dispatchNetworkChanged(String ssid) {
-            removeAllActions();
-            sendMessage(obtainMessage(MESSAGE_NETWORK_CHANGED, ssid));
-        }
-
-        void dispatchDisconnected() {
-            removeAllActions();
-            sendMessage(obtainMessage(MESSAGE_DISCONNECTED));
-        }
-
-        void reset() {
-            removeAllActions();
-            sendMessage(obtainMessage(MESSAGE_RESET));
-        }
-        
-        private void removeAllActions() {
-            removeMessages(ACTION_CHECK_AP);
-            removeMessages(ACTION_IDLE);
-            removeMessages(ACTION_BACKGROUND_CHECK_AP);
-        }
-        
-        @Override
-        public void handleMessage(Message msg) {
-            if (V) {
-                myLogV("handleMessage: " + msg.what);
-            }
-            switch (msg.what) {
-                case MESSAGE_NETWORK_CHANGED:
-                    handleNetworkChanged((String) msg.obj);
-                    break;
-                case ACTION_CHECK_AP:
-                    handleCheckAp((AccessPoint) msg.obj);
-                    break;
-                case ACTION_BACKGROUND_CHECK_AP:
-                    handleBackgroundCheckAp((AccessPoint) msg.obj);
-                    break;
-                case ACTION_CHECK_WALLED_GARDEN:
-                    handleWalledGardenCheck((String) msg.obj);
-                    break;
-                case MESSAGE_SLEEP:
-                    handleSleep((String) msg.obj);
-                    break;
-                case ACTION_IDLE:
-                    handleIdle();
-                    break;
-                case MESSAGE_DISABLE_WATCHDOG:
-                    handleIdle();
-                    break;
-                case MESSAGE_DISCONNECTED:
-                    handleDisconnected();
-                    break;
-                case MESSAGE_RESET:
-                    handleReset();
-                    break;
-            }
-        }
-
-        /**
-         * DNS based detection techniques do not work at all hotspots. The one sure way to check
-         * a walled garden is to see if a URL fetch on a known address fetches the data we
-         * expect
-         */
-        private boolean isWalledGardenConnection() {
-            InputStream in = null;
-            HttpURLConnection  urlConnection = null;
-            try {
-                URL url = new URL(getWalledGardenUrl());
-                urlConnection = (HttpURLConnection) url.openConnection();
-                in = new BufferedInputStream(urlConnection.getInputStream());
-                Scanner scanner = new Scanner(in);
-                if (scanner.findInLine(getWalledGardenPattern()) != null) {
-                    return false;
-                } else {
-                    return true;
-                }
-            } catch (IOException e) {
-                return false;
-            } finally {
-                if (in != null) {
-                    try {
-                        in.close();
-                    } catch (IOException e) {
-                    }
-                }
-                if (urlConnection != null) urlConnection.disconnect();
-            }
-        }
-
-        private void handleWalledGardenCheck(String ssid) {
-            long currentTime = System.currentTimeMillis();
-            //Avoid a walled garden test on the same network if one was already done
-            //within MIN_WALLED_GARDEN_TEST_INTERVAL. This will handle scenarios where
-            //there are frequent network disconnections
-            if (ssid.equals(mLastSsid) &&
-                    (currentTime - mLastTime) < MIN_WALLED_GARDEN_TEST_INTERVAL) {
-                return;
-            }
-
-            mLastTime = currentTime;
-            mLastSsid = ssid;
-
-            if (isWalledGardenConnection()) {
-                Uri uri = Uri.parse("http://www.google.com");
-                Intent intent = new Intent(Intent.ACTION_VIEW, uri);
-                intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
-                        Intent.FLAG_ACTIVITY_NEW_TASK);
-                mContext.startActivity(intent);
-            }
-        }
-    }
-
-    /**
-     * Receives Wi-Fi broadcasts.
-     * <p>
-     * There is little logic in this class, instead methods of the form "on___"
-     * are called in the {@link WifiWatchdogService}.
-     */
-    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
-                handleNetworkStateChanged(
-                        (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO));
-            } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
-                handleWifiStateChanged(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
-                        WifiManager.WIFI_STATE_UNKNOWN));
-            }
-        }
-
-        private void handleNetworkStateChanged(NetworkInfo info) {
-            if (V) {
-                myLogV("Receiver.handleNetworkStateChanged: NetworkInfo: "
-                        + info);
-            }
-            
-            switch (info.getState()) {
-                case CONNECTED:
-                    WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
-                    if (wifiInfo.getSSID() == null || wifiInfo.getBSSID() == null) {
-                        if (V) {
-                            myLogV("handleNetworkStateChanged: Got connected event but SSID or BSSID are null. SSID: "
-                                + wifiInfo.getSSID()
-                                + ", BSSID: "
-                                + wifiInfo.getBSSID() + ", ignoring event");
-                        }
-                        return;
-                    }
-                    onConnected(wifiInfo.getSSID(), wifiInfo.getBSSID());
-                    break;
-
-                case DISCONNECTED:
-                    onDisconnected();
-                    break;
-            }
-        }
-
-        private void handleWifiStateChanged(int wifiState) {
-            if (wifiState == WifiManager.WIFI_STATE_DISABLED) {
-                onDisconnected();
-            } else if (wifiState == WifiManager.WIFI_STATE_ENABLED) {
-                onEnabled();
-            }
-        }
-    };
-
-    /**
-     * Describes an access point by its SSID and BSSID.
-     *
-     */
-    private static class AccessPoint {
-        String ssid;
-        String bssid;
-        
-        /**
-         * @param ssid cannot be null
-         * @param bssid cannot be null
-         */
-        AccessPoint(String ssid, String bssid) {
-            if (ssid == null || bssid == null) {
-                Slog.e(TAG, String.format("(%s) INVALID ACCESSPOINT: (%s, %s)",
-                        Thread.currentThread().getName(),ssid,bssid));
-            }
-            this.ssid = ssid;
-            this.bssid = bssid;
-        }
-        
-        @Override
-        public boolean equals(Object o) {
-            if (!(o instanceof AccessPoint)) return false;
-            AccessPoint otherAp = (AccessPoint) o;
-
-            // Either we both have a null, or our SSIDs and BSSIDs are equal
-            return ssid.equals(otherAp.ssid) && bssid.equals(otherAp.bssid);
-        }
-        
-        @Override
-        public int hashCode() {
-            return ssid.hashCode() + bssid.hashCode();
-        }
-
-        @Override
-        public String toString() {
-            return ssid + " (" + bssid + ")";
-        }
-    }
-
-    /**
-     * Performs a simple DNS "ping" by sending a "server status" query packet to
-     * the DNS server. As long as the server replies, we consider it a success.
-     * <p>
-     * We do not use a simple hostname lookup because that could be cached and
-     * the API may not differentiate between a time out and a failure lookup
-     * (which we really care about).
-     */
-    private static class DnsPinger {
-        
-        /** Number of bytes for the query */
-        private static final int DNS_QUERY_BASE_SIZE = 33;
-        
-        /** The DNS port */
-        private static final int DNS_PORT = 53;
-        
-        /** Used to generate IDs */
-        private static Random sRandom = new Random();
-
-        static boolean isDnsReachable(InetAddress dnsAddress, int timeout) {
-            DatagramSocket socket = null;
-            try {
-                socket = new DatagramSocket();
-
-                // Set some socket properties
-                socket.setSoTimeout(timeout);
-
-                byte[] buf = new byte[DNS_QUERY_BASE_SIZE];
-                fillQuery(buf);
-
-                // Send the DNS query
-
-                DatagramPacket packet = new DatagramPacket(buf,
-                        buf.length, dnsAddress, DNS_PORT);
-                socket.send(packet);
-
-                // Wait for reply (blocks for the above timeout)
-                DatagramPacket replyPacket = new DatagramPacket(buf, buf.length);
-                socket.receive(replyPacket);
-
-                // If a timeout occurred, an exception would have been thrown.  We got a reply!
-                return true;
-
-            } catch (SocketException e) {
-                if (V) {
-                    Slog.v(TAG, "DnsPinger.isReachable received SocketException", e);
-                }
-                return false;
-
-            } catch (UnknownHostException e) {
-                if (V) {
-                    Slog.v(TAG, "DnsPinger.isReachable is unable to resolve the DNS host", e);
-                }
-                return false;
-
-            } catch (SocketTimeoutException e) {
-                return false;
-                
-            } catch (IOException e) {
-                if (V) {
-                    Slog.v(TAG, "DnsPinger.isReachable got an IOException", e);
-                }
-                return false;
-                
-            } catch (Exception e) {
-                if (V) {
-                    Slog.d(TAG, "DnsPinger.isReachable got an unknown exception", e);
-                }
-                return false;
-            } finally {
-                if (socket != null) {
-                    socket.close();
-                }
-            }
-        }
-        
-        private static void fillQuery(byte[] buf) {
-
-            /*
-             * See RFC2929 (though the bit tables in there are misleading for
-             * us. For example, the recursion desired bit is the 0th bit for us,
-             * but looking there it would appear as the 7th bit of the byte
-             */
-
-            // Make sure it's all zeroed out
-            for (int i = 0; i < buf.length; i++) buf[i] = 0;
-
-            // Form a query for www.android.com
-            
-            // [0-1] bytes are an ID, generate random ID for this query
-            buf[0] = (byte) sRandom.nextInt(256); 
-            buf[1] = (byte) sRandom.nextInt(256); 
-            
-            // [2-3] bytes are for flags.
-            buf[2] = 1; // Recursion desired
-
-            // [4-5] bytes are for the query count
-            buf[5] = 1; // One query 
-            
-            // [6-7] [8-9] [10-11] are all counts of other fields we don't use
-
-            // [12-15] for www
-            writeString(buf, 12, "www");
-            
-            // [16-23] for android
-            writeString(buf, 16, "android");
-            
-            // [24-27] for com
-            writeString(buf, 24, "com");
-            
-            // [29-30] bytes are for QTYPE, set to 1 
-            buf[30] = 1;
-
-            // [31-32] bytes are for QCLASS, set to 1 
-            buf[32] = 1;
-        }
-        
-        private static void writeString(byte[] buf, int startPos, String string) {
-            int pos = startPos;
-            
-            // Write the length first
-            buf[pos++] = (byte) string.length();
-            for (int i = 0; i < string.length(); i++) {
-                buf[pos++] = (byte) string.charAt(i);
-            }
-        }
+                Settings.Secure.WIFI_WATCHDOG_ON, 1) == 1;
     }
 }
diff --git a/tests/CoreTests/MODULE_LICENSE_APACHE2 b/tests/CoreTests/MODULE_LICENSE_APACHE2
deleted file mode 100644
index e69de29..0000000
--- a/tests/CoreTests/MODULE_LICENSE_APACHE2
+++ /dev/null