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