blob: 6c7895e35b142b3a10494ec3fe180c4dbe59b1ab [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
Irfan Sheriff5ea65d62011-06-05 21:29:56 -070039import java.io.BufferedInputStream;
40import java.io.InputStream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041import java.io.IOException;
42import java.net.DatagramPacket;
43import java.net.DatagramSocket;
Irfan Sheriff5ea65d62011-06-05 21:29:56 -070044import java.net.HttpURLConnection;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080045import java.net.InetAddress;
46import java.net.SocketException;
47import java.net.SocketTimeoutException;
48import java.net.UnknownHostException;
Irfan Sheriff5ea65d62011-06-05 21:29:56 -070049import java.net.URL;
Robert Greenwalt8e9abc52011-02-16 10:37:50 -080050import java.util.Collection;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051import java.util.List;
52import java.util.Random;
Irfan Sheriff5ea65d62011-06-05 21:29:56 -070053import java.util.Scanner;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080054
55/**
56 * {@link WifiWatchdogService} monitors the initial connection to a Wi-Fi
57 * network with multiple access points. After the framework successfully
58 * connects to an access point, the watchdog verifies whether the DNS server is
59 * reachable. If not, the watchdog blacklists the current access point, leading
60 * to a connection on another access point within the same network.
61 * <p>
62 * The watchdog has a few safeguards:
63 * <ul>
64 * <li>Only monitor networks with multiple access points
65 * <li>Only check at most {@link #getMaxApChecks()} different access points
66 * within the network before giving up
67 * <p>
68 * The watchdog checks for connectivity on an access point by ICMP pinging the
69 * DNS. There are settings that allow disabling the watchdog, or tweaking the
70 * acceptable packet loss (and other various parameters).
71 * <p>
72 * The core logic of the watchdog is done on the main watchdog thread. Wi-Fi
73 * callbacks can come in on other threads, so we must queue messages to the main
74 * watchdog thread's handler. Most (if not all) state is only written to from
75 * the main thread.
76 *
77 * {@hide}
78 */
79public class WifiWatchdogService {
80 private static final String TAG = "WifiWatchdogService";
Joe Onorato43a17652011-04-06 19:22:23 -070081 private static final boolean V = false || false;
82 private static final boolean D = true || false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080083
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080084 private Context mContext;
85 private ContentResolver mContentResolver;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080086 private WifiManager mWifiManager;
Robert Greenwalt8e9abc52011-02-16 10:37:50 -080087 private ConnectivityManager mConnectivityManager;
88
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080089 /**
90 * The main watchdog thread.
91 */
92 private WifiWatchdogThread mThread;
93 /**
94 * The handler for the main watchdog thread.
95 */
96 private WifiWatchdogHandler mHandler;
97
Irfan Sheriff7b009782010-03-11 16:37:45 -080098 private ContentObserver mContentObserver;
99
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800100 /**
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
Irfan Sheriff0d255342010-07-28 09:35:20 -0700117 WifiWatchdogService(Context context) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800118 mContext = context;
119 mContentResolver = context.getContentResolver();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800120 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
121
122 createThread();
123
124 // The content observer to listen needs a handler, which createThread creates
125 registerForSettingsChanges();
126 if (isWatchdogEnabled()) {
127 registerForWifiBroadcasts();
128 }
129
130 if (V) {
131 myLogV("WifiWatchdogService: Created");
132 }
133 }
134
135 /**
136 * Observes the watchdog on/off setting, and takes action when changed.
137 */
138 private void registerForSettingsChanges() {
139 ContentResolver contentResolver = mContext.getContentResolver();
140 contentResolver.registerContentObserver(
141 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON), false,
Irfan Sheriff7b009782010-03-11 16:37:45 -0800142 mContentObserver = new ContentObserver(mHandler) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800143 @Override
144 public void onChange(boolean selfChange) {
145 if (isWatchdogEnabled()) {
146 registerForWifiBroadcasts();
147 } else {
148 unregisterForWifiBroadcasts();
149 if (mHandler != null) {
150 mHandler.disableWatchdog();
151 }
152 }
153 }
154 });
155 }
156
157 /**
158 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ON
159 */
160 private boolean isWatchdogEnabled() {
161 return Settings.Secure.getInt(mContentResolver, Settings.Secure.WIFI_WATCHDOG_ON, 1) == 1;
162 }
163
164 /**
165 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_AP_COUNT
166 */
167 private int getApCount() {
168 return Settings.Secure.getInt(mContentResolver,
169 Settings.Secure.WIFI_WATCHDOG_AP_COUNT, 2);
170 }
171
172 /**
173 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT
174 */
175 private int getInitialIgnoredPingCount() {
176 return Settings.Secure.getInt(mContentResolver,
177 Settings.Secure.WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT , 2);
178 }
179
180 /**
181 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_COUNT
182 */
183 private int getPingCount() {
184 return Settings.Secure.getInt(mContentResolver,
185 Settings.Secure.WIFI_WATCHDOG_PING_COUNT, 4);
186 }
187
188 /**
189 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_TIMEOUT_MS
190 */
191 private int getPingTimeoutMs() {
192 return Settings.Secure.getInt(mContentResolver,
193 Settings.Secure.WIFI_WATCHDOG_PING_TIMEOUT_MS, 500);
194 }
195
196 /**
197 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_DELAY_MS
198 */
199 private int getPingDelayMs() {
200 return Settings.Secure.getInt(mContentResolver,
201 Settings.Secure.WIFI_WATCHDOG_PING_DELAY_MS, 250);
202 }
Irfan Sheriff5ea65d62011-06-05 21:29:56 -0700203
204 /**
205 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED
206 */
207 private Boolean isWalledGardenTestEnabled() {
208 return Settings.Secure.getInt(mContentResolver,
209 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, 1) == 1;
210 }
211
212 /**
213 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_URL
214 */
215 private String getWalledGardenUrl() {
216 String url = Settings.Secure.getString(mContentResolver,
217 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL);
218 if (TextUtils.isEmpty(url)) return "http://www.google.com/";
219 return url;
220 }
221
222 /**
223 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_PATTERN
224 */
225 private String getWalledGardenPattern() {
226 String pattern = Settings.Secure.getString(mContentResolver,
227 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_PATTERN);
228 if (TextUtils.isEmpty(pattern)) return "<title>.*Google.*</title>";
229 return pattern;
230 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800231
232 /**
233 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE
234 */
235 private int getAcceptablePacketLossPercentage() {
236 return Settings.Secure.getInt(mContentResolver,
237 Settings.Secure.WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE, 25);
238 }
239
240 /**
241 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_MAX_AP_CHECKS
242 */
243 private int getMaxApChecks() {
244 return Settings.Secure.getInt(mContentResolver,
245 Settings.Secure.WIFI_WATCHDOG_MAX_AP_CHECKS, 7);
246 }
247
248 /**
249 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED
250 */
251 private boolean isBackgroundCheckEnabled() {
252 return Settings.Secure.getInt(mContentResolver,
253 Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED, 1) == 1;
254 }
255
256 /**
257 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS
258 */
259 private int getBackgroundCheckDelayMs() {
260 return Settings.Secure.getInt(mContentResolver,
261 Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS, 60000);
262 }
263
264 /**
265 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS
266 */
267 private int getBackgroundCheckTimeoutMs() {
268 return Settings.Secure.getInt(mContentResolver,
269 Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS, 1000);
270 }
271
272 /**
273 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WATCH_LIST
274 * @return the comma-separated list of SSIDs
275 */
276 private String getWatchList() {
277 return Settings.Secure.getString(mContentResolver,
278 Settings.Secure.WIFI_WATCHDOG_WATCH_LIST);
279 }
280
281 /**
282 * Registers to receive the necessary Wi-Fi broadcasts.
283 */
284 private void registerForWifiBroadcasts() {
285 IntentFilter intentFilter = new IntentFilter();
286 intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800287 intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
288 mContext.registerReceiver(mReceiver, intentFilter);
289 }
290
291 /**
292 * Unregisters from receiving the Wi-Fi broadcasts.
293 */
294 private void unregisterForWifiBroadcasts() {
295 mContext.unregisterReceiver(mReceiver);
296 }
297
298 /**
299 * Creates the main watchdog thread, including waiting for the handler to be
300 * created.
301 */
302 private void createThread() {
303 mThread = new WifiWatchdogThread();
304 mThread.start();
305 waitForHandlerCreation();
306 }
307
308 /**
Irfan Sheriff7b009782010-03-11 16:37:45 -0800309 * Unregister broadcasts and quit the watchdog thread
310 */
Irfan Sheriffa2a1b912010-06-07 09:03:04 -0700311 //TODO: Change back to running WWS when needed
312// private void quit() {
313// unregisterForWifiBroadcasts();
314// mContext.getContentResolver().unregisterContentObserver(mContentObserver);
315// mHandler.removeAllActions();
316// mHandler.getLooper().quit();
317// }
Irfan Sheriff7b009782010-03-11 16:37:45 -0800318
319 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800320 * Waits for the main watchdog thread to create the handler.
321 */
322 private void waitForHandlerCreation() {
323 synchronized(this) {
324 while (mHandler == null) {
325 try {
326 // Wait for the handler to be set by the other thread
327 wait();
328 } catch (InterruptedException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800329 Slog.e(TAG, "Interrupted while waiting on handler.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800330 }
331 }
332 }
333 }
334
335 // Utility methods
336
337 /**
338 * Logs with the current thread.
339 */
340 private static void myLogV(String message) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800341 Slog.v(TAG, "(" + Thread.currentThread().getName() + ") " + message);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800342 }
343
344 private static void myLogD(String message) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800345 Slog.d(TAG, "(" + Thread.currentThread().getName() + ") " + message);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800346 }
347
348 /**
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800349 * Gets the first DNS of the current AP.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800350 *
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800351 * @return The first DNS of the current AP.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800352 */
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800353 private InetAddress getDns() {
354 if (mConnectivityManager == null) {
355 mConnectivityManager = (ConnectivityManager)mContext.getSystemService(
356 Context.CONNECTIVITY_SERVICE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800357 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800358
359 LinkProperties linkProperties = mConnectivityManager.getLinkProperties(
360 ConnectivityManager.TYPE_WIFI);
361 if (linkProperties == null) return null;
362
363 Collection<InetAddress> dnses = linkProperties.getDnses();
364 if (dnses == null || dnses.size() == 0) return null;
365
366 return dnses.iterator().next();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800367 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800368
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800369 /**
370 * Checks whether the DNS can be reached using multiple attempts according
371 * to the current setting values.
372 *
373 * @return Whether the DNS is reachable
374 */
375 private boolean checkDnsConnectivity() {
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800376 InetAddress dns = getDns();
377 if (dns == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800378 if (V) {
379 myLogV("checkDnsConnectivity: Invalid DNS, returning false");
380 }
381 return false;
382 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800383
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800384 if (V) {
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800385 myLogV("checkDnsConnectivity: Checking " + dns.getHostAddress() + " for connectivity");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800386 }
387
388 int numInitialIgnoredPings = getInitialIgnoredPingCount();
389 int numPings = getPingCount();
390 int pingDelay = getPingDelayMs();
391 int acceptableLoss = getAcceptablePacketLossPercentage();
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800392
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800393 /** See {@link Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT} */
394 int ignoredPingCounter = 0;
395 int pingCounter = 0;
396 int successCounter = 0;
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800397
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800398 // No connectivity check needed
399 if (numPings == 0) {
400 return true;
401 }
402
403 // Do the initial pings that we ignore
404 for (; ignoredPingCounter < numInitialIgnoredPings; ignoredPingCounter++) {
405 if (shouldCancel()) return false;
406
407 boolean dnsAlive = DnsPinger.isDnsReachable(dns, getPingTimeoutMs());
408 if (dnsAlive) {
409 /*
410 * Successful "ignored" pings are *not* ignored (they count in the total number
411 * of pings), but failures are really ignored.
412 */
413 pingCounter++;
414 successCounter++;
415 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800416
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800417 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800418 Slog.v(TAG, (dnsAlive ? " +" : " Ignored: -"));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800419 }
420
421 if (shouldCancel()) return false;
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800422
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800423 try {
424 Thread.sleep(pingDelay);
425 } catch (InterruptedException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800426 Slog.w(TAG, "Interrupted while pausing between pings", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800427 }
428 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800429
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800430 // Do the pings that we use to measure packet loss
431 for (; pingCounter < numPings; pingCounter++) {
432 if (shouldCancel()) return false;
433
434 if (DnsPinger.isDnsReachable(dns, getPingTimeoutMs())) {
435 successCounter++;
436 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800437 Slog.v(TAG, " +");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800438 }
439 } else {
440 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800441 Slog.v(TAG, " -");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800442 }
443 }
444
445 if (shouldCancel()) return false;
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800446
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800447 try {
448 Thread.sleep(pingDelay);
449 } catch (InterruptedException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800450 Slog.w(TAG, "Interrupted while pausing between pings", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800451 }
452 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800453
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800454 int packetLossPercentage = 100 * (numPings - successCounter) / numPings;
455 if (D) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800456 Slog.d(TAG, packetLossPercentage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800457 + "% packet loss (acceptable is " + acceptableLoss + "%)");
458 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800459
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800460 return !shouldCancel() && (packetLossPercentage <= acceptableLoss);
461 }
462
463 private boolean backgroundCheckDnsConnectivity() {
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800464 InetAddress dns = getDns();
465
466 if (dns == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800467 if (V) {
468 myLogV("backgroundCheckDnsConnectivity: DNS is empty, returning false");
469 }
470 return false;
471 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800472
473 if (false && V) {
474 myLogV("backgroundCheckDnsConnectivity: Background checking " +
475 dns.getHostAddress() + " for connectivity");
476 }
477
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800478 return DnsPinger.isDnsReachable(dns, getBackgroundCheckTimeoutMs());
479 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800480
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800481 /**
482 * Signals the current action to cancel.
483 */
484 private void cancelCurrentAction() {
485 mShouldCancel = true;
486 }
487
488 /**
489 * Helper to check whether to cancel.
490 *
491 * @return Whether to cancel processing the action.
492 */
493 private boolean shouldCancel() {
494 if (V && mShouldCancel) {
495 myLogV("shouldCancel: Cancelling");
496 }
497
498 return mShouldCancel;
499 }
500
501 // Wi-Fi initiated callbacks (could be executed in another thread)
502
503 /**
504 * Called when connected to an AP (this can be the next AP in line, or
505 * it can be a completely different network).
506 *
507 * @param ssid The SSID of the access point.
508 * @param bssid The BSSID of the access point.
509 */
510 private void onConnected(String ssid, String bssid) {
511 if (V) {
512 myLogV("onConnected: SSID: " + ssid + ", BSSID: " + bssid);
513 }
514
515 /*
516 * The current action being processed by the main watchdog thread is now
517 * stale, so cancel it.
518 */
519 cancelCurrentAction();
520
521 if ((mSsid == null) || !mSsid.equals(ssid)) {
522 /*
523 * This is a different network than what the main watchdog thread is
524 * processing, dispatch the network change message on the main thread.
525 */
526 mHandler.dispatchNetworkChanged(ssid);
527 }
528
529 if (requiresWatchdog(ssid, bssid)) {
530 if (D) {
531 myLogD(ssid + " (" + bssid + ") requires the watchdog");
532 }
533
534 // This access point requires a watchdog, so queue the check on the main thread
535 mHandler.checkAp(new AccessPoint(ssid, bssid));
536
537 } else {
538 if (D) {
539 myLogD(ssid + " (" + bssid + ") does not require the watchdog");
540 }
541
542 // This access point does not require a watchdog, so queue idle on the main thread
543 mHandler.idle();
544 }
Irfan Sheriff5ea65d62011-06-05 21:29:56 -0700545 if (isWalledGardenTestEnabled()) mHandler.checkWalledGarden(ssid);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800546 }
547
548 /**
549 * Called when Wi-Fi is enabled.
550 */
551 private void onEnabled() {
552 cancelCurrentAction();
553 // Queue a hard-reset of the state on the main thread
554 mHandler.reset();
555 }
556
557 /**
558 * Called when disconnected (or some other event similar to being disconnected).
559 */
560 private void onDisconnected() {
561 if (V) {
562 myLogV("onDisconnected");
563 }
564
565 /*
566 * Disconnected from an access point, the action being processed by the
567 * watchdog thread is now stale, so cancel it.
568 */
569 cancelCurrentAction();
570 // Dispatch the disconnected to the main watchdog thread
571 mHandler.dispatchDisconnected();
572 // Queue the action to go idle
573 mHandler.idle();
574 }
575
576 /**
577 * Checks whether an access point requires watchdog monitoring.
578 *
579 * @param ssid The SSID of the access point.
580 * @param bssid The BSSID of the access point.
581 * @return Whether the access point/network should be monitored by the
582 * watchdog.
583 */
584 private boolean requiresWatchdog(String ssid, String bssid) {
585 if (V) {
586 myLogV("requiresWatchdog: SSID: " + ssid + ", BSSID: " + bssid);
587 }
588
589 WifiInfo info = null;
590 if (ssid == null) {
591 /*
592 * This is called from a Wi-Fi callback, so assume the WifiInfo does
593 * not have stale data.
594 */
595 info = mWifiManager.getConnectionInfo();
596 ssid = info.getSSID();
597 if (ssid == null) {
598 // It's still null, give up
599 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800600 Slog.v(TAG, " Invalid SSID, returning false");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800601 }
602 return false;
603 }
604 }
605
606 if (TextUtils.isEmpty(bssid)) {
607 // Similar as above
608 if (info == null) {
609 info = mWifiManager.getConnectionInfo();
610 }
611 bssid = info.getBSSID();
612 if (TextUtils.isEmpty(bssid)) {
613 // It's still null, give up
614 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800615 Slog.v(TAG, " Invalid BSSID, returning false");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800616 }
617 return false;
618 }
619 }
620
621 if (!isOnWatchList(ssid)) {
622 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800623 Slog.v(TAG, " SSID not on watch list, returning false");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800624 }
625 return false;
626 }
627
628 // The watchdog only monitors networks with multiple APs
629 if (!hasRequiredNumberOfAps(ssid)) {
630 return false;
631 }
632
633 return true;
634 }
635
636 private boolean isOnWatchList(String ssid) {
637 String watchList;
638
639 if (ssid == null || (watchList = getWatchList()) == null) {
640 return false;
641 }
642
643 String[] list = watchList.split(" *, *");
644
645 for (String name : list) {
646 if (ssid.equals(name)) {
647 return true;
648 }
649 }
650
651 return false;
652 }
653
654 /**
655 * Checks if the current scan results have multiple access points with an SSID.
656 *
657 * @param ssid The SSID to check.
658 * @return Whether the SSID has multiple access points.
659 */
660 private boolean hasRequiredNumberOfAps(String ssid) {
661 List<ScanResult> results = mWifiManager.getScanResults();
662 if (results == null) {
663 if (V) {
664 myLogV("hasRequiredNumberOfAps: Got null scan results, returning false");
665 }
666 return false;
667 }
668
669 int numApsRequired = getApCount();
670 int numApsFound = 0;
671 int resultsSize = results.size();
672 for (int i = 0; i < resultsSize; i++) {
673 ScanResult result = results.get(i);
674 if (result == null) continue;
675 if (result.SSID == null) continue;
676
677 if (result.SSID.equals(ssid)) {
678 numApsFound++;
679
680 if (numApsFound >= numApsRequired) {
681 if (V) {
682 myLogV("hasRequiredNumberOfAps: SSID: " + ssid + ", returning true");
683 }
684 return true;
685 }
686 }
687 }
688
689 if (V) {
690 myLogV("hasRequiredNumberOfAps: SSID: " + ssid + ", returning false");
691 }
692 return false;
693 }
694
695 // Watchdog logic (assume all of these methods will be in our main thread)
696
697 /**
698 * Handles a Wi-Fi network change (for example, from networkA to networkB).
699 */
700 private void handleNetworkChanged(String ssid) {
701 // Set the SSID being monitored to the new SSID
702 mSsid = ssid;
703 // Set various state to that when being idle
704 setIdleState(true);
705 }
706
707 /**
708 * Handles checking whether an AP is a "good" AP. If not, it will be blacklisted.
709 *
710 * @param ap The access point to check.
711 */
712 private void handleCheckAp(AccessPoint ap) {
713 // Reset the cancel state since this is the entry point of this action
714 mShouldCancel = false;
715
716 if (V) {
717 myLogV("handleCheckAp: AccessPoint: " + ap);
718 }
719
720 // Make sure we are not sleeping
721 if (mState == WatchdogState.SLEEP) {
722 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800723 Slog.v(TAG, " Sleeping (in " + mSsid + "), so returning");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800724 }
725 return;
726 }
727
728 mState = WatchdogState.CHECKING_AP;
729
730 /*
731 * Checks to make sure we haven't exceeded the max number of checks
732 * we're allowed per network
733 */
734 mNumApsChecked++;
735 if (mNumApsChecked > getMaxApChecks()) {
736 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800737 Slog.v(TAG, " Passed the max attempts (" + getMaxApChecks()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800738 + "), going to sleep for " + mSsid);
739 }
740 mHandler.sleep(mSsid);
741 return;
742 }
743
744 // Do the check
745 boolean isApAlive = checkDnsConnectivity();
746
747 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800748 Slog.v(TAG, " Is it alive: " + isApAlive);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800749 }
750
751 // Take action based on results
752 if (isApAlive) {
753 handleApAlive(ap);
754 } else {
755 handleApUnresponsive(ap);
756 }
757 }
758
759 /**
760 * Handles the case when an access point is alive.
761 *
762 * @param ap The access point.
763 */
764 private void handleApAlive(AccessPoint ap) {
765 // Check whether we are stale and should cancel
766 if (shouldCancel()) return;
767 // We're satisfied with this AP, so go idle
768 setIdleState(false);
769
770 if (D) {
771 myLogD("AP is alive: " + ap.toString());
772 }
773
774 // Queue the next action to be a background check
775 mHandler.backgroundCheckAp(ap);
776 }
777
778 /**
779 * Handles an unresponsive AP by blacklisting it.
780 *
781 * @param ap The access point.
782 */
783 private void handleApUnresponsive(AccessPoint ap) {
784 // Check whether we are stale and should cancel
785 if (shouldCancel()) return;
786 // This AP is "bad", switch to another
787 mState = WatchdogState.SWITCHING_AP;
788
789 if (D) {
790 myLogD("AP is dead: " + ap.toString());
791 }
792
793 // Black list this "bad" AP, this will cause an attempt to connect to another
794 blacklistAp(ap.bssid);
Irfan Sheriff0049a1b2010-01-14 12:37:49 -0800795 // Initiate an association to an alternate AP
Irfan Sheriff0d255342010-07-28 09:35:20 -0700796 mWifiManager.reassociate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800797 }
798
799 private void blacklistAp(String bssid) {
800 if (TextUtils.isEmpty(bssid)) {
801 return;
802 }
803
804 // Before taking action, make sure we should not cancel our processing
805 if (shouldCancel()) return;
806
Irfan Sheriff0d255342010-07-28 09:35:20 -0700807 mWifiManager.addToBlacklist(bssid);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800808
809 if (D) {
810 myLogD("Blacklisting " + bssid);
811 }
812 }
813
814 /**
815 * Handles a single background check. If it fails, it should trigger a
816 * normal check. If it succeeds, it should queue another background check.
817 *
818 * @param ap The access point to do a background check for. If this is no
819 * longer the current AP, it is okay to return without any
820 * processing.
821 */
822 private void handleBackgroundCheckAp(AccessPoint ap) {
823 // Reset the cancel state since this is the entry point of this action
824 mShouldCancel = false;
825
826 if (false && V) {
827 myLogV("handleBackgroundCheckAp: AccessPoint: " + ap);
828 }
829
830 // Make sure we are not sleeping
831 if (mState == WatchdogState.SLEEP) {
832 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800833 Slog.v(TAG, " handleBackgroundCheckAp: Sleeping (in " + mSsid + "), so returning");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800834 }
835 return;
836 }
837
838 // Make sure the AP we're supposed to be background checking is still the active one
839 WifiInfo info = mWifiManager.getConnectionInfo();
840 if (info.getSSID() == null || !info.getSSID().equals(ap.ssid)) {
841 if (V) {
842 myLogV("handleBackgroundCheckAp: We are no longer connected to "
843 + ap + ", and instead are on " + info);
844 }
845 return;
846 }
847
848 if (info.getBSSID() == null || !info.getBSSID().equals(ap.bssid)) {
849 if (V) {
850 myLogV("handleBackgroundCheckAp: We are no longer connected to "
851 + ap + ", and instead are on " + info);
852 }
853 return;
854 }
855
856 // Do the check
857 boolean isApAlive = backgroundCheckDnsConnectivity();
858
859 if (V && !isApAlive) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800860 Slog.v(TAG, " handleBackgroundCheckAp: Is it alive: " + isApAlive);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800861 }
862
863 if (shouldCancel()) {
864 return;
865 }
866
867 // Take action based on results
868 if (isApAlive) {
869 // Queue another background check
870 mHandler.backgroundCheckAp(ap);
871
872 } else {
873 if (D) {
874 myLogD("Background check failed for " + ap.toString());
875 }
876
877 // Queue a normal check, so it can take proper action
878 mHandler.checkAp(ap);
879 }
880 }
881
882 /**
883 * Handles going to sleep for this network. Going to sleep means we will not
884 * monitor this network anymore.
885 *
886 * @param ssid The network that will not be monitored anymore.
887 */
888 private void handleSleep(String ssid) {
889 // Make sure the network we're trying to sleep in is still the current network
890 if (ssid != null && ssid.equals(mSsid)) {
891 mState = WatchdogState.SLEEP;
892
893 if (D) {
894 myLogD("Going to sleep for " + ssid);
895 }
896
897 /*
898 * Before deciding to go to sleep, we may have checked a few APs
899 * (and blacklisted them). Clear the blacklist so the AP with best
900 * signal is chosen.
901 */
Irfan Sheriff0d255342010-07-28 09:35:20 -0700902 mWifiManager.clearBlacklist();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800903
904 if (V) {
905 myLogV("handleSleep: Set state to SLEEP and cleared blacklist");
906 }
907 }
908 }
909
910 /**
911 * Handles an access point disconnection.
912 */
913 private void handleDisconnected() {
914 /*
915 * We purposefully do not change mSsid to null. This is to handle
916 * disconnected followed by connected better (even if there is some
917 * duration in between). For example, if the watchdog went to sleep in a
918 * network, and then the phone goes to sleep, when the phone wakes up we
919 * still want to be in the sleeping state. When the phone went to sleep,
920 * we would have gotten a disconnected event which would then set mSsid
921 * = null. This is bad, since the following connect would cause us to do
922 * the "network is good?" check all over again. */
923
924 /*
925 * Set the state as if we were idle (don't come out of sleep, only
926 * hard reset and network changed should do that.
927 */
928 setIdleState(false);
929 }
930
931 /**
932 * Handles going idle. Idle means we are satisfied with the current state of
933 * things, but if a new connection occurs we'll re-evaluate.
934 */
935 private void handleIdle() {
936 // Reset the cancel state since this is the entry point for this action
937 mShouldCancel = false;
938
939 if (V) {
940 myLogV("handleSwitchToIdle");
941 }
942
943 // If we're sleeping, don't do anything
944 if (mState == WatchdogState.SLEEP) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800945 Slog.v(TAG, " Sleeping (in " + mSsid + "), so returning");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800946 return;
947 }
948
949 // Set the idle state
950 setIdleState(false);
951
952 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800953 Slog.v(TAG, " Set state to IDLE");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800954 }
955 }
956
957 /**
958 * Sets the state as if we are going idle.
959 */
960 private void setIdleState(boolean forceIdleState) {
961 // Setting idle state does not kick us out of sleep unless the forceIdleState is set
962 if (forceIdleState || (mState != WatchdogState.SLEEP)) {
963 mState = WatchdogState.IDLE;
964 }
965 mNumApsChecked = 0;
966 }
967
968 /**
969 * Handles a hard reset. A hard reset is rarely used, but when used it
970 * should revert anything done by the watchdog monitoring.
971 */
972 private void handleReset() {
Irfan Sheriff0d255342010-07-28 09:35:20 -0700973 mWifiManager.clearBlacklist();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800974 setIdleState(true);
975 }
976
977 // Inner classes
978
979 /**
980 * Possible states for the watchdog to be in.
981 */
982 private static enum WatchdogState {
983 /** The watchdog is currently idle, but it is still responsive to future AP checks in this network. */
984 IDLE,
985 /** The watchdog is sleeping, so it will not try any AP checks for the network. */
986 SLEEP,
987 /** The watchdog is currently checking an AP for connectivity. */
988 CHECKING_AP,
989 /** The watchdog is switching to another AP in the network. */
990 SWITCHING_AP
991 }
992
993 /**
994 * The main thread for the watchdog monitoring. This will be turned into a
995 * {@link Looper} thread.
996 */
997 private class WifiWatchdogThread extends Thread {
998 WifiWatchdogThread() {
999 super("WifiWatchdogThread");
1000 }
1001
1002 @Override
1003 public void run() {
1004 // Set this thread up so the handler will work on it
1005 Looper.prepare();
1006
1007 synchronized(WifiWatchdogService.this) {
1008 mHandler = new WifiWatchdogHandler();
1009
1010 // Notify that the handler has been created
1011 WifiWatchdogService.this.notify();
1012 }
1013
1014 // Listen for messages to the handler
1015 Looper.loop();
1016 }
1017 }
1018
1019 /**
1020 * The main thread's handler. There are 'actions', and just general
1021 * 'messages'. There should only ever be one 'action' in the queue (aside
1022 * from the one being processed, if any). There may be multiple messages in
1023 * the queue. So, actions are replaced by more recent actions, where as
1024 * messages will be executed for sure. Messages end up being used to just
1025 * change some state, and not really take any action.
1026 * <p>
1027 * There is little logic inside this class, instead methods of the form
1028 * "handle___" are called in the main {@link WifiWatchdogService}.
1029 */
1030 private class WifiWatchdogHandler extends Handler {
1031 /** Check whether the AP is "good". The object will be an {@link AccessPoint}. */
1032 static final int ACTION_CHECK_AP = 1;
1033 /** Go into the idle state. */
1034 static final int ACTION_IDLE = 2;
1035 /**
1036 * Performs a periodic background check whether the AP is still "good".
1037 * The object will be an {@link AccessPoint}.
1038 */
1039 static final int ACTION_BACKGROUND_CHECK_AP = 3;
Irfan Sheriff2f5f4bc2011-05-02 11:43:28 -07001040 /** Check whether the connection is a walled garden */
1041 static final int ACTION_CHECK_WALLED_GARDEN = 4;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001042
1043 /**
1044 * Go to sleep for the current network. We are conservative with making
1045 * this a message rather than action. We want to make sure our main
1046 * thread sees this message, but if it were an action it could be
1047 * removed from the queue and replaced by another action. The main
1048 * thread will ensure when it sees the message that the state is still
1049 * valid for going to sleep.
1050 * <p>
1051 * For an explanation of sleep, see {@link android.provider.Settings.Secure#WIFI_WATCHDOG_MAX_AP_CHECKS}.
1052 */
1053 static final int MESSAGE_SLEEP = 101;
1054 /** Disables the watchdog. */
1055 static final int MESSAGE_DISABLE_WATCHDOG = 102;
1056 /** The network has changed. */
1057 static final int MESSAGE_NETWORK_CHANGED = 103;
1058 /** The current access point has disconnected. */
1059 static final int MESSAGE_DISCONNECTED = 104;
1060 /** Performs a hard-reset on the watchdog state. */
1061 static final int MESSAGE_RESET = 105;
Irfan Sheriff2f5f4bc2011-05-02 11:43:28 -07001062
1063 /* Walled garden detection */
1064 private String mLastSsid;
1065 private long mLastTime;
1066 private final long MIN_WALLED_GARDEN_TEST_INTERVAL = 15 * 60 * 1000; //15 minutes
1067
1068 void checkWalledGarden(String ssid) {
1069 sendMessage(obtainMessage(ACTION_CHECK_WALLED_GARDEN, ssid));
1070 }
1071
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001072 void checkAp(AccessPoint ap) {
1073 removeAllActions();
1074 sendMessage(obtainMessage(ACTION_CHECK_AP, ap));
1075 }
1076
1077 void backgroundCheckAp(AccessPoint ap) {
1078 if (!isBackgroundCheckEnabled()) return;
1079
1080 removeAllActions();
1081 sendMessageDelayed(obtainMessage(ACTION_BACKGROUND_CHECK_AP, ap),
1082 getBackgroundCheckDelayMs());
1083 }
1084
1085 void idle() {
1086 removeAllActions();
1087 sendMessage(obtainMessage(ACTION_IDLE));
1088 }
1089
1090 void sleep(String ssid) {
1091 removeAllActions();
1092 sendMessage(obtainMessage(MESSAGE_SLEEP, ssid));
1093 }
1094
1095 void disableWatchdog() {
1096 removeAllActions();
1097 sendMessage(obtainMessage(MESSAGE_DISABLE_WATCHDOG));
1098 }
1099
1100 void dispatchNetworkChanged(String ssid) {
1101 removeAllActions();
1102 sendMessage(obtainMessage(MESSAGE_NETWORK_CHANGED, ssid));
1103 }
1104
1105 void dispatchDisconnected() {
1106 removeAllActions();
1107 sendMessage(obtainMessage(MESSAGE_DISCONNECTED));
1108 }
1109
1110 void reset() {
1111 removeAllActions();
1112 sendMessage(obtainMessage(MESSAGE_RESET));
1113 }
1114
1115 private void removeAllActions() {
1116 removeMessages(ACTION_CHECK_AP);
1117 removeMessages(ACTION_IDLE);
1118 removeMessages(ACTION_BACKGROUND_CHECK_AP);
1119 }
1120
1121 @Override
1122 public void handleMessage(Message msg) {
1123 switch (msg.what) {
1124 case MESSAGE_NETWORK_CHANGED:
1125 handleNetworkChanged((String) msg.obj);
1126 break;
1127 case ACTION_CHECK_AP:
1128 handleCheckAp((AccessPoint) msg.obj);
1129 break;
1130 case ACTION_BACKGROUND_CHECK_AP:
1131 handleBackgroundCheckAp((AccessPoint) msg.obj);
1132 break;
Irfan Sheriff2f5f4bc2011-05-02 11:43:28 -07001133 case ACTION_CHECK_WALLED_GARDEN:
1134 handleWalledGardenCheck((String) msg.obj);
1135 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001136 case MESSAGE_SLEEP:
1137 handleSleep((String) msg.obj);
1138 break;
1139 case ACTION_IDLE:
1140 handleIdle();
1141 break;
1142 case MESSAGE_DISABLE_WATCHDOG:
1143 handleIdle();
1144 break;
1145 case MESSAGE_DISCONNECTED:
1146 handleDisconnected();
1147 break;
1148 case MESSAGE_RESET:
1149 handleReset();
1150 break;
1151 }
1152 }
Irfan Sheriff2f5f4bc2011-05-02 11:43:28 -07001153
Irfan Sheriff5ea65d62011-06-05 21:29:56 -07001154 /**
1155 * DNS based detection techniques do not work at all hotspots. The one sure way to check
1156 * a walled garden is to see if a URL fetch on a known address fetches the data we
1157 * expect
1158 */
Irfan Sheriff2f5f4bc2011-05-02 11:43:28 -07001159 private boolean isWalledGardenConnection() {
Irfan Sheriff5ea65d62011-06-05 21:29:56 -07001160 InputStream in = null;
1161 HttpURLConnection urlConnection = null;
Irfan Sheriff2f5f4bc2011-05-02 11:43:28 -07001162 try {
Irfan Sheriff5ea65d62011-06-05 21:29:56 -07001163 URL url = new URL(getWalledGardenUrl());
1164 urlConnection = (HttpURLConnection) url.openConnection();
1165 in = new BufferedInputStream(urlConnection.getInputStream());
1166 Scanner scanner = new Scanner(in);
1167 if (scanner.findInLine(getWalledGardenPattern()) != null) {
1168 return false;
1169 } else {
1170 return true;
1171 }
1172 } catch (IOException e) {
Irfan Sheriff2f5f4bc2011-05-02 11:43:28 -07001173 return false;
Irfan Sheriff5ea65d62011-06-05 21:29:56 -07001174 } finally {
1175 if (in != null) {
1176 try {
1177 in.close();
1178 } catch (IOException e) {
1179 }
1180 }
1181 if (urlConnection != null) urlConnection.disconnect();
Irfan Sheriff2f5f4bc2011-05-02 11:43:28 -07001182 }
Irfan Sheriff2f5f4bc2011-05-02 11:43:28 -07001183 }
1184
1185 private void handleWalledGardenCheck(String ssid) {
1186 long currentTime = System.currentTimeMillis();
1187 //Avoid a walled garden test on the same network if one was already done
1188 //within MIN_WALLED_GARDEN_TEST_INTERVAL. This will handle scenarios where
1189 //there are frequent network disconnections
1190 if (ssid.equals(mLastSsid) &&
1191 (currentTime - mLastTime) < MIN_WALLED_GARDEN_TEST_INTERVAL) {
1192 return;
1193 }
1194
1195 mLastTime = currentTime;
1196 mLastSsid = ssid;
1197
1198 if (isWalledGardenConnection()) {
1199 Uri uri = Uri.parse("http://www.google.com");
1200 Intent intent = new Intent(Intent.ACTION_VIEW, uri);
1201 intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
1202 Intent.FLAG_ACTIVITY_NEW_TASK);
1203 mContext.startActivity(intent);
1204 }
1205 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001206 }
1207
1208 /**
1209 * Receives Wi-Fi broadcasts.
1210 * <p>
1211 * There is little logic in this class, instead methods of the form "on___"
1212 * are called in the {@link WifiWatchdogService}.
1213 */
1214 private BroadcastReceiver mReceiver = new BroadcastReceiver() {
1215
1216 @Override
1217 public void onReceive(Context context, Intent intent) {
1218 final String action = intent.getAction();
1219 if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
1220 handleNetworkStateChanged(
1221 (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001222 } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
1223 handleWifiStateChanged(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
1224 WifiManager.WIFI_STATE_UNKNOWN));
1225 }
1226 }
1227
1228 private void handleNetworkStateChanged(NetworkInfo info) {
1229 if (V) {
1230 myLogV("Receiver.handleNetworkStateChanged: NetworkInfo: "
1231 + info);
1232 }
1233
1234 switch (info.getState()) {
1235 case CONNECTED:
1236 WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
1237 if (wifiInfo.getSSID() == null || wifiInfo.getBSSID() == null) {
1238 if (V) {
1239 myLogV("handleNetworkStateChanged: Got connected event but SSID or BSSID are null. SSID: "
1240 + wifiInfo.getSSID()
1241 + ", BSSID: "
1242 + wifiInfo.getBSSID() + ", ignoring event");
1243 }
1244 return;
1245 }
1246 onConnected(wifiInfo.getSSID(), wifiInfo.getBSSID());
1247 break;
1248
1249 case DISCONNECTED:
1250 onDisconnected();
1251 break;
1252 }
1253 }
1254
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001255 private void handleWifiStateChanged(int wifiState) {
1256 if (wifiState == WifiManager.WIFI_STATE_DISABLED) {
Irfan Sheriffa2a1b912010-06-07 09:03:04 -07001257 onDisconnected();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001258 } else if (wifiState == WifiManager.WIFI_STATE_ENABLED) {
1259 onEnabled();
1260 }
1261 }
1262 };
1263
1264 /**
1265 * Describes an access point by its SSID and BSSID.
1266 */
1267 private static class AccessPoint {
1268 String ssid;
1269 String bssid;
1270
1271 AccessPoint(String ssid, String bssid) {
1272 this.ssid = ssid;
1273 this.bssid = bssid;
1274 }
1275
1276 private boolean hasNull() {
1277 return ssid == null || bssid == null;
1278 }
1279
1280 @Override
1281 public boolean equals(Object o) {
1282 if (!(o instanceof AccessPoint)) return false;
1283 AccessPoint otherAp = (AccessPoint) o;
1284 boolean iHaveNull = hasNull();
1285 // Either we both have a null, or our SSIDs and BSSIDs are equal
1286 return (iHaveNull && otherAp.hasNull()) ||
1287 (otherAp.bssid != null && ssid.equals(otherAp.ssid)
1288 && bssid.equals(otherAp.bssid));
1289 }
1290
1291 @Override
1292 public int hashCode() {
1293 if (ssid == null || bssid == null) return 0;
1294 return ssid.hashCode() + bssid.hashCode();
1295 }
1296
1297 @Override
1298 public String toString() {
1299 return ssid + " (" + bssid + ")";
1300 }
1301 }
1302
1303 /**
1304 * Performs a simple DNS "ping" by sending a "server status" query packet to
1305 * the DNS server. As long as the server replies, we consider it a success.
1306 * <p>
1307 * We do not use a simple hostname lookup because that could be cached and
1308 * the API may not differentiate between a time out and a failure lookup
1309 * (which we really care about).
1310 */
1311 private static class DnsPinger {
1312
1313 /** Number of bytes for the query */
1314 private static final int DNS_QUERY_BASE_SIZE = 33;
1315
1316 /** The DNS port */
1317 private static final int DNS_PORT = 53;
1318
1319 /** Used to generate IDs */
1320 private static Random sRandom = new Random();
Robert Greenwalt8e9abc52011-02-16 10:37:50 -08001321
1322 static boolean isDnsReachable(InetAddress dnsAddress, int timeout) {
Nick Kralevich929b4852010-06-09 14:27:43 -07001323 DatagramSocket socket = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001324 try {
Nick Kralevich929b4852010-06-09 14:27:43 -07001325 socket = new DatagramSocket();
Robert Greenwalt8e9abc52011-02-16 10:37:50 -08001326
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001327 // Set some socket properties
1328 socket.setSoTimeout(timeout);
Robert Greenwalt8e9abc52011-02-16 10:37:50 -08001329
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001330 byte[] buf = new byte[DNS_QUERY_BASE_SIZE];
1331 fillQuery(buf);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001332
Robert Greenwalt8e9abc52011-02-16 10:37:50 -08001333 // Send the DNS query
1334
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001335 DatagramPacket packet = new DatagramPacket(buf,
1336 buf.length, dnsAddress, DNS_PORT);
1337 socket.send(packet);
Robert Greenwalt8e9abc52011-02-16 10:37:50 -08001338
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001339 // Wait for reply (blocks for the above timeout)
1340 DatagramPacket replyPacket = new DatagramPacket(buf, buf.length);
1341 socket.receive(replyPacket);
1342
1343 // If a timeout occurred, an exception would have been thrown. We got a reply!
1344 return true;
Robert Greenwalt8e9abc52011-02-16 10:37:50 -08001345
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001346 } catch (SocketException e) {
1347 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001348 Slog.v(TAG, "DnsPinger.isReachable received SocketException", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001349 }
1350 return false;
Robert Greenwalt8e9abc52011-02-16 10:37:50 -08001351
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001352 } catch (UnknownHostException e) {
1353 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001354 Slog.v(TAG, "DnsPinger.isReachable is unable to resolve the DNS host", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001355 }
1356 return false;
1357
1358 } catch (SocketTimeoutException e) {
1359 return false;
1360
1361 } catch (IOException e) {
1362 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001363 Slog.v(TAG, "DnsPinger.isReachable got an IOException", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001364 }
1365 return false;
1366
1367 } catch (Exception e) {
Joe Onorato43a17652011-04-06 19:22:23 -07001368 if (V || false) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001369 Slog.d(TAG, "DnsPinger.isReachable got an unknown exception", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001370 }
1371 return false;
Nick Kralevich929b4852010-06-09 14:27:43 -07001372 } finally {
1373 if (socket != null) {
1374 socket.close();
1375 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001376 }
1377 }
1378
1379 private static void fillQuery(byte[] buf) {
1380
1381 /*
1382 * See RFC2929 (though the bit tables in there are misleading for
1383 * us. For example, the recursion desired bit is the 0th bit for us,
1384 * but looking there it would appear as the 7th bit of the byte
1385 */
1386
1387 // Make sure it's all zeroed out
1388 for (int i = 0; i < buf.length; i++) buf[i] = 0;
1389
1390 // Form a query for www.android.com
1391
1392 // [0-1] bytes are an ID, generate random ID for this query
1393 buf[0] = (byte) sRandom.nextInt(256);
1394 buf[1] = (byte) sRandom.nextInt(256);
1395
1396 // [2-3] bytes are for flags.
1397 buf[2] = 1; // Recursion desired
1398
1399 // [4-5] bytes are for the query count
1400 buf[5] = 1; // One query
1401
1402 // [6-7] [8-9] [10-11] are all counts of other fields we don't use
1403
1404 // [12-15] for www
1405 writeString(buf, 12, "www");
1406
1407 // [16-23] for android
1408 writeString(buf, 16, "android");
1409
1410 // [24-27] for com
1411 writeString(buf, 24, "com");
1412
1413 // [29-30] bytes are for QTYPE, set to 1
1414 buf[30] = 1;
1415
1416 // [31-32] bytes are for QCLASS, set to 1
1417 buf[32] = 1;
1418 }
1419
1420 private static void writeString(byte[] buf, int startPos, String string) {
1421 int pos = startPos;
1422
1423 // Write the length first
1424 buf[pos++] = (byte) string.length();
1425 for (int i = 0; i < string.length(); i++) {
1426 buf[pos++] = (byte) string.charAt(i);
1427 }
1428 }
1429 }
1430}