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
17package com.android.server;
18
19import android.content.BroadcastReceiver;
20import android.content.ContentResolver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.database.ContentObserver;
25import android.net.NetworkInfo;
26import android.net.DhcpInfo;
27import android.net.wifi.ScanResult;
28import android.net.wifi.WifiInfo;
29import android.net.wifi.WifiManager;
30import android.net.wifi.WifiStateTracker;
31import android.os.Handler;
32import android.os.Looper;
33import android.os.Message;
34import android.provider.Settings;
35import android.text.TextUtils;
36import android.util.Config;
37import android.util.Slog;
38
39import java.io.IOException;
40import java.net.DatagramPacket;
41import java.net.DatagramSocket;
42import java.net.InetAddress;
43import java.net.SocketException;
44import java.net.SocketTimeoutException;
45import java.net.UnknownHostException;
46import java.util.List;
47import 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 */
73public 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