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