blob: be14cd3d70f1773737ee5c600578cc1f05e25e6f [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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;
Joe Onorato8a9b2202010-02-26 18:56:32 -080037import android.util.Slog;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038
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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080078 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
Irfan Sheriff7b009782010-03-11 16:37:45 -080092 private ContentObserver mContentObserver;
93
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080094 /**
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,
Irfan Sheriff7b009782010-03-11 16:37:45 -0800137 mContentObserver = new ContentObserver(mHandler) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800138 @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);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800254 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 /**
Irfan Sheriff7b009782010-03-11 16:37:45 -0800276 * Unregister broadcasts and quit the watchdog thread
277 */
Irfan Sheriffa2a1b912010-06-07 09:03:04 -0700278 //TODO: Change back to running WWS when needed
279// private void quit() {
280// unregisterForWifiBroadcasts();
281// mContext.getContentResolver().unregisterContentObserver(mContentObserver);
282// mHandler.removeAllActions();
283// mHandler.getLooper().quit();
284// }
Irfan Sheriff7b009782010-03-11 16:37:45 -0800285
286 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800287 * Waits for the main watchdog thread to create the handler.
288 */
289 private void waitForHandlerCreation() {
290 synchronized(this) {
291 while (mHandler == null) {
292 try {
293 // Wait for the handler to be set by the other thread
294 wait();
295 } catch (InterruptedException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800296 Slog.e(TAG, "Interrupted while waiting on handler.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800297 }
298 }
299 }
300 }
301
302 // Utility methods
303
304 /**
305 * Logs with the current thread.
306 */
307 private static void myLogV(String message) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800308 Slog.v(TAG, "(" + Thread.currentThread().getName() + ") " + message);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800309 }
310
311 private static void myLogD(String message) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800312 Slog.d(TAG, "(" + Thread.currentThread().getName() + ") " + message);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800313 }
314
315 /**
316 * Gets the DNS of the current AP.
317 *
318 * @return The DNS of the current AP.
319 */
320 private int getDns() {
321 DhcpInfo addressInfo = mWifiManager.getDhcpInfo();
322 if (addressInfo != null) {
323 return addressInfo.dns1;
324 } else {
325 return -1;
326 }
327 }
328
329 /**
330 * Checks whether the DNS can be reached using multiple attempts according
331 * to the current setting values.
332 *
333 * @return Whether the DNS is reachable
334 */
335 private boolean checkDnsConnectivity() {
336 int dns = getDns();
337 if (dns == -1) {
338 if (V) {
339 myLogV("checkDnsConnectivity: Invalid DNS, returning false");
340 }
341 return false;
342 }
343
344 if (V) {
345 myLogV("checkDnsConnectivity: Checking 0x" +
346 Integer.toHexString(Integer.reverseBytes(dns)) + " for connectivity");
347 }
348
349 int numInitialIgnoredPings = getInitialIgnoredPingCount();
350 int numPings = getPingCount();
351 int pingDelay = getPingDelayMs();
352 int acceptableLoss = getAcceptablePacketLossPercentage();
353
354 /** See {@link Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT} */
355 int ignoredPingCounter = 0;
356 int pingCounter = 0;
357 int successCounter = 0;
358
359 // No connectivity check needed
360 if (numPings == 0) {
361 return true;
362 }
363
364 // Do the initial pings that we ignore
365 for (; ignoredPingCounter < numInitialIgnoredPings; ignoredPingCounter++) {
366 if (shouldCancel()) return false;
367
368 boolean dnsAlive = DnsPinger.isDnsReachable(dns, getPingTimeoutMs());
369 if (dnsAlive) {
370 /*
371 * Successful "ignored" pings are *not* ignored (they count in the total number
372 * of pings), but failures are really ignored.
373 */
374 pingCounter++;
375 successCounter++;
376 }
377
378 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800379 Slog.v(TAG, (dnsAlive ? " +" : " Ignored: -"));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800380 }
381
382 if (shouldCancel()) return false;
383
384 try {
385 Thread.sleep(pingDelay);
386 } catch (InterruptedException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800387 Slog.w(TAG, "Interrupted while pausing between pings", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800388 }
389 }
390
391 // Do the pings that we use to measure packet loss
392 for (; pingCounter < numPings; pingCounter++) {
393 if (shouldCancel()) return false;
394
395 if (DnsPinger.isDnsReachable(dns, getPingTimeoutMs())) {
396 successCounter++;
397 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800398 Slog.v(TAG, " +");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800399 }
400 } else {
401 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800402 Slog.v(TAG, " -");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800403 }
404 }
405
406 if (shouldCancel()) return false;
407
408 try {
409 Thread.sleep(pingDelay);
410 } catch (InterruptedException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800411 Slog.w(TAG, "Interrupted while pausing between pings", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800412 }
413 }
414
415 int packetLossPercentage = 100 * (numPings - successCounter) / numPings;
416 if (D) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800417 Slog.d(TAG, packetLossPercentage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800418 + "% packet loss (acceptable is " + acceptableLoss + "%)");
419 }
420
421 return !shouldCancel() && (packetLossPercentage <= acceptableLoss);
422 }
423
424 private boolean backgroundCheckDnsConnectivity() {
425 int dns = getDns();
426 if (false && V) {
427 myLogV("backgroundCheckDnsConnectivity: Background checking " + dns +
428 " for connectivity");
429 }
430
431 if (dns == -1) {
432 if (V) {
433 myLogV("backgroundCheckDnsConnectivity: DNS is empty, returning false");
434 }
435 return false;
436 }
437
438 return DnsPinger.isDnsReachable(dns, getBackgroundCheckTimeoutMs());
439 }
440
441 /**
442 * Signals the current action to cancel.
443 */
444 private void cancelCurrentAction() {
445 mShouldCancel = true;
446 }
447
448 /**
449 * Helper to check whether to cancel.
450 *
451 * @return Whether to cancel processing the action.
452 */
453 private boolean shouldCancel() {
454 if (V && mShouldCancel) {
455 myLogV("shouldCancel: Cancelling");
456 }
457
458 return mShouldCancel;
459 }
460
461 // Wi-Fi initiated callbacks (could be executed in another thread)
462
463 /**
464 * Called when connected to an AP (this can be the next AP in line, or
465 * it can be a completely different network).
466 *
467 * @param ssid The SSID of the access point.
468 * @param bssid The BSSID of the access point.
469 */
470 private void onConnected(String ssid, String bssid) {
471 if (V) {
472 myLogV("onConnected: SSID: " + ssid + ", BSSID: " + bssid);
473 }
474
475 /*
476 * The current action being processed by the main watchdog thread is now
477 * stale, so cancel it.
478 */
479 cancelCurrentAction();
480
481 if ((mSsid == null) || !mSsid.equals(ssid)) {
482 /*
483 * This is a different network than what the main watchdog thread is
484 * processing, dispatch the network change message on the main thread.
485 */
486 mHandler.dispatchNetworkChanged(ssid);
487 }
488
489 if (requiresWatchdog(ssid, bssid)) {
490 if (D) {
491 myLogD(ssid + " (" + bssid + ") requires the watchdog");
492 }
493
494 // This access point requires a watchdog, so queue the check on the main thread
495 mHandler.checkAp(new AccessPoint(ssid, bssid));
496
497 } else {
498 if (D) {
499 myLogD(ssid + " (" + bssid + ") does not require the watchdog");
500 }
501
502 // This access point does not require a watchdog, so queue idle on the main thread
503 mHandler.idle();
504 }
505 }
506
507 /**
508 * Called when Wi-Fi is enabled.
509 */
510 private void onEnabled() {
511 cancelCurrentAction();
512 // Queue a hard-reset of the state on the main thread
513 mHandler.reset();
514 }
515
516 /**
517 * Called when disconnected (or some other event similar to being disconnected).
518 */
519 private void onDisconnected() {
520 if (V) {
521 myLogV("onDisconnected");
522 }
523
524 /*
525 * Disconnected from an access point, the action being processed by the
526 * watchdog thread is now stale, so cancel it.
527 */
528 cancelCurrentAction();
529 // Dispatch the disconnected to the main watchdog thread
530 mHandler.dispatchDisconnected();
531 // Queue the action to go idle
532 mHandler.idle();
533 }
534
535 /**
536 * Checks whether an access point requires watchdog monitoring.
537 *
538 * @param ssid The SSID of the access point.
539 * @param bssid The BSSID of the access point.
540 * @return Whether the access point/network should be monitored by the
541 * watchdog.
542 */
543 private boolean requiresWatchdog(String ssid, String bssid) {
544 if (V) {
545 myLogV("requiresWatchdog: SSID: " + ssid + ", BSSID: " + bssid);
546 }
547
548 WifiInfo info = null;
549 if (ssid == null) {
550 /*
551 * This is called from a Wi-Fi callback, so assume the WifiInfo does
552 * not have stale data.
553 */
554 info = mWifiManager.getConnectionInfo();
555 ssid = info.getSSID();
556 if (ssid == null) {
557 // It's still null, give up
558 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800559 Slog.v(TAG, " Invalid SSID, returning false");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800560 }
561 return false;
562 }
563 }
564
565 if (TextUtils.isEmpty(bssid)) {
566 // Similar as above
567 if (info == null) {
568 info = mWifiManager.getConnectionInfo();
569 }
570 bssid = info.getBSSID();
571 if (TextUtils.isEmpty(bssid)) {
572 // It's still null, give up
573 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800574 Slog.v(TAG, " Invalid BSSID, returning false");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800575 }
576 return false;
577 }
578 }
579
580 if (!isOnWatchList(ssid)) {
581 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800582 Slog.v(TAG, " SSID not on watch list, returning false");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800583 }
584 return false;
585 }
586
587 // The watchdog only monitors networks with multiple APs
588 if (!hasRequiredNumberOfAps(ssid)) {
589 return false;
590 }
591
592 return true;
593 }
594
595 private boolean isOnWatchList(String ssid) {
596 String watchList;
597
598 if (ssid == null || (watchList = getWatchList()) == null) {
599 return false;
600 }
601
602 String[] list = watchList.split(" *, *");
603
604 for (String name : list) {
605 if (ssid.equals(name)) {
606 return true;
607 }
608 }
609
610 return false;
611 }
612
613 /**
614 * Checks if the current scan results have multiple access points with an SSID.
615 *
616 * @param ssid The SSID to check.
617 * @return Whether the SSID has multiple access points.
618 */
619 private boolean hasRequiredNumberOfAps(String ssid) {
620 List<ScanResult> results = mWifiManager.getScanResults();
621 if (results == null) {
622 if (V) {
623 myLogV("hasRequiredNumberOfAps: Got null scan results, returning false");
624 }
625 return false;
626 }
627
628 int numApsRequired = getApCount();
629 int numApsFound = 0;
630 int resultsSize = results.size();
631 for (int i = 0; i < resultsSize; i++) {
632 ScanResult result = results.get(i);
633 if (result == null) continue;
634 if (result.SSID == null) continue;
635
636 if (result.SSID.equals(ssid)) {
637 numApsFound++;
638
639 if (numApsFound >= numApsRequired) {
640 if (V) {
641 myLogV("hasRequiredNumberOfAps: SSID: " + ssid + ", returning true");
642 }
643 return true;
644 }
645 }
646 }
647
648 if (V) {
649 myLogV("hasRequiredNumberOfAps: SSID: " + ssid + ", returning false");
650 }
651 return false;
652 }
653
654 // Watchdog logic (assume all of these methods will be in our main thread)
655
656 /**
657 * Handles a Wi-Fi network change (for example, from networkA to networkB).
658 */
659 private void handleNetworkChanged(String ssid) {
660 // Set the SSID being monitored to the new SSID
661 mSsid = ssid;
662 // Set various state to that when being idle
663 setIdleState(true);
664 }
665
666 /**
667 * Handles checking whether an AP is a "good" AP. If not, it will be blacklisted.
668 *
669 * @param ap The access point to check.
670 */
671 private void handleCheckAp(AccessPoint ap) {
672 // Reset the cancel state since this is the entry point of this action
673 mShouldCancel = false;
674
675 if (V) {
676 myLogV("handleCheckAp: AccessPoint: " + ap);
677 }
678
679 // Make sure we are not sleeping
680 if (mState == WatchdogState.SLEEP) {
681 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800682 Slog.v(TAG, " Sleeping (in " + mSsid + "), so returning");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800683 }
684 return;
685 }
686
687 mState = WatchdogState.CHECKING_AP;
688
689 /*
690 * Checks to make sure we haven't exceeded the max number of checks
691 * we're allowed per network
692 */
693 mNumApsChecked++;
694 if (mNumApsChecked > getMaxApChecks()) {
695 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800696 Slog.v(TAG, " Passed the max attempts (" + getMaxApChecks()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800697 + "), going to sleep for " + mSsid);
698 }
699 mHandler.sleep(mSsid);
700 return;
701 }
702
703 // Do the check
704 boolean isApAlive = checkDnsConnectivity();
705
706 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800707 Slog.v(TAG, " Is it alive: " + isApAlive);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800708 }
709
710 // Take action based on results
711 if (isApAlive) {
712 handleApAlive(ap);
713 } else {
714 handleApUnresponsive(ap);
715 }
716 }
717
718 /**
719 * Handles the case when an access point is alive.
720 *
721 * @param ap The access point.
722 */
723 private void handleApAlive(AccessPoint ap) {
724 // Check whether we are stale and should cancel
725 if (shouldCancel()) return;
726 // We're satisfied with this AP, so go idle
727 setIdleState(false);
728
729 if (D) {
730 myLogD("AP is alive: " + ap.toString());
731 }
732
733 // Queue the next action to be a background check
734 mHandler.backgroundCheckAp(ap);
735 }
736
737 /**
738 * Handles an unresponsive AP by blacklisting it.
739 *
740 * @param ap The access point.
741 */
742 private void handleApUnresponsive(AccessPoint ap) {
743 // Check whether we are stale and should cancel
744 if (shouldCancel()) return;
745 // This AP is "bad", switch to another
746 mState = WatchdogState.SWITCHING_AP;
747
748 if (D) {
749 myLogD("AP is dead: " + ap.toString());
750 }
751
752 // Black list this "bad" AP, this will cause an attempt to connect to another
753 blacklistAp(ap.bssid);
Irfan Sheriff0049a1b2010-01-14 12:37:49 -0800754 // Initiate an association to an alternate AP
Irfan Sheriffa2a1b912010-06-07 09:03:04 -0700755 mWifiStateTracker.reassociateCommand();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800756 }
757
758 private void blacklistAp(String bssid) {
759 if (TextUtils.isEmpty(bssid)) {
760 return;
761 }
762
763 // Before taking action, make sure we should not cancel our processing
764 if (shouldCancel()) return;
765
Irfan Sheriffa2a1b912010-06-07 09:03:04 -0700766 mWifiStateTracker.addToBlacklist(bssid);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800767
768 if (D) {
769 myLogD("Blacklisting " + bssid);
770 }
771 }
772
773 /**
774 * Handles a single background check. If it fails, it should trigger a
775 * normal check. If it succeeds, it should queue another background check.
776 *
777 * @param ap The access point to do a background check for. If this is no
778 * longer the current AP, it is okay to return without any
779 * processing.
780 */
781 private void handleBackgroundCheckAp(AccessPoint ap) {
782 // Reset the cancel state since this is the entry point of this action
783 mShouldCancel = false;
784
785 if (false && V) {
786 myLogV("handleBackgroundCheckAp: AccessPoint: " + ap);
787 }
788
789 // Make sure we are not sleeping
790 if (mState == WatchdogState.SLEEP) {
791 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800792 Slog.v(TAG, " handleBackgroundCheckAp: Sleeping (in " + mSsid + "), so returning");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800793 }
794 return;
795 }
796
797 // Make sure the AP we're supposed to be background checking is still the active one
798 WifiInfo info = mWifiManager.getConnectionInfo();
799 if (info.getSSID() == null || !info.getSSID().equals(ap.ssid)) {
800 if (V) {
801 myLogV("handleBackgroundCheckAp: We are no longer connected to "
802 + ap + ", and instead are on " + info);
803 }
804 return;
805 }
806
807 if (info.getBSSID() == null || !info.getBSSID().equals(ap.bssid)) {
808 if (V) {
809 myLogV("handleBackgroundCheckAp: We are no longer connected to "
810 + ap + ", and instead are on " + info);
811 }
812 return;
813 }
814
815 // Do the check
816 boolean isApAlive = backgroundCheckDnsConnectivity();
817
818 if (V && !isApAlive) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800819 Slog.v(TAG, " handleBackgroundCheckAp: Is it alive: " + isApAlive);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800820 }
821
822 if (shouldCancel()) {
823 return;
824 }
825
826 // Take action based on results
827 if (isApAlive) {
828 // Queue another background check
829 mHandler.backgroundCheckAp(ap);
830
831 } else {
832 if (D) {
833 myLogD("Background check failed for " + ap.toString());
834 }
835
836 // Queue a normal check, so it can take proper action
837 mHandler.checkAp(ap);
838 }
839 }
840
841 /**
842 * Handles going to sleep for this network. Going to sleep means we will not
843 * monitor this network anymore.
844 *
845 * @param ssid The network that will not be monitored anymore.
846 */
847 private void handleSleep(String ssid) {
848 // Make sure the network we're trying to sleep in is still the current network
849 if (ssid != null && ssid.equals(mSsid)) {
850 mState = WatchdogState.SLEEP;
851
852 if (D) {
853 myLogD("Going to sleep for " + ssid);
854 }
855
856 /*
857 * Before deciding to go to sleep, we may have checked a few APs
858 * (and blacklisted them). Clear the blacklist so the AP with best
859 * signal is chosen.
860 */
Irfan Sheriffa2a1b912010-06-07 09:03:04 -0700861 mWifiStateTracker.clearBlacklist();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800862
863 if (V) {
864 myLogV("handleSleep: Set state to SLEEP and cleared blacklist");
865 }
866 }
867 }
868
869 /**
870 * Handles an access point disconnection.
871 */
872 private void handleDisconnected() {
873 /*
874 * We purposefully do not change mSsid to null. This is to handle
875 * disconnected followed by connected better (even if there is some
876 * duration in between). For example, if the watchdog went to sleep in a
877 * network, and then the phone goes to sleep, when the phone wakes up we
878 * still want to be in the sleeping state. When the phone went to sleep,
879 * we would have gotten a disconnected event which would then set mSsid
880 * = null. This is bad, since the following connect would cause us to do
881 * the "network is good?" check all over again. */
882
883 /*
884 * Set the state as if we were idle (don't come out of sleep, only
885 * hard reset and network changed should do that.
886 */
887 setIdleState(false);
888 }
889
890 /**
891 * Handles going idle. Idle means we are satisfied with the current state of
892 * things, but if a new connection occurs we'll re-evaluate.
893 */
894 private void handleIdle() {
895 // Reset the cancel state since this is the entry point for this action
896 mShouldCancel = false;
897
898 if (V) {
899 myLogV("handleSwitchToIdle");
900 }
901
902 // If we're sleeping, don't do anything
903 if (mState == WatchdogState.SLEEP) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800904 Slog.v(TAG, " Sleeping (in " + mSsid + "), so returning");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800905 return;
906 }
907
908 // Set the idle state
909 setIdleState(false);
910
911 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800912 Slog.v(TAG, " Set state to IDLE");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800913 }
914 }
915
916 /**
917 * Sets the state as if we are going idle.
918 */
919 private void setIdleState(boolean forceIdleState) {
920 // Setting idle state does not kick us out of sleep unless the forceIdleState is set
921 if (forceIdleState || (mState != WatchdogState.SLEEP)) {
922 mState = WatchdogState.IDLE;
923 }
924 mNumApsChecked = 0;
925 }
926
927 /**
928 * Handles a hard reset. A hard reset is rarely used, but when used it
929 * should revert anything done by the watchdog monitoring.
930 */
931 private void handleReset() {
932 mWifiStateTracker.clearBlacklist();
933 setIdleState(true);
934 }
935
936 // Inner classes
937
938 /**
939 * Possible states for the watchdog to be in.
940 */
941 private static enum WatchdogState {
942 /** The watchdog is currently idle, but it is still responsive to future AP checks in this network. */
943 IDLE,
944 /** The watchdog is sleeping, so it will not try any AP checks for the network. */
945 SLEEP,
946 /** The watchdog is currently checking an AP for connectivity. */
947 CHECKING_AP,
948 /** The watchdog is switching to another AP in the network. */
949 SWITCHING_AP
950 }
951
952 /**
953 * The main thread for the watchdog monitoring. This will be turned into a
954 * {@link Looper} thread.
955 */
956 private class WifiWatchdogThread extends Thread {
957 WifiWatchdogThread() {
958 super("WifiWatchdogThread");
959 }
960
961 @Override
962 public void run() {
963 // Set this thread up so the handler will work on it
964 Looper.prepare();
965
966 synchronized(WifiWatchdogService.this) {
967 mHandler = new WifiWatchdogHandler();
968
969 // Notify that the handler has been created
970 WifiWatchdogService.this.notify();
971 }
972
973 // Listen for messages to the handler
974 Looper.loop();
975 }
976 }
977
978 /**
979 * The main thread's handler. There are 'actions', and just general
980 * 'messages'. There should only ever be one 'action' in the queue (aside
981 * from the one being processed, if any). There may be multiple messages in
982 * the queue. So, actions are replaced by more recent actions, where as
983 * messages will be executed for sure. Messages end up being used to just
984 * change some state, and not really take any action.
985 * <p>
986 * There is little logic inside this class, instead methods of the form
987 * "handle___" are called in the main {@link WifiWatchdogService}.
988 */
989 private class WifiWatchdogHandler extends Handler {
990 /** Check whether the AP is "good". The object will be an {@link AccessPoint}. */
991 static final int ACTION_CHECK_AP = 1;
992 /** Go into the idle state. */
993 static final int ACTION_IDLE = 2;
994 /**
995 * Performs a periodic background check whether the AP is still "good".
996 * The object will be an {@link AccessPoint}.
997 */
998 static final int ACTION_BACKGROUND_CHECK_AP = 3;
999
1000 /**
1001 * Go to sleep for the current network. We are conservative with making
1002 * this a message rather than action. We want to make sure our main
1003 * thread sees this message, but if it were an action it could be
1004 * removed from the queue and replaced by another action. The main
1005 * thread will ensure when it sees the message that the state is still
1006 * valid for going to sleep.
1007 * <p>
1008 * For an explanation of sleep, see {@link android.provider.Settings.Secure#WIFI_WATCHDOG_MAX_AP_CHECKS}.
1009 */
1010 static final int MESSAGE_SLEEP = 101;
1011 /** Disables the watchdog. */
1012 static final int MESSAGE_DISABLE_WATCHDOG = 102;
1013 /** The network has changed. */
1014 static final int MESSAGE_NETWORK_CHANGED = 103;
1015 /** The current access point has disconnected. */
1016 static final int MESSAGE_DISCONNECTED = 104;
1017 /** Performs a hard-reset on the watchdog state. */
1018 static final int MESSAGE_RESET = 105;
1019
1020 void checkAp(AccessPoint ap) {
1021 removeAllActions();
1022 sendMessage(obtainMessage(ACTION_CHECK_AP, ap));
1023 }
1024
1025 void backgroundCheckAp(AccessPoint ap) {
1026 if (!isBackgroundCheckEnabled()) return;
1027
1028 removeAllActions();
1029 sendMessageDelayed(obtainMessage(ACTION_BACKGROUND_CHECK_AP, ap),
1030 getBackgroundCheckDelayMs());
1031 }
1032
1033 void idle() {
1034 removeAllActions();
1035 sendMessage(obtainMessage(ACTION_IDLE));
1036 }
1037
1038 void sleep(String ssid) {
1039 removeAllActions();
1040 sendMessage(obtainMessage(MESSAGE_SLEEP, ssid));
1041 }
1042
1043 void disableWatchdog() {
1044 removeAllActions();
1045 sendMessage(obtainMessage(MESSAGE_DISABLE_WATCHDOG));
1046 }
1047
1048 void dispatchNetworkChanged(String ssid) {
1049 removeAllActions();
1050 sendMessage(obtainMessage(MESSAGE_NETWORK_CHANGED, ssid));
1051 }
1052
1053 void dispatchDisconnected() {
1054 removeAllActions();
1055 sendMessage(obtainMessage(MESSAGE_DISCONNECTED));
1056 }
1057
1058 void reset() {
1059 removeAllActions();
1060 sendMessage(obtainMessage(MESSAGE_RESET));
1061 }
1062
1063 private void removeAllActions() {
1064 removeMessages(ACTION_CHECK_AP);
1065 removeMessages(ACTION_IDLE);
1066 removeMessages(ACTION_BACKGROUND_CHECK_AP);
1067 }
1068
1069 @Override
1070 public void handleMessage(Message msg) {
1071 switch (msg.what) {
1072 case MESSAGE_NETWORK_CHANGED:
1073 handleNetworkChanged((String) msg.obj);
1074 break;
1075 case ACTION_CHECK_AP:
1076 handleCheckAp((AccessPoint) msg.obj);
1077 break;
1078 case ACTION_BACKGROUND_CHECK_AP:
1079 handleBackgroundCheckAp((AccessPoint) msg.obj);
1080 break;
1081 case MESSAGE_SLEEP:
1082 handleSleep((String) msg.obj);
1083 break;
1084 case ACTION_IDLE:
1085 handleIdle();
1086 break;
1087 case MESSAGE_DISABLE_WATCHDOG:
1088 handleIdle();
1089 break;
1090 case MESSAGE_DISCONNECTED:
1091 handleDisconnected();
1092 break;
1093 case MESSAGE_RESET:
1094 handleReset();
1095 break;
1096 }
1097 }
1098 }
1099
1100 /**
1101 * Receives Wi-Fi broadcasts.
1102 * <p>
1103 * There is little logic in this class, instead methods of the form "on___"
1104 * are called in the {@link WifiWatchdogService}.
1105 */
1106 private BroadcastReceiver mReceiver = new BroadcastReceiver() {
1107
1108 @Override
1109 public void onReceive(Context context, Intent intent) {
1110 final String action = intent.getAction();
1111 if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
1112 handleNetworkStateChanged(
1113 (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001114 } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
1115 handleWifiStateChanged(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
1116 WifiManager.WIFI_STATE_UNKNOWN));
1117 }
1118 }
1119
1120 private void handleNetworkStateChanged(NetworkInfo info) {
1121 if (V) {
1122 myLogV("Receiver.handleNetworkStateChanged: NetworkInfo: "
1123 + info);
1124 }
1125
1126 switch (info.getState()) {
1127 case CONNECTED:
1128 WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
1129 if (wifiInfo.getSSID() == null || wifiInfo.getBSSID() == null) {
1130 if (V) {
1131 myLogV("handleNetworkStateChanged: Got connected event but SSID or BSSID are null. SSID: "
1132 + wifiInfo.getSSID()
1133 + ", BSSID: "
1134 + wifiInfo.getBSSID() + ", ignoring event");
1135 }
1136 return;
1137 }
1138 onConnected(wifiInfo.getSSID(), wifiInfo.getBSSID());
1139 break;
1140
1141 case DISCONNECTED:
1142 onDisconnected();
1143 break;
1144 }
1145 }
1146
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001147 private void handleWifiStateChanged(int wifiState) {
1148 if (wifiState == WifiManager.WIFI_STATE_DISABLED) {
Irfan Sheriffa2a1b912010-06-07 09:03:04 -07001149 onDisconnected();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001150 } else if (wifiState == WifiManager.WIFI_STATE_ENABLED) {
1151 onEnabled();
1152 }
1153 }
1154 };
1155
1156 /**
1157 * Describes an access point by its SSID and BSSID.
1158 */
1159 private static class AccessPoint {
1160 String ssid;
1161 String bssid;
1162
1163 AccessPoint(String ssid, String bssid) {
1164 this.ssid = ssid;
1165 this.bssid = bssid;
1166 }
1167
1168 private boolean hasNull() {
1169 return ssid == null || bssid == null;
1170 }
1171
1172 @Override
1173 public boolean equals(Object o) {
1174 if (!(o instanceof AccessPoint)) return false;
1175 AccessPoint otherAp = (AccessPoint) o;
1176 boolean iHaveNull = hasNull();
1177 // Either we both have a null, or our SSIDs and BSSIDs are equal
1178 return (iHaveNull && otherAp.hasNull()) ||
1179 (otherAp.bssid != null && ssid.equals(otherAp.ssid)
1180 && bssid.equals(otherAp.bssid));
1181 }
1182
1183 @Override
1184 public int hashCode() {
1185 if (ssid == null || bssid == null) return 0;
1186 return ssid.hashCode() + bssid.hashCode();
1187 }
1188
1189 @Override
1190 public String toString() {
1191 return ssid + " (" + bssid + ")";
1192 }
1193 }
1194
1195 /**
1196 * Performs a simple DNS "ping" by sending a "server status" query packet to
1197 * the DNS server. As long as the server replies, we consider it a success.
1198 * <p>
1199 * We do not use a simple hostname lookup because that could be cached and
1200 * the API may not differentiate between a time out and a failure lookup
1201 * (which we really care about).
1202 */
1203 private static class DnsPinger {
1204
1205 /** Number of bytes for the query */
1206 private static final int DNS_QUERY_BASE_SIZE = 33;
1207
1208 /** The DNS port */
1209 private static final int DNS_PORT = 53;
1210
1211 /** Used to generate IDs */
1212 private static Random sRandom = new Random();
1213
1214 static boolean isDnsReachable(int dns, int timeout) {
Nick Kralevich929b4852010-06-09 14:27:43 -07001215 DatagramSocket socket = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001216 try {
Nick Kralevich929b4852010-06-09 14:27:43 -07001217 socket = new DatagramSocket();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001218
1219 // Set some socket properties
1220 socket.setSoTimeout(timeout);
1221
1222 byte[] buf = new byte[DNS_QUERY_BASE_SIZE];
1223 fillQuery(buf);
1224
1225 // Send the DNS query
1226 byte parts[] = new byte[4];
1227 parts[0] = (byte)(dns & 0xff);
1228 parts[1] = (byte)((dns >> 8) & 0xff);
1229 parts[2] = (byte)((dns >> 16) & 0xff);
1230 parts[3] = (byte)((dns >> 24) & 0xff);
1231
1232 InetAddress dnsAddress = InetAddress.getByAddress(parts);
1233 DatagramPacket packet = new DatagramPacket(buf,
1234 buf.length, dnsAddress, DNS_PORT);
1235 socket.send(packet);
1236
1237 // Wait for reply (blocks for the above timeout)
1238 DatagramPacket replyPacket = new DatagramPacket(buf, buf.length);
1239 socket.receive(replyPacket);
1240
1241 // If a timeout occurred, an exception would have been thrown. We got a reply!
1242 return true;
1243
1244 } catch (SocketException e) {
1245 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001246 Slog.v(TAG, "DnsPinger.isReachable received SocketException", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001247 }
1248 return false;
1249
1250 } catch (UnknownHostException e) {
1251 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001252 Slog.v(TAG, "DnsPinger.isReachable is unable to resolve the DNS host", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001253 }
1254 return false;
1255
1256 } catch (SocketTimeoutException e) {
1257 return false;
1258
1259 } catch (IOException e) {
1260 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001261 Slog.v(TAG, "DnsPinger.isReachable got an IOException", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001262 }
1263 return false;
1264
1265 } catch (Exception e) {
1266 if (V || Config.LOGD) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001267 Slog.d(TAG, "DnsPinger.isReachable got an unknown exception", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001268 }
1269 return false;
Nick Kralevich929b4852010-06-09 14:27:43 -07001270 } finally {
1271 if (socket != null) {
1272 socket.close();
1273 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001274 }
1275 }
1276
1277 private static void fillQuery(byte[] buf) {
1278
1279 /*
1280 * See RFC2929 (though the bit tables in there are misleading for
1281 * us. For example, the recursion desired bit is the 0th bit for us,
1282 * but looking there it would appear as the 7th bit of the byte
1283 */
1284
1285 // Make sure it's all zeroed out
1286 for (int i = 0; i < buf.length; i++) buf[i] = 0;
1287
1288 // Form a query for www.android.com
1289
1290 // [0-1] bytes are an ID, generate random ID for this query
1291 buf[0] = (byte) sRandom.nextInt(256);
1292 buf[1] = (byte) sRandom.nextInt(256);
1293
1294 // [2-3] bytes are for flags.
1295 buf[2] = 1; // Recursion desired
1296
1297 // [4-5] bytes are for the query count
1298 buf[5] = 1; // One query
1299
1300 // [6-7] [8-9] [10-11] are all counts of other fields we don't use
1301
1302 // [12-15] for www
1303 writeString(buf, 12, "www");
1304
1305 // [16-23] for android
1306 writeString(buf, 16, "android");
1307
1308 // [24-27] for com
1309 writeString(buf, 24, "com");
1310
1311 // [29-30] bytes are for QTYPE, set to 1
1312 buf[30] = 1;
1313
1314 // [31-32] bytes are for QCLASS, set to 1
1315 buf[32] = 1;
1316 }
1317
1318 private static void writeString(byte[] buf, int startPos, String string) {
1319 int pos = startPos;
1320
1321 // Write the length first
1322 buf[pos++] = (byte) string.length();
1323 for (int i = 0; i < string.length(); i++) {
1324 buf[pos++] = (byte) string.charAt(i);
1325 }
1326 }
1327 }
1328}