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