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