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