diff options
Diffstat (limited to 'services/java/com/android/server/WifiWatchdogService.java')
-rw-r--r-- | services/java/com/android/server/WifiWatchdogService.java | 1450 |
1 files changed, 0 insertions, 1450 deletions
diff --git a/services/java/com/android/server/WifiWatchdogService.java b/services/java/com/android/server/WifiWatchdogService.java deleted file mode 100644 index 56bfbe0..0000000 --- a/services/java/com/android/server/WifiWatchdogService.java +++ /dev/null @@ -1,1450 +0,0 @@ -/* - * Copyright (C) 2008 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.BroadcastReceiver; -import android.content.ContentResolver; -import android.content.Context; -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.wifi.ScanResult; -import android.net.wifi.WifiInfo; -import android.net.wifi.WifiManager; -import android.net.Uri; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -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.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.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. - * <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 - * <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. - * - * {@hide} - */ -public class WifiWatchdogService { - private static final String TAG = "WifiWatchdogService"; - private static final boolean V = false; - private static final boolean D = true; - - 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; - - /** - * The current watchdog state. Only written from the main thread! - */ - 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; - - 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 - registerForSettingsChanges(); - if (isWatchdogEnabled()) { - registerForWifiBroadcasts(); - } - - if (V) { - myLogV("WifiWatchdogService: Created"); - } - } - - /** - * 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) { - @Override - public void onChange(boolean selfChange) { - if (isWatchdogEnabled()) { - registerForWifiBroadcasts(); - } else { - unregisterForWifiBroadcasts(); - if (mHandler != null) { - mHandler.disableWatchdog(); - } - } - } - }); - } - - /** - * @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); - } - } - } -} |