blob: 6ef6963c8a6913169e8b1a0c1260ef53cc98c62f [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;
Robert Greenwalt8e9abc52011-02-16 10:37:50 -080025import android.net.ConnectivityManager;
26import android.net.LinkProperties;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.net.NetworkInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.net.wifi.ScanResult;
29import android.net.wifi.WifiInfo;
30import android.net.wifi.WifiManager;
Irfan Sheriff2f5f4bc2011-05-02 11:43:28 -070031import android.net.Uri;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import android.os.Handler;
33import android.os.Looper;
34import android.os.Message;
35import android.provider.Settings;
36import android.text.TextUtils;
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;
Robert Greenwalt8e9abc52011-02-16 10:37:50 -080046import java.util.Collection;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047import java.util.List;
48import java.util.Random;
49
50/**
51 * {@link WifiWatchdogService} monitors the initial connection to a Wi-Fi
52 * network with multiple access points. After the framework successfully
53 * connects to an access point, the watchdog verifies whether the DNS server is
54 * reachable. If not, the watchdog blacklists the current access point, leading
55 * to a connection on another access point within the same network.
56 * <p>
57 * The watchdog has a few safeguards:
58 * <ul>
59 * <li>Only monitor networks with multiple access points
60 * <li>Only check at most {@link #getMaxApChecks()} different access points
61 * within the network before giving up
62 * <p>
63 * The watchdog checks for connectivity on an access point by ICMP pinging the
64 * DNS. There are settings that allow disabling the watchdog, or tweaking the
65 * acceptable packet loss (and other various parameters).
66 * <p>
67 * The core logic of the watchdog is done on the main watchdog thread. Wi-Fi
68 * callbacks can come in on other threads, so we must queue messages to the main
69 * watchdog thread's handler. Most (if not all) state is only written to from
70 * the main thread.
71 *
72 * {@hide}
73 */
74public class WifiWatchdogService {
75 private static final String TAG = "WifiWatchdogService";
Joe Onorato43a17652011-04-06 19:22:23 -070076 private static final boolean V = false || false;
77 private static final boolean D = true || false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080078
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080079 private Context mContext;
80 private ContentResolver mContentResolver;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080081 private WifiManager mWifiManager;
Robert Greenwalt8e9abc52011-02-16 10:37:50 -080082 private ConnectivityManager mConnectivityManager;
83
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080084 /**
85 * The main watchdog thread.
86 */
87 private WifiWatchdogThread mThread;
88 /**
89 * The handler for the main watchdog thread.
90 */
91 private WifiWatchdogHandler mHandler;
92
Irfan Sheriff7b009782010-03-11 16:37:45 -080093 private ContentObserver mContentObserver;
94
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080095 /**
96 * The current watchdog state. Only written from the main thread!
97 */
98 private WatchdogState mState = WatchdogState.IDLE;
99 /**
100 * The SSID of the network that the watchdog is currently monitoring. Only
101 * touched in the main thread!
102 */
103 private String mSsid;
104 /**
105 * The number of access points in the current network ({@link #mSsid}) that
106 * have been checked. Only touched in the main thread!
107 */
108 private int mNumApsChecked;
109 /** Whether the current AP check should be canceled. */
110 private boolean mShouldCancel;
111
Irfan Sheriff0d255342010-07-28 09:35:20 -0700112 WifiWatchdogService(Context context) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800113 mContext = context;
114 mContentResolver = context.getContentResolver();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800115 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
116
117 createThread();
118
119 // The content observer to listen needs a handler, which createThread creates
120 registerForSettingsChanges();
121 if (isWatchdogEnabled()) {
122 registerForWifiBroadcasts();
123 }
124
125 if (V) {
126 myLogV("WifiWatchdogService: Created");
127 }
128 }
129
130 /**
131 * Observes the watchdog on/off setting, and takes action when changed.
132 */
133 private void registerForSettingsChanges() {
134 ContentResolver contentResolver = mContext.getContentResolver();
135 contentResolver.registerContentObserver(
136 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON), false,
Irfan Sheriff7b009782010-03-11 16:37:45 -0800137 mContentObserver = new ContentObserver(mHandler) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800138 @Override
139 public void onChange(boolean selfChange) {
140 if (isWatchdogEnabled()) {
141 registerForWifiBroadcasts();
142 } else {
143 unregisterForWifiBroadcasts();
144 if (mHandler != null) {
145 mHandler.disableWatchdog();
146 }
147 }
148 }
149 });
150 }
151
152 /**
153 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ON
154 */
155 private boolean isWatchdogEnabled() {
156 return Settings.Secure.getInt(mContentResolver, Settings.Secure.WIFI_WATCHDOG_ON, 1) == 1;
157 }
158
159 /**
160 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_AP_COUNT
161 */
162 private int getApCount() {
163 return Settings.Secure.getInt(mContentResolver,
164 Settings.Secure.WIFI_WATCHDOG_AP_COUNT, 2);
165 }
166
167 /**
168 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT
169 */
170 private int getInitialIgnoredPingCount() {
171 return Settings.Secure.getInt(mContentResolver,
172 Settings.Secure.WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT , 2);
173 }
174
175 /**
176 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_COUNT
177 */
178 private int getPingCount() {
179 return Settings.Secure.getInt(mContentResolver,
180 Settings.Secure.WIFI_WATCHDOG_PING_COUNT, 4);
181 }
182
183 /**
184 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_TIMEOUT_MS
185 */
186 private int getPingTimeoutMs() {
187 return Settings.Secure.getInt(mContentResolver,
188 Settings.Secure.WIFI_WATCHDOG_PING_TIMEOUT_MS, 500);
189 }
190
191 /**
192 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_DELAY_MS
193 */
194 private int getPingDelayMs() {
195 return Settings.Secure.getInt(mContentResolver,
196 Settings.Secure.WIFI_WATCHDOG_PING_DELAY_MS, 250);
197 }
198
199 /**
200 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE
201 */
202 private int getAcceptablePacketLossPercentage() {
203 return Settings.Secure.getInt(mContentResolver,
204 Settings.Secure.WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE, 25);
205 }
206
207 /**
208 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_MAX_AP_CHECKS
209 */
210 private int getMaxApChecks() {
211 return Settings.Secure.getInt(mContentResolver,
212 Settings.Secure.WIFI_WATCHDOG_MAX_AP_CHECKS, 7);
213 }
214
215 /**
216 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED
217 */
218 private boolean isBackgroundCheckEnabled() {
219 return Settings.Secure.getInt(mContentResolver,
220 Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED, 1) == 1;
221 }
222
223 /**
224 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS
225 */
226 private int getBackgroundCheckDelayMs() {
227 return Settings.Secure.getInt(mContentResolver,
228 Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS, 60000);
229 }
230
231 /**
232 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS
233 */
234 private int getBackgroundCheckTimeoutMs() {
235 return Settings.Secure.getInt(mContentResolver,
236 Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS, 1000);
237 }
238
239 /**
240 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WATCH_LIST
241 * @return the comma-separated list of SSIDs
242 */
243 private String getWatchList() {
244 return Settings.Secure.getString(mContentResolver,
245 Settings.Secure.WIFI_WATCHDOG_WATCH_LIST);
246 }
247
248 /**
249 * Registers to receive the necessary Wi-Fi broadcasts.
250 */
251 private void registerForWifiBroadcasts() {
252 IntentFilter intentFilter = new IntentFilter();
253 intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800254 intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
255 mContext.registerReceiver(mReceiver, intentFilter);
256 }
257
258 /**
259 * Unregisters from receiving the Wi-Fi broadcasts.
260 */
261 private void unregisterForWifiBroadcasts() {
262 mContext.unregisterReceiver(mReceiver);
263 }
264
265 /**
266 * Creates the main watchdog thread, including waiting for the handler to be
267 * created.
268 */
269 private void createThread() {
270 mThread = new WifiWatchdogThread();
271 mThread.start();
272 waitForHandlerCreation();
273 }
274
275 /**
Irfan Sheriff7b009782010-03-11 16:37:45 -0800276 * Unregister broadcasts and quit the watchdog thread
277 */
Irfan Sheriffa2a1b912010-06-07 09:03:04 -0700278 //TODO: Change back to running WWS when needed
279// private void quit() {
280// unregisterForWifiBroadcasts();
281// mContext.getContentResolver().unregisterContentObserver(mContentObserver);
282// mHandler.removeAllActions();
283// mHandler.getLooper().quit();
284// }
Irfan Sheriff7b009782010-03-11 16:37:45 -0800285
286 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800287 * Waits for the main watchdog thread to create the handler.
288 */
289 private void waitForHandlerCreation() {
290 synchronized(this) {
291 while (mHandler == null) {
292 try {
293 // Wait for the handler to be set by the other thread
294 wait();
295 } catch (InterruptedException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800296 Slog.e(TAG, "Interrupted while waiting on handler.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800297 }
298 }
299 }
300 }
301
302 // Utility methods
303
304 /**
305 * Logs with the current thread.
306 */
307 private static void myLogV(String message) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800308 Slog.v(TAG, "(" + Thread.currentThread().getName() + ") " + message);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800309 }
310
311 private static void myLogD(String message) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800312 Slog.d(TAG, "(" + Thread.currentThread().getName() + ") " + message);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800313 }
314
315 /**
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800316 * Gets the first DNS of the current AP.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800317 *
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800318 * @return The first DNS of the current AP.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800319 */
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800320 private InetAddress getDns() {
321 if (mConnectivityManager == null) {
322 mConnectivityManager = (ConnectivityManager)mContext.getSystemService(
323 Context.CONNECTIVITY_SERVICE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800324 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800325
326 LinkProperties linkProperties = mConnectivityManager.getLinkProperties(
327 ConnectivityManager.TYPE_WIFI);
328 if (linkProperties == null) return null;
329
330 Collection<InetAddress> dnses = linkProperties.getDnses();
331 if (dnses == null || dnses.size() == 0) return null;
332
333 return dnses.iterator().next();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800334 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800335
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800336 /**
337 * Checks whether the DNS can be reached using multiple attempts according
338 * to the current setting values.
339 *
340 * @return Whether the DNS is reachable
341 */
342 private boolean checkDnsConnectivity() {
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800343 InetAddress dns = getDns();
344 if (dns == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800345 if (V) {
346 myLogV("checkDnsConnectivity: Invalid DNS, returning false");
347 }
348 return false;
349 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800350
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800351 if (V) {
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800352 myLogV("checkDnsConnectivity: Checking " + dns.getHostAddress() + " for connectivity");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800353 }
354
355 int numInitialIgnoredPings = getInitialIgnoredPingCount();
356 int numPings = getPingCount();
357 int pingDelay = getPingDelayMs();
358 int acceptableLoss = getAcceptablePacketLossPercentage();
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800359
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800360 /** See {@link Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT} */
361 int ignoredPingCounter = 0;
362 int pingCounter = 0;
363 int successCounter = 0;
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800364
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800365 // No connectivity check needed
366 if (numPings == 0) {
367 return true;
368 }
369
370 // Do the initial pings that we ignore
371 for (; ignoredPingCounter < numInitialIgnoredPings; ignoredPingCounter++) {
372 if (shouldCancel()) return false;
373
374 boolean dnsAlive = DnsPinger.isDnsReachable(dns, getPingTimeoutMs());
375 if (dnsAlive) {
376 /*
377 * Successful "ignored" pings are *not* ignored (they count in the total number
378 * of pings), but failures are really ignored.
379 */
380 pingCounter++;
381 successCounter++;
382 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800383
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800384 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800385 Slog.v(TAG, (dnsAlive ? " +" : " Ignored: -"));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800386 }
387
388 if (shouldCancel()) return false;
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800389
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800390 try {
391 Thread.sleep(pingDelay);
392 } catch (InterruptedException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800393 Slog.w(TAG, "Interrupted while pausing between pings", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800394 }
395 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800396
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800397 // Do the pings that we use to measure packet loss
398 for (; pingCounter < numPings; pingCounter++) {
399 if (shouldCancel()) return false;
400
401 if (DnsPinger.isDnsReachable(dns, getPingTimeoutMs())) {
402 successCounter++;
403 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800404 Slog.v(TAG, " +");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800405 }
406 } else {
407 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800408 Slog.v(TAG, " -");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800409 }
410 }
411
412 if (shouldCancel()) return false;
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800413
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800414 try {
415 Thread.sleep(pingDelay);
416 } catch (InterruptedException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800417 Slog.w(TAG, "Interrupted while pausing between pings", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800418 }
419 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800420
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800421 int packetLossPercentage = 100 * (numPings - successCounter) / numPings;
422 if (D) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800423 Slog.d(TAG, packetLossPercentage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800424 + "% packet loss (acceptable is " + acceptableLoss + "%)");
425 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800426
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800427 return !shouldCancel() && (packetLossPercentage <= acceptableLoss);
428 }
429
430 private boolean backgroundCheckDnsConnectivity() {
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800431 InetAddress dns = getDns();
432
433 if (dns == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800434 if (V) {
435 myLogV("backgroundCheckDnsConnectivity: DNS is empty, returning false");
436 }
437 return false;
438 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800439
440 if (false && V) {
441 myLogV("backgroundCheckDnsConnectivity: Background checking " +
442 dns.getHostAddress() + " for connectivity");
443 }
444
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800445 return DnsPinger.isDnsReachable(dns, getBackgroundCheckTimeoutMs());
446 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800447
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800448 /**
449 * Signals the current action to cancel.
450 */
451 private void cancelCurrentAction() {
452 mShouldCancel = true;
453 }
454
455 /**
456 * Helper to check whether to cancel.
457 *
458 * @return Whether to cancel processing the action.
459 */
460 private boolean shouldCancel() {
461 if (V && mShouldCancel) {
462 myLogV("shouldCancel: Cancelling");
463 }
464
465 return mShouldCancel;
466 }
467
468 // Wi-Fi initiated callbacks (could be executed in another thread)
469
470 /**
471 * Called when connected to an AP (this can be the next AP in line, or
472 * it can be a completely different network).
473 *
474 * @param ssid The SSID of the access point.
475 * @param bssid The BSSID of the access point.
476 */
477 private void onConnected(String ssid, String bssid) {
478 if (V) {
479 myLogV("onConnected: SSID: " + ssid + ", BSSID: " + bssid);
480 }
481
482 /*
483 * The current action being processed by the main watchdog thread is now
484 * stale, so cancel it.
485 */
486 cancelCurrentAction();
487
488 if ((mSsid == null) || !mSsid.equals(ssid)) {
489 /*
490 * This is a different network than what the main watchdog thread is
491 * processing, dispatch the network change message on the main thread.
492 */
493 mHandler.dispatchNetworkChanged(ssid);
494 }
495
496 if (requiresWatchdog(ssid, bssid)) {
497 if (D) {
498 myLogD(ssid + " (" + bssid + ") requires the watchdog");
499 }
500
501 // This access point requires a watchdog, so queue the check on the main thread
502 mHandler.checkAp(new AccessPoint(ssid, bssid));
503
504 } else {
505 if (D) {
506 myLogD(ssid + " (" + bssid + ") does not require the watchdog");
507 }
508
509 // This access point does not require a watchdog, so queue idle on the main thread
510 mHandler.idle();
511 }
Irfan Sheriff2f5f4bc2011-05-02 11:43:28 -0700512 mHandler.checkWalledGarden(ssid);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800513 }
514
515 /**
516 * Called when Wi-Fi is enabled.
517 */
518 private void onEnabled() {
519 cancelCurrentAction();
520 // Queue a hard-reset of the state on the main thread
521 mHandler.reset();
522 }
523
524 /**
525 * Called when disconnected (or some other event similar to being disconnected).
526 */
527 private void onDisconnected() {
528 if (V) {
529 myLogV("onDisconnected");
530 }
531
532 /*
533 * Disconnected from an access point, the action being processed by the
534 * watchdog thread is now stale, so cancel it.
535 */
536 cancelCurrentAction();
537 // Dispatch the disconnected to the main watchdog thread
538 mHandler.dispatchDisconnected();
539 // Queue the action to go idle
540 mHandler.idle();
541 }
542
543 /**
544 * Checks whether an access point requires watchdog monitoring.
545 *
546 * @param ssid The SSID of the access point.
547 * @param bssid The BSSID of the access point.
548 * @return Whether the access point/network should be monitored by the
549 * watchdog.
550 */
551 private boolean requiresWatchdog(String ssid, String bssid) {
552 if (V) {
553 myLogV("requiresWatchdog: SSID: " + ssid + ", BSSID: " + bssid);
554 }
555
556 WifiInfo info = null;
557 if (ssid == null) {
558 /*
559 * This is called from a Wi-Fi callback, so assume the WifiInfo does
560 * not have stale data.
561 */
562 info = mWifiManager.getConnectionInfo();
563 ssid = info.getSSID();
564 if (ssid == null) {
565 // It's still null, give up
566 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800567 Slog.v(TAG, " Invalid SSID, returning false");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800568 }
569 return false;
570 }
571 }
572
573 if (TextUtils.isEmpty(bssid)) {
574 // Similar as above
575 if (info == null) {
576 info = mWifiManager.getConnectionInfo();
577 }
578 bssid = info.getBSSID();
579 if (TextUtils.isEmpty(bssid)) {
580 // It's still null, give up
581 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800582 Slog.v(TAG, " Invalid BSSID, returning false");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800583 }
584 return false;
585 }
586 }
587
588 if (!isOnWatchList(ssid)) {
589 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800590 Slog.v(TAG, " SSID not on watch list, returning false");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800591 }
592 return false;
593 }
594
595 // The watchdog only monitors networks with multiple APs
596 if (!hasRequiredNumberOfAps(ssid)) {
597 return false;
598 }
599
600 return true;
601 }
602
603 private boolean isOnWatchList(String ssid) {
604 String watchList;
605
606 if (ssid == null || (watchList = getWatchList()) == null) {
607 return false;
608 }
609
610 String[] list = watchList.split(" *, *");
611
612 for (String name : list) {
613 if (ssid.equals(name)) {
614 return true;
615 }
616 }
617
618 return false;
619 }
620
621 /**
622 * Checks if the current scan results have multiple access points with an SSID.
623 *
624 * @param ssid The SSID to check.
625 * @return Whether the SSID has multiple access points.
626 */
627 private boolean hasRequiredNumberOfAps(String ssid) {
628 List<ScanResult> results = mWifiManager.getScanResults();
629 if (results == null) {
630 if (V) {
631 myLogV("hasRequiredNumberOfAps: Got null scan results, returning false");
632 }
633 return false;
634 }
635
636 int numApsRequired = getApCount();
637 int numApsFound = 0;
638 int resultsSize = results.size();
639 for (int i = 0; i < resultsSize; i++) {
640 ScanResult result = results.get(i);
641 if (result == null) continue;
642 if (result.SSID == null) continue;
643
644 if (result.SSID.equals(ssid)) {
645 numApsFound++;
646
647 if (numApsFound >= numApsRequired) {
648 if (V) {
649 myLogV("hasRequiredNumberOfAps: SSID: " + ssid + ", returning true");
650 }
651 return true;
652 }
653 }
654 }
655
656 if (V) {
657 myLogV("hasRequiredNumberOfAps: SSID: " + ssid + ", returning false");
658 }
659 return false;
660 }
661
662 // Watchdog logic (assume all of these methods will be in our main thread)
663
664 /**
665 * Handles a Wi-Fi network change (for example, from networkA to networkB).
666 */
667 private void handleNetworkChanged(String ssid) {
668 // Set the SSID being monitored to the new SSID
669 mSsid = ssid;
670 // Set various state to that when being idle
671 setIdleState(true);
672 }
673
674 /**
675 * Handles checking whether an AP is a "good" AP. If not, it will be blacklisted.
676 *
677 * @param ap The access point to check.
678 */
679 private void handleCheckAp(AccessPoint ap) {
680 // Reset the cancel state since this is the entry point of this action
681 mShouldCancel = false;
682
683 if (V) {
684 myLogV("handleCheckAp: AccessPoint: " + ap);
685 }
686
687 // Make sure we are not sleeping
688 if (mState == WatchdogState.SLEEP) {
689 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800690 Slog.v(TAG, " Sleeping (in " + mSsid + "), so returning");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800691 }
692 return;
693 }
694
695 mState = WatchdogState.CHECKING_AP;
696
697 /*
698 * Checks to make sure we haven't exceeded the max number of checks
699 * we're allowed per network
700 */
701 mNumApsChecked++;
702 if (mNumApsChecked > getMaxApChecks()) {
703 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800704 Slog.v(TAG, " Passed the max attempts (" + getMaxApChecks()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800705 + "), going to sleep for " + mSsid);
706 }
707 mHandler.sleep(mSsid);
708 return;
709 }
710
711 // Do the check
712 boolean isApAlive = checkDnsConnectivity();
713
714 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800715 Slog.v(TAG, " Is it alive: " + isApAlive);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800716 }
717
718 // Take action based on results
719 if (isApAlive) {
720 handleApAlive(ap);
721 } else {
722 handleApUnresponsive(ap);
723 }
724 }
725
726 /**
727 * Handles the case when an access point is alive.
728 *
729 * @param ap The access point.
730 */
731 private void handleApAlive(AccessPoint ap) {
732 // Check whether we are stale and should cancel
733 if (shouldCancel()) return;
734 // We're satisfied with this AP, so go idle
735 setIdleState(false);
736
737 if (D) {
738 myLogD("AP is alive: " + ap.toString());
739 }
740
741 // Queue the next action to be a background check
742 mHandler.backgroundCheckAp(ap);
743 }
744
745 /**
746 * Handles an unresponsive AP by blacklisting it.
747 *
748 * @param ap The access point.
749 */
750 private void handleApUnresponsive(AccessPoint ap) {
751 // Check whether we are stale and should cancel
752 if (shouldCancel()) return;
753 // This AP is "bad", switch to another
754 mState = WatchdogState.SWITCHING_AP;
755
756 if (D) {
757 myLogD("AP is dead: " + ap.toString());
758 }
759
760 // Black list this "bad" AP, this will cause an attempt to connect to another
761 blacklistAp(ap.bssid);
Irfan Sheriff0049a1b2010-01-14 12:37:49 -0800762 // Initiate an association to an alternate AP
Irfan Sheriff0d255342010-07-28 09:35:20 -0700763 mWifiManager.reassociate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800764 }
765
766 private void blacklistAp(String bssid) {
767 if (TextUtils.isEmpty(bssid)) {
768 return;
769 }
770
771 // Before taking action, make sure we should not cancel our processing
772 if (shouldCancel()) return;
773
Irfan Sheriff0d255342010-07-28 09:35:20 -0700774 mWifiManager.addToBlacklist(bssid);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800775
776 if (D) {
777 myLogD("Blacklisting " + bssid);
778 }
779 }
780
781 /**
782 * Handles a single background check. If it fails, it should trigger a
783 * normal check. If it succeeds, it should queue another background check.
784 *
785 * @param ap The access point to do a background check for. If this is no
786 * longer the current AP, it is okay to return without any
787 * processing.
788 */
789 private void handleBackgroundCheckAp(AccessPoint ap) {
790 // Reset the cancel state since this is the entry point of this action
791 mShouldCancel = false;
792
793 if (false && V) {
794 myLogV("handleBackgroundCheckAp: AccessPoint: " + ap);
795 }
796
797 // Make sure we are not sleeping
798 if (mState == WatchdogState.SLEEP) {
799 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800800 Slog.v(TAG, " handleBackgroundCheckAp: Sleeping (in " + mSsid + "), so returning");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800801 }
802 return;
803 }
804
805 // Make sure the AP we're supposed to be background checking is still the active one
806 WifiInfo info = mWifiManager.getConnectionInfo();
807 if (info.getSSID() == null || !info.getSSID().equals(ap.ssid)) {
808 if (V) {
809 myLogV("handleBackgroundCheckAp: We are no longer connected to "
810 + ap + ", and instead are on " + info);
811 }
812 return;
813 }
814
815 if (info.getBSSID() == null || !info.getBSSID().equals(ap.bssid)) {
816 if (V) {
817 myLogV("handleBackgroundCheckAp: We are no longer connected to "
818 + ap + ", and instead are on " + info);
819 }
820 return;
821 }
822
823 // Do the check
824 boolean isApAlive = backgroundCheckDnsConnectivity();
825
826 if (V && !isApAlive) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800827 Slog.v(TAG, " handleBackgroundCheckAp: Is it alive: " + isApAlive);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800828 }
829
830 if (shouldCancel()) {
831 return;
832 }
833
834 // Take action based on results
835 if (isApAlive) {
836 // Queue another background check
837 mHandler.backgroundCheckAp(ap);
838
839 } else {
840 if (D) {
841 myLogD("Background check failed for " + ap.toString());
842 }
843
844 // Queue a normal check, so it can take proper action
845 mHandler.checkAp(ap);
846 }
847 }
848
849 /**
850 * Handles going to sleep for this network. Going to sleep means we will not
851 * monitor this network anymore.
852 *
853 * @param ssid The network that will not be monitored anymore.
854 */
855 private void handleSleep(String ssid) {
856 // Make sure the network we're trying to sleep in is still the current network
857 if (ssid != null && ssid.equals(mSsid)) {
858 mState = WatchdogState.SLEEP;
859
860 if (D) {
861 myLogD("Going to sleep for " + ssid);
862 }
863
864 /*
865 * Before deciding to go to sleep, we may have checked a few APs
866 * (and blacklisted them). Clear the blacklist so the AP with best
867 * signal is chosen.
868 */
Irfan Sheriff0d255342010-07-28 09:35:20 -0700869 mWifiManager.clearBlacklist();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800870
871 if (V) {
872 myLogV("handleSleep: Set state to SLEEP and cleared blacklist");
873 }
874 }
875 }
876
877 /**
878 * Handles an access point disconnection.
879 */
880 private void handleDisconnected() {
881 /*
882 * We purposefully do not change mSsid to null. This is to handle
883 * disconnected followed by connected better (even if there is some
884 * duration in between). For example, if the watchdog went to sleep in a
885 * network, and then the phone goes to sleep, when the phone wakes up we
886 * still want to be in the sleeping state. When the phone went to sleep,
887 * we would have gotten a disconnected event which would then set mSsid
888 * = null. This is bad, since the following connect would cause us to do
889 * the "network is good?" check all over again. */
890
891 /*
892 * Set the state as if we were idle (don't come out of sleep, only
893 * hard reset and network changed should do that.
894 */
895 setIdleState(false);
896 }
897
898 /**
899 * Handles going idle. Idle means we are satisfied with the current state of
900 * things, but if a new connection occurs we'll re-evaluate.
901 */
902 private void handleIdle() {
903 // Reset the cancel state since this is the entry point for this action
904 mShouldCancel = false;
905
906 if (V) {
907 myLogV("handleSwitchToIdle");
908 }
909
910 // If we're sleeping, don't do anything
911 if (mState == WatchdogState.SLEEP) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800912 Slog.v(TAG, " Sleeping (in " + mSsid + "), so returning");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800913 return;
914 }
915
916 // Set the idle state
917 setIdleState(false);
918
919 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800920 Slog.v(TAG, " Set state to IDLE");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800921 }
922 }
923
924 /**
925 * Sets the state as if we are going idle.
926 */
927 private void setIdleState(boolean forceIdleState) {
928 // Setting idle state does not kick us out of sleep unless the forceIdleState is set
929 if (forceIdleState || (mState != WatchdogState.SLEEP)) {
930 mState = WatchdogState.IDLE;
931 }
932 mNumApsChecked = 0;
933 }
934
935 /**
936 * Handles a hard reset. A hard reset is rarely used, but when used it
937 * should revert anything done by the watchdog monitoring.
938 */
939 private void handleReset() {
Irfan Sheriff0d255342010-07-28 09:35:20 -0700940 mWifiManager.clearBlacklist();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800941 setIdleState(true);
942 }
943
944 // Inner classes
945
946 /**
947 * Possible states for the watchdog to be in.
948 */
949 private static enum WatchdogState {
950 /** The watchdog is currently idle, but it is still responsive to future AP checks in this network. */
951 IDLE,
952 /** The watchdog is sleeping, so it will not try any AP checks for the network. */
953 SLEEP,
954 /** The watchdog is currently checking an AP for connectivity. */
955 CHECKING_AP,
956 /** The watchdog is switching to another AP in the network. */
957 SWITCHING_AP
958 }
959
960 /**
961 * The main thread for the watchdog monitoring. This will be turned into a
962 * {@link Looper} thread.
963 */
964 private class WifiWatchdogThread extends Thread {
965 WifiWatchdogThread() {
966 super("WifiWatchdogThread");
967 }
968
969 @Override
970 public void run() {
971 // Set this thread up so the handler will work on it
972 Looper.prepare();
973
974 synchronized(WifiWatchdogService.this) {
975 mHandler = new WifiWatchdogHandler();
976
977 // Notify that the handler has been created
978 WifiWatchdogService.this.notify();
979 }
980
981 // Listen for messages to the handler
982 Looper.loop();
983 }
984 }
985
986 /**
987 * The main thread's handler. There are 'actions', and just general
988 * 'messages'. There should only ever be one 'action' in the queue (aside
989 * from the one being processed, if any). There may be multiple messages in
990 * the queue. So, actions are replaced by more recent actions, where as
991 * messages will be executed for sure. Messages end up being used to just
992 * change some state, and not really take any action.
993 * <p>
994 * There is little logic inside this class, instead methods of the form
995 * "handle___" are called in the main {@link WifiWatchdogService}.
996 */
997 private class WifiWatchdogHandler extends Handler {
998 /** Check whether the AP is "good". The object will be an {@link AccessPoint}. */
999 static final int ACTION_CHECK_AP = 1;
1000 /** Go into the idle state. */
1001 static final int ACTION_IDLE = 2;
1002 /**
1003 * Performs a periodic background check whether the AP is still "good".
1004 * The object will be an {@link AccessPoint}.
1005 */
1006 static final int ACTION_BACKGROUND_CHECK_AP = 3;
Irfan Sheriff2f5f4bc2011-05-02 11:43:28 -07001007 /** Check whether the connection is a walled garden */
1008 static final int ACTION_CHECK_WALLED_GARDEN = 4;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001009
1010 /**
1011 * Go to sleep for the current network. We are conservative with making
1012 * this a message rather than action. We want to make sure our main
1013 * thread sees this message, but if it were an action it could be
1014 * removed from the queue and replaced by another action. The main
1015 * thread will ensure when it sees the message that the state is still
1016 * valid for going to sleep.
1017 * <p>
1018 * For an explanation of sleep, see {@link android.provider.Settings.Secure#WIFI_WATCHDOG_MAX_AP_CHECKS}.
1019 */
1020 static final int MESSAGE_SLEEP = 101;
1021 /** Disables the watchdog. */
1022 static final int MESSAGE_DISABLE_WATCHDOG = 102;
1023 /** The network has changed. */
1024 static final int MESSAGE_NETWORK_CHANGED = 103;
1025 /** The current access point has disconnected. */
1026 static final int MESSAGE_DISCONNECTED = 104;
1027 /** Performs a hard-reset on the watchdog state. */
1028 static final int MESSAGE_RESET = 105;
Irfan Sheriff2f5f4bc2011-05-02 11:43:28 -07001029
1030 /* Walled garden detection */
1031 private String mLastSsid;
1032 private long mLastTime;
1033 private final long MIN_WALLED_GARDEN_TEST_INTERVAL = 15 * 60 * 1000; //15 minutes
1034
1035 void checkWalledGarden(String ssid) {
1036 sendMessage(obtainMessage(ACTION_CHECK_WALLED_GARDEN, ssid));
1037 }
1038
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001039 void checkAp(AccessPoint ap) {
1040 removeAllActions();
1041 sendMessage(obtainMessage(ACTION_CHECK_AP, ap));
1042 }
1043
1044 void backgroundCheckAp(AccessPoint ap) {
1045 if (!isBackgroundCheckEnabled()) return;
1046
1047 removeAllActions();
1048 sendMessageDelayed(obtainMessage(ACTION_BACKGROUND_CHECK_AP, ap),
1049 getBackgroundCheckDelayMs());
1050 }
1051
1052 void idle() {
1053 removeAllActions();
1054 sendMessage(obtainMessage(ACTION_IDLE));
1055 }
1056
1057 void sleep(String ssid) {
1058 removeAllActions();
1059 sendMessage(obtainMessage(MESSAGE_SLEEP, ssid));
1060 }
1061
1062 void disableWatchdog() {
1063 removeAllActions();
1064 sendMessage(obtainMessage(MESSAGE_DISABLE_WATCHDOG));
1065 }
1066
1067 void dispatchNetworkChanged(String ssid) {
1068 removeAllActions();
1069 sendMessage(obtainMessage(MESSAGE_NETWORK_CHANGED, ssid));
1070 }
1071
1072 void dispatchDisconnected() {
1073 removeAllActions();
1074 sendMessage(obtainMessage(MESSAGE_DISCONNECTED));
1075 }
1076
1077 void reset() {
1078 removeAllActions();
1079 sendMessage(obtainMessage(MESSAGE_RESET));
1080 }
1081
1082 private void removeAllActions() {
1083 removeMessages(ACTION_CHECK_AP);
1084 removeMessages(ACTION_IDLE);
1085 removeMessages(ACTION_BACKGROUND_CHECK_AP);
1086 }
1087
1088 @Override
1089 public void handleMessage(Message msg) {
1090 switch (msg.what) {
1091 case MESSAGE_NETWORK_CHANGED:
1092 handleNetworkChanged((String) msg.obj);
1093 break;
1094 case ACTION_CHECK_AP:
1095 handleCheckAp((AccessPoint) msg.obj);
1096 break;
1097 case ACTION_BACKGROUND_CHECK_AP:
1098 handleBackgroundCheckAp((AccessPoint) msg.obj);
1099 break;
Irfan Sheriff2f5f4bc2011-05-02 11:43:28 -07001100 case ACTION_CHECK_WALLED_GARDEN:
1101 handleWalledGardenCheck((String) msg.obj);
1102 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001103 case MESSAGE_SLEEP:
1104 handleSleep((String) msg.obj);
1105 break;
1106 case ACTION_IDLE:
1107 handleIdle();
1108 break;
1109 case MESSAGE_DISABLE_WATCHDOG:
1110 handleIdle();
1111 break;
1112 case MESSAGE_DISCONNECTED:
1113 handleDisconnected();
1114 break;
1115 case MESSAGE_RESET:
1116 handleReset();
1117 break;
1118 }
1119 }
Irfan Sheriff2f5f4bc2011-05-02 11:43:28 -07001120
1121 private boolean isWalledGardenConnection() {
1122 //One way to detect a walled garden is to see if multiple DNS queries
1123 //resolve to the same IP address
1124 try {
1125 String host1 = "www.google.com";
1126 String host2 = "www.android.com";
1127 String address1 = InetAddress.getByName(host1).getHostAddress();
1128 String address2 = InetAddress.getByName(host2).getHostAddress();
1129 if (address1.equals(address2)) return true;
1130 } catch (UnknownHostException e) {
1131 return false;
1132 }
1133 return false;
1134 }
1135
1136 private void handleWalledGardenCheck(String ssid) {
1137 long currentTime = System.currentTimeMillis();
1138 //Avoid a walled garden test on the same network if one was already done
1139 //within MIN_WALLED_GARDEN_TEST_INTERVAL. This will handle scenarios where
1140 //there are frequent network disconnections
1141 if (ssid.equals(mLastSsid) &&
1142 (currentTime - mLastTime) < MIN_WALLED_GARDEN_TEST_INTERVAL) {
1143 return;
1144 }
1145
1146 mLastTime = currentTime;
1147 mLastSsid = ssid;
1148
1149 if (isWalledGardenConnection()) {
1150 Uri uri = Uri.parse("http://www.google.com");
1151 Intent intent = new Intent(Intent.ACTION_VIEW, uri);
1152 intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
1153 Intent.FLAG_ACTIVITY_NEW_TASK);
1154 mContext.startActivity(intent);
1155 }
1156 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001157 }
1158
1159 /**
1160 * Receives Wi-Fi broadcasts.
1161 * <p>
1162 * There is little logic in this class, instead methods of the form "on___"
1163 * are called in the {@link WifiWatchdogService}.
1164 */
1165 private BroadcastReceiver mReceiver = new BroadcastReceiver() {
1166
1167 @Override
1168 public void onReceive(Context context, Intent intent) {
1169 final String action = intent.getAction();
1170 if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
1171 handleNetworkStateChanged(
1172 (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001173 } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
1174 handleWifiStateChanged(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
1175 WifiManager.WIFI_STATE_UNKNOWN));
1176 }
1177 }
1178
1179 private void handleNetworkStateChanged(NetworkInfo info) {
1180 if (V) {
1181 myLogV("Receiver.handleNetworkStateChanged: NetworkInfo: "
1182 + info);
1183 }
1184
1185 switch (info.getState()) {
1186 case CONNECTED:
1187 WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
1188 if (wifiInfo.getSSID() == null || wifiInfo.getBSSID() == null) {
1189 if (V) {
1190 myLogV("handleNetworkStateChanged: Got connected event but SSID or BSSID are null. SSID: "
1191 + wifiInfo.getSSID()
1192 + ", BSSID: "
1193 + wifiInfo.getBSSID() + ", ignoring event");
1194 }
1195 return;
1196 }
1197 onConnected(wifiInfo.getSSID(), wifiInfo.getBSSID());
1198 break;
1199
1200 case DISCONNECTED:
1201 onDisconnected();
1202 break;
1203 }
1204 }
1205
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001206 private void handleWifiStateChanged(int wifiState) {
1207 if (wifiState == WifiManager.WIFI_STATE_DISABLED) {
Irfan Sheriffa2a1b912010-06-07 09:03:04 -07001208 onDisconnected();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001209 } else if (wifiState == WifiManager.WIFI_STATE_ENABLED) {
1210 onEnabled();
1211 }
1212 }
1213 };
1214
1215 /**
1216 * Describes an access point by its SSID and BSSID.
1217 */
1218 private static class AccessPoint {
1219 String ssid;
1220 String bssid;
1221
1222 AccessPoint(String ssid, String bssid) {
1223 this.ssid = ssid;
1224 this.bssid = bssid;
1225 }
1226
1227 private boolean hasNull() {
1228 return ssid == null || bssid == null;
1229 }
1230
1231 @Override
1232 public boolean equals(Object o) {
1233 if (!(o instanceof AccessPoint)) return false;
1234 AccessPoint otherAp = (AccessPoint) o;
1235 boolean iHaveNull = hasNull();
1236 // Either we both have a null, or our SSIDs and BSSIDs are equal
1237 return (iHaveNull && otherAp.hasNull()) ||
1238 (otherAp.bssid != null && ssid.equals(otherAp.ssid)
1239 && bssid.equals(otherAp.bssid));
1240 }
1241
1242 @Override
1243 public int hashCode() {
1244 if (ssid == null || bssid == null) return 0;
1245 return ssid.hashCode() + bssid.hashCode();
1246 }
1247
1248 @Override
1249 public String toString() {
1250 return ssid + " (" + bssid + ")";
1251 }
1252 }
1253
1254 /**
1255 * Performs a simple DNS "ping" by sending a "server status" query packet to
1256 * the DNS server. As long as the server replies, we consider it a success.
1257 * <p>
1258 * We do not use a simple hostname lookup because that could be cached and
1259 * the API may not differentiate between a time out and a failure lookup
1260 * (which we really care about).
1261 */
1262 private static class DnsPinger {
1263
1264 /** Number of bytes for the query */
1265 private static final int DNS_QUERY_BASE_SIZE = 33;
1266
1267 /** The DNS port */
1268 private static final int DNS_PORT = 53;
1269
1270 /** Used to generate IDs */
1271 private static Random sRandom = new Random();
Robert Greenwalt8e9abc52011-02-16 10:37:50 -08001272
1273 static boolean isDnsReachable(InetAddress dnsAddress, int timeout) {
Nick Kralevich929b4852010-06-09 14:27:43 -07001274 DatagramSocket socket = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001275 try {
Nick Kralevich929b4852010-06-09 14:27:43 -07001276 socket = new DatagramSocket();
Robert Greenwalt8e9abc52011-02-16 10:37:50 -08001277
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001278 // Set some socket properties
1279 socket.setSoTimeout(timeout);
Robert Greenwalt8e9abc52011-02-16 10:37:50 -08001280
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001281 byte[] buf = new byte[DNS_QUERY_BASE_SIZE];
1282 fillQuery(buf);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001283
Robert Greenwalt8e9abc52011-02-16 10:37:50 -08001284 // Send the DNS query
1285
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001286 DatagramPacket packet = new DatagramPacket(buf,
1287 buf.length, dnsAddress, DNS_PORT);
1288 socket.send(packet);
Robert Greenwalt8e9abc52011-02-16 10:37:50 -08001289
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001290 // Wait for reply (blocks for the above timeout)
1291 DatagramPacket replyPacket = new DatagramPacket(buf, buf.length);
1292 socket.receive(replyPacket);
1293
1294 // If a timeout occurred, an exception would have been thrown. We got a reply!
1295 return true;
Robert Greenwalt8e9abc52011-02-16 10:37:50 -08001296
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001297 } catch (SocketException e) {
1298 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001299 Slog.v(TAG, "DnsPinger.isReachable received SocketException", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001300 }
1301 return false;
Robert Greenwalt8e9abc52011-02-16 10:37:50 -08001302
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001303 } catch (UnknownHostException e) {
1304 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001305 Slog.v(TAG, "DnsPinger.isReachable is unable to resolve the DNS host", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001306 }
1307 return false;
1308
1309 } catch (SocketTimeoutException e) {
1310 return false;
1311
1312 } catch (IOException e) {
1313 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001314 Slog.v(TAG, "DnsPinger.isReachable got an IOException", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001315 }
1316 return false;
1317
1318 } catch (Exception e) {
Joe Onorato43a17652011-04-06 19:22:23 -07001319 if (V || false) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001320 Slog.d(TAG, "DnsPinger.isReachable got an unknown exception", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001321 }
1322 return false;
Nick Kralevich929b4852010-06-09 14:27:43 -07001323 } finally {
1324 if (socket != null) {
1325 socket.close();
1326 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001327 }
1328 }
1329
1330 private static void fillQuery(byte[] buf) {
1331
1332 /*
1333 * See RFC2929 (though the bit tables in there are misleading for
1334 * us. For example, the recursion desired bit is the 0th bit for us,
1335 * but looking there it would appear as the 7th bit of the byte
1336 */
1337
1338 // Make sure it's all zeroed out
1339 for (int i = 0; i < buf.length; i++) buf[i] = 0;
1340
1341 // Form a query for www.android.com
1342
1343 // [0-1] bytes are an ID, generate random ID for this query
1344 buf[0] = (byte) sRandom.nextInt(256);
1345 buf[1] = (byte) sRandom.nextInt(256);
1346
1347 // [2-3] bytes are for flags.
1348 buf[2] = 1; // Recursion desired
1349
1350 // [4-5] bytes are for the query count
1351 buf[5] = 1; // One query
1352
1353 // [6-7] [8-9] [10-11] are all counts of other fields we don't use
1354
1355 // [12-15] for www
1356 writeString(buf, 12, "www");
1357
1358 // [16-23] for android
1359 writeString(buf, 16, "android");
1360
1361 // [24-27] for com
1362 writeString(buf, 24, "com");
1363
1364 // [29-30] bytes are for QTYPE, set to 1
1365 buf[30] = 1;
1366
1367 // [31-32] bytes are for QCLASS, set to 1
1368 buf[32] = 1;
1369 }
1370
1371 private static void writeString(byte[] buf, int startPos, String string) {
1372 int pos = startPos;
1373
1374 // Write the length first
1375 buf[pos++] = (byte) string.length();
1376 for (int i = 0; i < string.length(); i++) {
1377 buf[pos++] = (byte) string.charAt(i);
1378 }
1379 }
1380 }
1381}