Home | History | Annotate | Download | only in server
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server;
     18 
     19 import android.content.BroadcastReceiver;
     20 import android.content.ContentResolver;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.IntentFilter;
     24 import android.database.ContentObserver;
     25 import android.net.NetworkInfo;
     26 import android.net.DhcpInfo;
     27 import android.net.wifi.ScanResult;
     28 import android.net.wifi.WifiInfo;
     29 import android.net.wifi.WifiManager;
     30 import android.net.wifi.WifiStateTracker;
     31 import android.os.Handler;
     32 import android.os.Looper;
     33 import android.os.Message;
     34 import android.provider.Settings;
     35 import android.text.TextUtils;
     36 import android.util.Config;
     37 import android.util.Slog;
     38 
     39 import java.io.IOException;
     40 import java.net.DatagramPacket;
     41 import java.net.DatagramSocket;
     42 import java.net.InetAddress;
     43 import java.net.SocketException;
     44 import java.net.SocketTimeoutException;
     45 import java.net.UnknownHostException;
     46 import java.util.List;
     47 import java.util.Random;
     48 
     49 /**
     50  * {@link WifiWatchdogService} monitors the initial connection to a Wi-Fi
     51  * network with multiple access points. After the framework successfully
     52  * connects to an access point, the watchdog verifies whether the DNS server is
     53  * reachable. If not, the watchdog blacklists the current access point, leading
     54  * to a connection on another access point within the same network.
     55  * <p>
     56  * The watchdog has a few safeguards:
     57  * <ul>
     58  * <li>Only monitor networks with multiple access points
     59  * <li>Only check at most {@link #getMaxApChecks()} different access points
     60  * within the network before giving up
     61  * <p>
     62  * The watchdog checks for connectivity on an access point by ICMP pinging the
     63  * DNS. There are settings that allow disabling the watchdog, or tweaking the
     64  * acceptable packet loss (and other various parameters).
     65  * <p>
     66  * The core logic of the watchdog is done on the main watchdog thread. Wi-Fi
     67  * callbacks can come in on other threads, so we must queue messages to the main
     68  * watchdog thread's handler. Most (if not all) state is only written to from
     69  * the main thread.
     70  *
     71  * {@hide}
     72  */
     73 public class WifiWatchdogService {
     74     private static final String TAG = "WifiWatchdogService";
     75     private static final boolean V = false || Config.LOGV;
     76     private static final boolean D = true || Config.LOGD;
     77 
     78     private Context mContext;
     79     private ContentResolver mContentResolver;
     80     private WifiStateTracker mWifiStateTracker;
     81     private WifiManager mWifiManager;
     82 
     83     /**
     84      * The main watchdog thread.
     85      */
     86     private WifiWatchdogThread mThread;
     87     /**
     88      * The handler for the main watchdog thread.
     89      */
     90     private WifiWatchdogHandler mHandler;
     91 
     92     private ContentObserver mContentObserver;
     93 
     94     /**
     95      * The current watchdog state. Only written from the main thread!
     96      */
     97     private WatchdogState mState = WatchdogState.IDLE;
     98     /**
     99      * The SSID of the network that the watchdog is currently monitoring. Only
    100      * touched in the main thread!
    101      */
    102     private String mSsid;
    103     /**
    104      * The number of access points in the current network ({@link #mSsid}) that
    105      * have been checked. Only touched in the main thread!
    106      */
    107     private int mNumApsChecked;
    108     /** Whether the current AP check should be canceled. */
    109     private boolean mShouldCancel;
    110 
    111     WifiWatchdogService(Context context, WifiStateTracker wifiStateTracker) {
    112         mContext = context;
    113         mContentResolver = context.getContentResolver();
    114         mWifiStateTracker = wifiStateTracker;
    115         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
    116 
    117         createThread();
    118 
    119         // The content observer to listen needs a handler, which createThread creates
    120         registerForSettingsChanges();
    121         if (isWatchdogEnabled()) {
    122             registerForWifiBroadcasts();
    123         }
    124 
    125         if (V) {
    126             myLogV("WifiWatchdogService: Created");
    127         }
    128     }
    129 
    130     /**
    131      * Observes the watchdog on/off setting, and takes action when changed.
    132      */
    133     private void registerForSettingsChanges() {
    134         ContentResolver contentResolver = mContext.getContentResolver();
    135         contentResolver.registerContentObserver(
    136                 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON), false,
    137                 mContentObserver = new ContentObserver(mHandler) {
    138             @Override
    139             public void onChange(boolean selfChange) {
    140                 if (isWatchdogEnabled()) {
    141                     registerForWifiBroadcasts();
    142                 } else {
    143                     unregisterForWifiBroadcasts();
    144                     if (mHandler != null) {
    145                         mHandler.disableWatchdog();
    146                     }
    147                 }
    148             }
    149         });
    150     }
    151 
    152     /**
    153      * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ON
    154      */
    155     private boolean isWatchdogEnabled() {
    156         return Settings.Secure.getInt(mContentResolver, Settings.Secure.WIFI_WATCHDOG_ON, 1) == 1;
    157     }
    158 
    159     /**
    160      * @see android.provider.Settings.Secure#WIFI_WATCHDOG_AP_COUNT
    161      */
    162     private int getApCount() {
    163         return Settings.Secure.getInt(mContentResolver,
    164             Settings.Secure.WIFI_WATCHDOG_AP_COUNT, 2);
    165     }
    166 
    167     /**
    168      * @see android.provider.Settings.Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT
    169      */
    170     private int getInitialIgnoredPingCount() {
    171         return Settings.Secure.getInt(mContentResolver,
    172             Settings.Secure.WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT , 2);
    173     }
    174 
    175     /**
    176      * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_COUNT
    177      */
    178     private int getPingCount() {
    179         return Settings.Secure.getInt(mContentResolver,
    180             Settings.Secure.WIFI_WATCHDOG_PING_COUNT, 4);
    181     }
    182 
    183     /**
    184      * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_TIMEOUT_MS
    185      */
    186     private int getPingTimeoutMs() {
    187         return Settings.Secure.getInt(mContentResolver,
    188             Settings.Secure.WIFI_WATCHDOG_PING_TIMEOUT_MS, 500);
    189     }
    190 
    191     /**
    192      * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_DELAY_MS
    193      */
    194     private int getPingDelayMs() {
    195         return Settings.Secure.getInt(mContentResolver,
    196             Settings.Secure.WIFI_WATCHDOG_PING_DELAY_MS, 250);
    197     }
    198 
    199     /**
    200      * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE
    201      */
    202     private int getAcceptablePacketLossPercentage() {
    203         return Settings.Secure.getInt(mContentResolver,
    204             Settings.Secure.WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE, 25);
    205     }
    206 
    207     /**
    208      * @see android.provider.Settings.Secure#WIFI_WATCHDOG_MAX_AP_CHECKS
    209      */
    210     private int getMaxApChecks() {
    211         return Settings.Secure.getInt(mContentResolver,
    212             Settings.Secure.WIFI_WATCHDOG_MAX_AP_CHECKS, 7);
    213     }
    214 
    215     /**
    216      * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED
    217      */
    218     private boolean isBackgroundCheckEnabled() {
    219         return Settings.Secure.getInt(mContentResolver,
    220             Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED, 1) == 1;
    221     }
    222 
    223     /**
    224      * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS
    225      */
    226     private int getBackgroundCheckDelayMs() {
    227         return Settings.Secure.getInt(mContentResolver,
    228             Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS, 60000);
    229     }
    230 
    231     /**
    232      * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS
    233      */
    234     private int getBackgroundCheckTimeoutMs() {
    235         return Settings.Secure.getInt(mContentResolver,
    236             Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS, 1000);
    237     }
    238 
    239     /**
    240      * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WATCH_LIST
    241      * @return the comma-separated list of SSIDs
    242      */
    243     private String getWatchList() {
    244         return Settings.Secure.getString(mContentResolver,
    245                 Settings.Secure.WIFI_WATCHDOG_WATCH_LIST);
    246     }
    247 
    248     /**
    249      * Registers to receive the necessary Wi-Fi broadcasts.
    250      */
    251     private void registerForWifiBroadcasts() {
    252         IntentFilter intentFilter = new IntentFilter();
    253         intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
    254         intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
    255         mContext.registerReceiver(mReceiver, intentFilter);
    256     }
    257 
    258     /**
    259      * Unregisters from receiving the Wi-Fi broadcasts.
    260      */
    261     private void unregisterForWifiBroadcasts() {
    262         mContext.unregisterReceiver(mReceiver);
    263     }
    264 
    265     /**
    266      * Creates the main watchdog thread, including waiting for the handler to be
    267      * created.
    268      */
    269     private void createThread() {
    270         mThread = new WifiWatchdogThread();
    271         mThread.start();
    272         waitForHandlerCreation();
    273     }
    274 
    275     /**
    276      * Unregister broadcasts and quit the watchdog thread
    277      */
    278     private void quit() {
    279         unregisterForWifiBroadcasts();
    280         mContext.getContentResolver().unregisterContentObserver(mContentObserver);
    281         mHandler.removeAllActions();
    282         mHandler.getLooper().quit();
    283     }
    284 
    285     /**
    286      * Waits for the main watchdog thread to create the handler.
    287      */
    288     private void waitForHandlerCreation() {
    289         synchronized(this) {
    290             while (mHandler == null) {
    291                 try {
    292                     // Wait for the handler to be set by the other thread
    293                     wait();
    294                 } catch (InterruptedException e) {
    295                     Slog.e(TAG, "Interrupted while waiting on handler.");
    296                 }
    297             }
    298         }
    299     }
    300 
    301     // Utility methods
    302 
    303     /**
    304      * Logs with the current thread.
    305      */
    306     private static void myLogV(String message) {
    307         Slog.v(TAG, "(" + Thread.currentThread().getName() + ") " + message);
    308     }
    309 
    310     private static void myLogD(String message) {
    311         Slog.d(TAG, "(" + Thread.currentThread().getName() + ") " + message);
    312     }
    313 
    314     /**
    315      * Gets the DNS of the current AP.
    316      *
    317      * @return The DNS of the current AP.
    318      */
    319     private int getDns() {
    320         DhcpInfo addressInfo = mWifiManager.getDhcpInfo();
    321         if (addressInfo != null) {
    322             return addressInfo.dns1;
    323         } else {
    324             return -1;
    325         }
    326     }
    327 
    328     /**
    329      * Checks whether the DNS can be reached using multiple attempts according
    330      * to the current setting values.
    331      *
    332      * @return Whether the DNS is reachable
    333      */
    334     private boolean checkDnsConnectivity() {
    335         int dns = getDns();
    336         if (dns == -1) {
    337             if (V) {
    338                 myLogV("checkDnsConnectivity: Invalid DNS, returning false");
    339             }
    340             return false;
    341         }
    342 
    343         if (V) {
    344             myLogV("checkDnsConnectivity: Checking 0x" +
    345                     Integer.toHexString(Integer.reverseBytes(dns)) + " for connectivity");
    346         }
    347 
    348         int numInitialIgnoredPings = getInitialIgnoredPingCount();
    349         int numPings = getPingCount();
    350         int pingDelay = getPingDelayMs();
    351         int acceptableLoss = getAcceptablePacketLossPercentage();
    352 
    353         /** See {@link Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT} */
    354         int ignoredPingCounter = 0;
    355         int pingCounter = 0;
    356         int successCounter = 0;
    357 
    358         // No connectivity check needed
    359         if (numPings == 0) {
    360             return true;
    361         }
    362 
    363         // Do the initial pings that we ignore
    364         for (; ignoredPingCounter < numInitialIgnoredPings; ignoredPingCounter++) {
    365             if (shouldCancel()) return false;
    366 
    367             boolean dnsAlive = DnsPinger.isDnsReachable(dns, getPingTimeoutMs());
    368             if (dnsAlive) {
    369                 /*
    370                  * Successful "ignored" pings are *not* ignored (they count in the total number
    371                  * of pings), but failures are really ignored.
    372                  */
    373                 pingCounter++;
    374                 successCounter++;
    375             }
    376 
    377             if (V) {
    378                 Slog.v(TAG, (dnsAlive ? "  +" : "  Ignored: -"));
    379             }
    380 
    381             if (shouldCancel()) return false;
    382 
    383             try {
    384                 Thread.sleep(pingDelay);
    385             } catch (InterruptedException e) {
    386                 Slog.w(TAG, "Interrupted while pausing between pings", e);
    387             }
    388         }
    389 
    390         // Do the pings that we use to measure packet loss
    391         for (; pingCounter < numPings; pingCounter++) {
    392             if (shouldCancel()) return false;
    393 
    394             if (DnsPinger.isDnsReachable(dns, getPingTimeoutMs())) {
    395                 successCounter++;
    396                 if (V) {
    397                     Slog.v(TAG, "  +");
    398                 }
    399             } else {
    400                 if (V) {
    401                     Slog.v(TAG, "  -");
    402                 }
    403             }
    404 
    405             if (shouldCancel()) return false;
    406 
    407             try {
    408                 Thread.sleep(pingDelay);
    409             } catch (InterruptedException e) {
    410                 Slog.w(TAG, "Interrupted while pausing between pings", e);
    411             }
    412         }
    413 
    414         int packetLossPercentage = 100 * (numPings - successCounter) / numPings;
    415         if (D) {
    416             Slog.d(TAG, packetLossPercentage
    417                     + "% packet loss (acceptable is " + acceptableLoss + "%)");
    418         }
    419 
    420         return !shouldCancel() && (packetLossPercentage <= acceptableLoss);
    421     }
    422 
    423     private boolean backgroundCheckDnsConnectivity() {
    424         int dns = getDns();
    425         if (false && V) {
    426             myLogV("backgroundCheckDnsConnectivity: Background checking " + dns +
    427                     " for connectivity");
    428         }
    429 
    430         if (dns == -1) {
    431             if (V) {
    432                 myLogV("backgroundCheckDnsConnectivity: DNS is empty, returning false");
    433             }
    434             return false;
    435         }
    436 
    437         return DnsPinger.isDnsReachable(dns, getBackgroundCheckTimeoutMs());
    438     }
    439 
    440     /**
    441      * Signals the current action to cancel.
    442      */
    443     private void cancelCurrentAction() {
    444         mShouldCancel = true;
    445     }
    446 
    447     /**
    448      * Helper to check whether to cancel.
    449      *
    450      * @return Whether to cancel processing the action.
    451      */
    452     private boolean shouldCancel() {
    453         if (V && mShouldCancel) {
    454             myLogV("shouldCancel: Cancelling");
    455         }
    456 
    457         return mShouldCancel;
    458     }
    459 
    460     // Wi-Fi initiated callbacks (could be executed in another thread)
    461 
    462     /**
    463      * Called when connected to an AP (this can be the next AP in line, or
    464      * it can be a completely different network).
    465      *
    466      * @param ssid The SSID of the access point.
    467      * @param bssid The BSSID of the access point.
    468      */
    469     private void onConnected(String ssid, String bssid) {
    470         if (V) {
    471             myLogV("onConnected: SSID: " + ssid + ", BSSID: " + bssid);
    472         }
    473 
    474         /*
    475          * The current action being processed by the main watchdog thread is now
    476          * stale, so cancel it.
    477          */
    478         cancelCurrentAction();
    479 
    480         if ((mSsid == null) || !mSsid.equals(ssid)) {
    481             /*
    482              * This is a different network than what the main watchdog thread is
    483              * processing, dispatch the network change message on the main thread.
    484              */
    485             mHandler.dispatchNetworkChanged(ssid);
    486         }
    487 
    488         if (requiresWatchdog(ssid, bssid)) {
    489             if (D) {
    490                 myLogD(ssid + " (" + bssid + ") requires the watchdog");
    491             }
    492 
    493             // This access point requires a watchdog, so queue the check on the main thread
    494             mHandler.checkAp(new AccessPoint(ssid, bssid));
    495 
    496         } else {
    497             if (D) {
    498                 myLogD(ssid + " (" + bssid + ") does not require the watchdog");
    499             }
    500 
    501             // This access point does not require a watchdog, so queue idle on the main thread
    502             mHandler.idle();
    503         }
    504     }
    505 
    506     /**
    507      * Called when Wi-Fi is enabled.
    508      */
    509     private void onEnabled() {
    510         cancelCurrentAction();
    511         // Queue a hard-reset of the state on the main thread
    512         mHandler.reset();
    513     }
    514 
    515     /**
    516      * Called when disconnected (or some other event similar to being disconnected).
    517      */
    518     private void onDisconnected() {
    519         if (V) {
    520             myLogV("onDisconnected");
    521         }
    522 
    523         /*
    524          * Disconnected from an access point, the action being processed by the
    525          * watchdog thread is now stale, so cancel it.
    526          */
    527         cancelCurrentAction();
    528         // Dispatch the disconnected to the main watchdog thread
    529         mHandler.dispatchDisconnected();
    530         // Queue the action to go idle
    531         mHandler.idle();
    532     }
    533 
    534     /**
    535      * Checks whether an access point requires watchdog monitoring.
    536      *
    537      * @param ssid The SSID of the access point.
    538      * @param bssid The BSSID of the access point.
    539      * @return Whether the access point/network should be monitored by the
    540      *         watchdog.
    541      */
    542     private boolean requiresWatchdog(String ssid, String bssid) {
    543         if (V) {
    544             myLogV("requiresWatchdog: SSID: " + ssid + ", BSSID: " + bssid);
    545         }
    546 
    547         WifiInfo info = null;
    548         if (ssid == null) {
    549             /*
    550              * This is called from a Wi-Fi callback, so assume the WifiInfo does
    551              * not have stale data.
    552              */
    553             info = mWifiManager.getConnectionInfo();
    554             ssid = info.getSSID();
    555             if (ssid == null) {
    556                 // It's still null, give up
    557                 if (V) {
    558                     Slog.v(TAG, "  Invalid SSID, returning false");
    559                 }
    560                 return false;
    561             }
    562         }
    563 
    564         if (TextUtils.isEmpty(bssid)) {
    565             // Similar as above
    566             if (info == null) {
    567                 info = mWifiManager.getConnectionInfo();
    568             }
    569             bssid = info.getBSSID();
    570             if (TextUtils.isEmpty(bssid)) {
    571                 // It's still null, give up
    572                 if (V) {
    573                     Slog.v(TAG, "  Invalid BSSID, returning false");
    574                 }
    575                 return false;
    576             }
    577         }
    578 
    579         if (!isOnWatchList(ssid)) {
    580             if (V) {
    581                 Slog.v(TAG, "  SSID not on watch list, returning false");
    582             }
    583             return false;
    584         }
    585 
    586         // The watchdog only monitors networks with multiple APs
    587         if (!hasRequiredNumberOfAps(ssid)) {
    588             return false;
    589         }
    590 
    591         return true;
    592     }
    593 
    594     private boolean isOnWatchList(String ssid) {
    595         String watchList;
    596 
    597         if (ssid == null || (watchList = getWatchList()) == null) {
    598             return false;
    599         }
    600 
    601         String[] list = watchList.split(" *, *");
    602 
    603         for (String name : list) {
    604             if (ssid.equals(name)) {
    605                 return true;
    606             }
    607         }
    608 
    609         return false;
    610     }
    611 
    612     /**
    613      * Checks if the current scan results have multiple access points with an SSID.
    614      *
    615      * @param ssid The SSID to check.
    616      * @return Whether the SSID has multiple access points.
    617      */
    618     private boolean hasRequiredNumberOfAps(String ssid) {
    619         List<ScanResult> results = mWifiManager.getScanResults();
    620         if (results == null) {
    621             if (V) {
    622                 myLogV("hasRequiredNumberOfAps: Got null scan results, returning false");
    623             }
    624             return false;
    625         }
    626 
    627         int numApsRequired = getApCount();
    628         int numApsFound = 0;
    629         int resultsSize = results.size();
    630         for (int i = 0; i < resultsSize; i++) {
    631             ScanResult result = results.get(i);
    632             if (result == null) continue;
    633             if (result.SSID == null) continue;
    634 
    635             if (result.SSID.equals(ssid)) {
    636                 numApsFound++;
    637 
    638                 if (numApsFound >= numApsRequired) {
    639                     if (V) {
    640                         myLogV("hasRequiredNumberOfAps: SSID: " + ssid + ", returning true");
    641                     }
    642                     return true;
    643                 }
    644             }
    645         }
    646 
    647         if (V) {
    648             myLogV("hasRequiredNumberOfAps: SSID: " + ssid + ", returning false");
    649         }
    650         return false;
    651     }
    652 
    653     // Watchdog logic (assume all of these methods will be in our main thread)
    654 
    655     /**
    656      * Handles a Wi-Fi network change (for example, from networkA to networkB).
    657      */
    658     private void handleNetworkChanged(String ssid) {
    659         // Set the SSID being monitored to the new SSID
    660         mSsid = ssid;
    661         // Set various state to that when being idle
    662         setIdleState(true);
    663     }
    664 
    665     /**
    666      * Handles checking whether an AP is a "good" AP.  If not, it will be blacklisted.
    667      *
    668      * @param ap The access point to check.
    669      */
    670     private void handleCheckAp(AccessPoint ap) {
    671         // Reset the cancel state since this is the entry point of this action
    672         mShouldCancel = false;
    673 
    674         if (V) {
    675             myLogV("handleCheckAp: AccessPoint: " + ap);
    676         }
    677 
    678         // Make sure we are not sleeping
    679         if (mState == WatchdogState.SLEEP) {
    680             if (V) {
    681                 Slog.v(TAG, "  Sleeping (in " + mSsid + "), so returning");
    682             }
    683             return;
    684         }
    685 
    686         mState = WatchdogState.CHECKING_AP;
    687 
    688         /*
    689          * Checks to make sure we haven't exceeded the max number of checks
    690          * we're allowed per network
    691          */
    692         mNumApsChecked++;
    693         if (mNumApsChecked > getMaxApChecks()) {
    694             if (V) {
    695                 Slog.v(TAG, "  Passed the max attempts (" + getMaxApChecks()
    696                         + "), going to sleep for " + mSsid);
    697             }
    698             mHandler.sleep(mSsid);
    699             return;
    700         }
    701 
    702         // Do the check
    703         boolean isApAlive = checkDnsConnectivity();
    704 
    705         if (V) {
    706             Slog.v(TAG, "  Is it alive: " + isApAlive);
    707         }
    708 
    709         // Take action based on results
    710         if (isApAlive) {
    711             handleApAlive(ap);
    712         } else {
    713             handleApUnresponsive(ap);
    714         }
    715     }
    716 
    717     /**
    718      * Handles the case when an access point is alive.
    719      *
    720      * @param ap The access point.
    721      */
    722     private void handleApAlive(AccessPoint ap) {
    723         // Check whether we are stale and should cancel
    724         if (shouldCancel()) return;
    725         // We're satisfied with this AP, so go idle
    726         setIdleState(false);
    727 
    728         if (D) {
    729             myLogD("AP is alive: " + ap.toString());
    730         }
    731 
    732         // Queue the next action to be a background check
    733         mHandler.backgroundCheckAp(ap);
    734     }
    735 
    736     /**
    737      * Handles an unresponsive AP by blacklisting it.
    738      *
    739      * @param ap The access point.
    740      */
    741     private void handleApUnresponsive(AccessPoint ap) {
    742         // Check whether we are stale and should cancel
    743         if (shouldCancel()) return;
    744         // This AP is "bad", switch to another
    745         mState = WatchdogState.SWITCHING_AP;
    746 
    747         if (D) {
    748             myLogD("AP is dead: " + ap.toString());
    749         }
    750 
    751         // Black list this "bad" AP, this will cause an attempt to connect to another
    752         blacklistAp(ap.bssid);
    753         // Initiate an association to an alternate AP
    754         mWifiStateTracker.reassociate();
    755     }
    756 
    757     private void blacklistAp(String bssid) {
    758         if (TextUtils.isEmpty(bssid)) {
    759             return;
    760         }
    761 
    762         // Before taking action, make sure we should not cancel our processing
    763         if (shouldCancel()) return;
    764 
    765         if (!mWifiStateTracker.addToBlacklist(bssid)) {
    766             // There's a known bug where this method returns failure on success
    767             //Slog.e(TAG, "Blacklisting " + bssid + " failed");
    768         }
    769 
    770         if (D) {
    771             myLogD("Blacklisting " + bssid);
    772         }
    773     }
    774 
    775     /**
    776      * Handles a single background check. If it fails, it should trigger a
    777      * normal check. If it succeeds, it should queue another background check.
    778      *
    779      * @param ap The access point to do a background check for. If this is no
    780      *        longer the current AP, it is okay to return without any
    781      *        processing.
    782      */
    783     private void handleBackgroundCheckAp(AccessPoint ap) {
    784         // Reset the cancel state since this is the entry point of this action
    785         mShouldCancel = false;
    786 
    787         if (false && V) {
    788             myLogV("handleBackgroundCheckAp: AccessPoint: " + ap);
    789         }
    790 
    791         // Make sure we are not sleeping
    792         if (mState == WatchdogState.SLEEP) {
    793             if (V) {
    794                 Slog.v(TAG, "  handleBackgroundCheckAp: Sleeping (in " + mSsid + "), so returning");
    795             }
    796             return;
    797         }
    798 
    799         // Make sure the AP we're supposed to be background checking is still the active one
    800         WifiInfo info = mWifiManager.getConnectionInfo();
    801         if (info.getSSID() == null || !info.getSSID().equals(ap.ssid)) {
    802             if (V) {
    803                 myLogV("handleBackgroundCheckAp: We are no longer connected to "
    804                         + ap + ", and instead are on " + info);
    805             }
    806             return;
    807         }
    808 
    809         if (info.getBSSID() == null || !info.getBSSID().equals(ap.bssid)) {
    810             if (V) {
    811                 myLogV("handleBackgroundCheckAp: We are no longer connected to "
    812                         + ap + ", and instead are on " + info);
    813             }
    814             return;
    815         }
    816 
    817         // Do the check
    818         boolean isApAlive = backgroundCheckDnsConnectivity();
    819 
    820         if (V && !isApAlive) {
    821             Slog.v(TAG, "  handleBackgroundCheckAp: Is it alive: " + isApAlive);
    822         }
    823 
    824         if (shouldCancel()) {
    825             return;
    826         }
    827 
    828         // Take action based on results
    829         if (isApAlive) {
    830             // Queue another background check
    831             mHandler.backgroundCheckAp(ap);
    832 
    833         } else {
    834             if (D) {
    835                 myLogD("Background check failed for " + ap.toString());
    836             }
    837 
    838             // Queue a normal check, so it can take proper action
    839             mHandler.checkAp(ap);
    840         }
    841     }
    842 
    843     /**
    844      * Handles going to sleep for this network. Going to sleep means we will not
    845      * monitor this network anymore.
    846      *
    847      * @param ssid The network that will not be monitored anymore.
    848      */
    849     private void handleSleep(String ssid) {
    850         // Make sure the network we're trying to sleep in is still the current network
    851         if (ssid != null && ssid.equals(mSsid)) {
    852             mState = WatchdogState.SLEEP;
    853 
    854             if (D) {
    855                 myLogD("Going to sleep for " + ssid);
    856             }
    857 
    858             /*
    859              * Before deciding to go to sleep, we may have checked a few APs
    860              * (and blacklisted them). Clear the blacklist so the AP with best
    861              * signal is chosen.
    862              */
    863             if (!mWifiStateTracker.clearBlacklist()) {
    864                 // There's a known bug where this method returns failure on success
    865                 //Slog.e(TAG, "Clearing blacklist failed");
    866             }
    867 
    868             if (V) {
    869                 myLogV("handleSleep: Set state to SLEEP and cleared blacklist");
    870             }
    871         }
    872     }
    873 
    874     /**
    875      * Handles an access point disconnection.
    876      */
    877     private void handleDisconnected() {
    878         /*
    879          * We purposefully do not change mSsid to null. This is to handle
    880          * disconnected followed by connected better (even if there is some
    881          * duration in between). For example, if the watchdog went to sleep in a
    882          * network, and then the phone goes to sleep, when the phone wakes up we
    883          * still want to be in the sleeping state. When the phone went to sleep,
    884          * we would have gotten a disconnected event which would then set mSsid
    885          * = null. This is bad, since the following connect would cause us to do
    886          * the "network is good?" check all over again. */
    887 
    888         /*
    889          * Set the state as if we were idle (don't come out of sleep, only
    890          * hard reset and network changed should do that.
    891          */
    892         setIdleState(false);
    893     }
    894 
    895     /**
    896      * Handles going idle. Idle means we are satisfied with the current state of
    897      * things, but if a new connection occurs we'll re-evaluate.
    898      */
    899     private void handleIdle() {
    900         // Reset the cancel state since this is the entry point for this action
    901         mShouldCancel = false;
    902 
    903         if (V) {
    904             myLogV("handleSwitchToIdle");
    905         }
    906 
    907         // If we're sleeping, don't do anything
    908         if (mState == WatchdogState.SLEEP) {
    909             Slog.v(TAG, "  Sleeping (in " + mSsid + "), so returning");
    910             return;
    911         }
    912 
    913         // Set the idle state
    914         setIdleState(false);
    915 
    916         if (V) {
    917             Slog.v(TAG, "  Set state to IDLE");
    918         }
    919     }
    920 
    921     /**
    922      * Sets the state as if we are going idle.
    923      */
    924     private void setIdleState(boolean forceIdleState) {
    925         // Setting idle state does not kick us out of sleep unless the forceIdleState is set
    926         if (forceIdleState || (mState != WatchdogState.SLEEP)) {
    927             mState = WatchdogState.IDLE;
    928         }
    929         mNumApsChecked = 0;
    930     }
    931 
    932     /**
    933      * Handles a hard reset. A hard reset is rarely used, but when used it
    934      * should revert anything done by the watchdog monitoring.
    935      */
    936     private void handleReset() {
    937         mWifiStateTracker.clearBlacklist();
    938         setIdleState(true);
    939     }
    940 
    941     // Inner classes
    942 
    943     /**
    944      * Possible states for the watchdog to be in.
    945      */
    946     private static enum WatchdogState {
    947         /** The watchdog is currently idle, but it is still responsive to future AP checks in this network. */
    948         IDLE,
    949         /** The watchdog is sleeping, so it will not try any AP checks for the network. */
    950         SLEEP,
    951         /** The watchdog is currently checking an AP for connectivity. */
    952         CHECKING_AP,
    953         /** The watchdog is switching to another AP in the network. */
    954         SWITCHING_AP
    955     }
    956 
    957     /**
    958      * The main thread for the watchdog monitoring. This will be turned into a
    959      * {@link Looper} thread.
    960      */
    961     private class WifiWatchdogThread extends Thread {
    962         WifiWatchdogThread() {
    963             super("WifiWatchdogThread");
    964         }
    965 
    966         @Override
    967         public void run() {
    968             // Set this thread up so the handler will work on it
    969             Looper.prepare();
    970 
    971             synchronized(WifiWatchdogService.this) {
    972                 mHandler = new WifiWatchdogHandler();
    973 
    974                 // Notify that the handler has been created
    975                 WifiWatchdogService.this.notify();
    976             }
    977 
    978             // Listen for messages to the handler
    979             Looper.loop();
    980         }
    981     }
    982 
    983     /**
    984      * The main thread's handler. There are 'actions', and just general
    985      * 'messages'. There should only ever be one 'action' in the queue (aside
    986      * from the one being processed, if any). There may be multiple messages in
    987      * the queue. So, actions are replaced by more recent actions, where as
    988      * messages will be executed for sure. Messages end up being used to just
    989      * change some state, and not really take any action.
    990      * <p>
    991      * There is little logic inside this class, instead methods of the form
    992      * "handle___" are called in the main {@link WifiWatchdogService}.
    993      */
    994     private class WifiWatchdogHandler extends Handler {
    995         /** Check whether the AP is "good".  The object will be an {@link AccessPoint}. */
    996         static final int ACTION_CHECK_AP = 1;
    997         /** Go into the idle state. */
    998         static final int ACTION_IDLE = 2;
    999         /**
   1000          * Performs a periodic background check whether the AP is still "good".
   1001          * The object will be an {@link AccessPoint}.
   1002          */
   1003         static final int ACTION_BACKGROUND_CHECK_AP = 3;
   1004 
   1005         /**
   1006          * Go to sleep for the current network. We are conservative with making
   1007          * this a message rather than action. We want to make sure our main
   1008          * thread sees this message, but if it were an action it could be
   1009          * removed from the queue and replaced by another action. The main
   1010          * thread will ensure when it sees the message that the state is still
   1011          * valid for going to sleep.
   1012          * <p>
   1013          * For an explanation of sleep, see {@link android.provider.Settings.Secure#WIFI_WATCHDOG_MAX_AP_CHECKS}.
   1014          */
   1015         static final int MESSAGE_SLEEP = 101;
   1016         /** Disables the watchdog. */
   1017         static final int MESSAGE_DISABLE_WATCHDOG = 102;
   1018         /** The network has changed. */
   1019         static final int MESSAGE_NETWORK_CHANGED = 103;
   1020         /** The current access point has disconnected. */
   1021         static final int MESSAGE_DISCONNECTED = 104;
   1022         /** Performs a hard-reset on the watchdog state. */
   1023         static final int MESSAGE_RESET = 105;
   1024 
   1025         void checkAp(AccessPoint ap) {
   1026             removeAllActions();
   1027             sendMessage(obtainMessage(ACTION_CHECK_AP, ap));
   1028         }
   1029 
   1030         void backgroundCheckAp(AccessPoint ap) {
   1031             if (!isBackgroundCheckEnabled()) return;
   1032 
   1033             removeAllActions();
   1034             sendMessageDelayed(obtainMessage(ACTION_BACKGROUND_CHECK_AP, ap),
   1035                     getBackgroundCheckDelayMs());
   1036         }
   1037 
   1038         void idle() {
   1039             removeAllActions();
   1040             sendMessage(obtainMessage(ACTION_IDLE));
   1041         }
   1042 
   1043         void sleep(String ssid) {
   1044             removeAllActions();
   1045             sendMessage(obtainMessage(MESSAGE_SLEEP, ssid));
   1046         }
   1047 
   1048         void disableWatchdog() {
   1049             removeAllActions();
   1050             sendMessage(obtainMessage(MESSAGE_DISABLE_WATCHDOG));
   1051         }
   1052 
   1053         void dispatchNetworkChanged(String ssid) {
   1054             removeAllActions();
   1055             sendMessage(obtainMessage(MESSAGE_NETWORK_CHANGED, ssid));
   1056         }
   1057 
   1058         void dispatchDisconnected() {
   1059             removeAllActions();
   1060             sendMessage(obtainMessage(MESSAGE_DISCONNECTED));
   1061         }
   1062 
   1063         void reset() {
   1064             removeAllActions();
   1065             sendMessage(obtainMessage(MESSAGE_RESET));
   1066         }
   1067 
   1068         private void removeAllActions() {
   1069             removeMessages(ACTION_CHECK_AP);
   1070             removeMessages(ACTION_IDLE);
   1071             removeMessages(ACTION_BACKGROUND_CHECK_AP);
   1072         }
   1073 
   1074         @Override
   1075         public void handleMessage(Message msg) {
   1076             switch (msg.what) {
   1077                 case MESSAGE_NETWORK_CHANGED:
   1078                     handleNetworkChanged((String) msg.obj);
   1079                     break;
   1080                 case ACTION_CHECK_AP:
   1081                     handleCheckAp((AccessPoint) msg.obj);
   1082                     break;
   1083                 case ACTION_BACKGROUND_CHECK_AP:
   1084                     handleBackgroundCheckAp((AccessPoint) msg.obj);
   1085                     break;
   1086                 case MESSAGE_SLEEP:
   1087                     handleSleep((String) msg.obj);
   1088                     break;
   1089                 case ACTION_IDLE:
   1090                     handleIdle();
   1091                     break;
   1092                 case MESSAGE_DISABLE_WATCHDOG:
   1093                     handleIdle();
   1094                     break;
   1095                 case MESSAGE_DISCONNECTED:
   1096                     handleDisconnected();
   1097                     break;
   1098                 case MESSAGE_RESET:
   1099                     handleReset();
   1100                     break;
   1101             }
   1102         }
   1103     }
   1104 
   1105     /**
   1106      * Receives Wi-Fi broadcasts.
   1107      * <p>
   1108      * There is little logic in this class, instead methods of the form "on___"
   1109      * are called in the {@link WifiWatchdogService}.
   1110      */
   1111     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
   1112 
   1113         @Override
   1114         public void onReceive(Context context, Intent intent) {
   1115             final String action = intent.getAction();
   1116             if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
   1117                 handleNetworkStateChanged(
   1118                         (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO));
   1119             } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
   1120                 handleWifiStateChanged(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
   1121                         WifiManager.WIFI_STATE_UNKNOWN));
   1122             }
   1123         }
   1124 
   1125         private void handleNetworkStateChanged(NetworkInfo info) {
   1126             if (V) {
   1127                 myLogV("Receiver.handleNetworkStateChanged: NetworkInfo: "
   1128                         + info);
   1129             }
   1130 
   1131             switch (info.getState()) {
   1132                 case CONNECTED:
   1133                     WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
   1134                     if (wifiInfo.getSSID() == null || wifiInfo.getBSSID() == null) {
   1135                         if (V) {
   1136                             myLogV("handleNetworkStateChanged: Got connected event but SSID or BSSID are null. SSID: "
   1137                                 + wifiInfo.getSSID()
   1138                                 + ", BSSID: "
   1139                                 + wifiInfo.getBSSID() + ", ignoring event");
   1140                         }
   1141                         return;
   1142                     }
   1143                     onConnected(wifiInfo.getSSID(), wifiInfo.getBSSID());
   1144                     break;
   1145 
   1146                 case DISCONNECTED:
   1147                     onDisconnected();
   1148                     break;
   1149             }
   1150         }
   1151 
   1152         private void handleWifiStateChanged(int wifiState) {
   1153             if (wifiState == WifiManager.WIFI_STATE_DISABLED) {
   1154                 quit();
   1155             } else if (wifiState == WifiManager.WIFI_STATE_ENABLED) {
   1156                 onEnabled();
   1157             }
   1158         }
   1159     };
   1160 
   1161     /**
   1162      * Describes an access point by its SSID and BSSID.
   1163      */
   1164     private static class AccessPoint {
   1165         String ssid;
   1166         String bssid;
   1167 
   1168         AccessPoint(String ssid, String bssid) {
   1169             this.ssid = ssid;
   1170             this.bssid = bssid;
   1171         }
   1172 
   1173         private boolean hasNull() {
   1174             return ssid == null || bssid == null;
   1175         }
   1176 
   1177         @Override
   1178         public boolean equals(Object o) {
   1179             if (!(o instanceof AccessPoint)) return false;
   1180             AccessPoint otherAp = (AccessPoint) o;
   1181             boolean iHaveNull = hasNull();
   1182             // Either we both have a null, or our SSIDs and BSSIDs are equal
   1183             return (iHaveNull && otherAp.hasNull()) ||
   1184                     (otherAp.bssid != null && ssid.equals(otherAp.ssid)
   1185                     && bssid.equals(otherAp.bssid));
   1186         }
   1187 
   1188         @Override
   1189         public int hashCode() {
   1190             if (ssid == null || bssid == null) return 0;
   1191             return ssid.hashCode() + bssid.hashCode();
   1192         }
   1193 
   1194         @Override
   1195         public String toString() {
   1196             return ssid + " (" + bssid + ")";
   1197         }
   1198     }
   1199 
   1200     /**
   1201      * Performs a simple DNS "ping" by sending a "server status" query packet to
   1202      * the DNS server. As long as the server replies, we consider it a success.
   1203      * <p>
   1204      * We do not use a simple hostname lookup because that could be cached and
   1205      * the API may not differentiate between a time out and a failure lookup
   1206      * (which we really care about).
   1207      */
   1208     private static class DnsPinger {
   1209 
   1210         /** Number of bytes for the query */
   1211         private static final int DNS_QUERY_BASE_SIZE = 33;
   1212 
   1213         /** The DNS port */
   1214         private static final int DNS_PORT = 53;
   1215 
   1216         /** Used to generate IDs */
   1217         private static Random sRandom = new Random();
   1218 
   1219         static boolean isDnsReachable(int dns, int timeout) {
   1220             DatagramSocket socket = null;
   1221             try {
   1222                 socket = new DatagramSocket();
   1223 
   1224                 // Set some socket properties
   1225                 socket.setSoTimeout(timeout);
   1226 
   1227                 byte[] buf = new byte[DNS_QUERY_BASE_SIZE];
   1228                 fillQuery(buf);
   1229 
   1230                 // Send the DNS query
   1231                 byte parts[] = new byte[4];
   1232                 parts[0] = (byte)(dns & 0xff);
   1233                 parts[1] = (byte)((dns >> 8) & 0xff);
   1234                 parts[2] = (byte)((dns >> 16) & 0xff);
   1235                 parts[3] = (byte)((dns >> 24) & 0xff);
   1236 
   1237                 InetAddress dnsAddress = InetAddress.getByAddress(parts);
   1238                 DatagramPacket packet = new DatagramPacket(buf,
   1239                         buf.length, dnsAddress, DNS_PORT);
   1240                 socket.send(packet);
   1241 
   1242                 // Wait for reply (blocks for the above timeout)
   1243                 DatagramPacket replyPacket = new DatagramPacket(buf, buf.length);
   1244                 socket.receive(replyPacket);
   1245 
   1246                 // If a timeout occurred, an exception would have been thrown.  We got a reply!
   1247                 return true;
   1248 
   1249             } catch (SocketException e) {
   1250                 if (V) {
   1251                     Slog.v(TAG, "DnsPinger.isReachable received SocketException", e);
   1252                 }
   1253                 return false;
   1254 
   1255             } catch (UnknownHostException e) {
   1256                 if (V) {
   1257                     Slog.v(TAG, "DnsPinger.isReachable is unable to resolve the DNS host", e);
   1258                 }
   1259                 return false;
   1260 
   1261             } catch (SocketTimeoutException e) {
   1262                 return false;
   1263 
   1264             } catch (IOException e) {
   1265                 if (V) {
   1266                     Slog.v(TAG, "DnsPinger.isReachable got an IOException", e);
   1267                 }
   1268                 return false;
   1269 
   1270             } catch (Exception e) {
   1271                 if (V || Config.LOGD) {
   1272                     Slog.d(TAG, "DnsPinger.isReachable got an unknown exception", e);
   1273                 }
   1274                 return false;
   1275             } finally {
   1276                 if (socket != null) {
   1277                     socket.close();
   1278                 }
   1279             }
   1280         }
   1281 
   1282         private static void fillQuery(byte[] buf) {
   1283 
   1284             /*
   1285              * See RFC2929 (though the bit tables in there are misleading for
   1286              * us. For example, the recursion desired bit is the 0th bit for us,
   1287              * but looking there it would appear as the 7th bit of the byte
   1288              */
   1289 
   1290             // Make sure it's all zeroed out
   1291             for (int i = 0; i < buf.length; i++) buf[i] = 0;
   1292 
   1293             // Form a query for www.android.com
   1294 
   1295             // [0-1] bytes are an ID, generate random ID for this query
   1296             buf[0] = (byte) sRandom.nextInt(256);
   1297             buf[1] = (byte) sRandom.nextInt(256);
   1298 
   1299             // [2-3] bytes are for flags.
   1300             buf[2] = 1; // Recursion desired
   1301 
   1302             // [4-5] bytes are for the query count
   1303             buf[5] = 1; // One query
   1304 
   1305             // [6-7] [8-9] [10-11] are all counts of other fields we don't use
   1306 
   1307             // [12-15] for www
   1308             writeString(buf, 12, "www");
   1309 
   1310             // [16-23] for android
   1311             writeString(buf, 16, "android");
   1312 
   1313             // [24-27] for com
   1314             writeString(buf, 24, "com");
   1315 
   1316             // [29-30] bytes are for QTYPE, set to 1
   1317             buf[30] = 1;
   1318 
   1319             // [31-32] bytes are for QCLASS, set to 1
   1320             buf[32] = 1;
   1321         }
   1322 
   1323         private static void writeString(byte[] buf, int startPos, String string) {
   1324             int pos = startPos;
   1325 
   1326             // Write the length first
   1327             buf[pos++] = (byte) string.length();
   1328             for (int i = 0; i < string.length(); i++) {
   1329                 buf[pos++] = (byte) string.charAt(i);
   1330             }
   1331         }
   1332     }
   1333 }
   1334