blob: 56bfbe00fb091d848dc3ae9218ba0bb2f46b1635 [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";
Isaac Levy188cecf2011-06-08 13:38:24 -070081 private static final boolean V = false;
82 private static final boolean D = true;
83
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
Isaac Levy188cecf2011-06-08 13:38:24 -0700111 * have been checked. Only touched in the main thread, using getter/setter methods.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112 */
Isaac Levy188cecf2011-06-08 13:38:24 -0700113 private int mBssidCheckCount;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 /** Whether the current AP check should be canceled. */
115 private boolean mShouldCancel;
Isaac Levy188cecf2011-06-08 13:38:24 -0700116
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);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800121 createThread();
122
123 // The content observer to listen needs a handler, which createThread creates
124 registerForSettingsChanges();
125 if (isWatchdogEnabled()) {
126 registerForWifiBroadcasts();
127 }
128
129 if (V) {
130 myLogV("WifiWatchdogService: Created");
131 }
132 }
133
134 /**
135 * Observes the watchdog on/off setting, and takes action when changed.
136 */
137 private void registerForSettingsChanges() {
138 ContentResolver contentResolver = mContext.getContentResolver();
139 contentResolver.registerContentObserver(
140 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON), false,
Irfan Sheriff7b009782010-03-11 16:37:45 -0800141 mContentObserver = new ContentObserver(mHandler) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800142 @Override
143 public void onChange(boolean selfChange) {
144 if (isWatchdogEnabled()) {
145 registerForWifiBroadcasts();
146 } else {
147 unregisterForWifiBroadcasts();
148 if (mHandler != null) {
149 mHandler.disableWatchdog();
150 }
151 }
152 }
153 });
154 }
155
156 /**
157 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ON
158 */
159 private boolean isWatchdogEnabled() {
160 return Settings.Secure.getInt(mContentResolver, Settings.Secure.WIFI_WATCHDOG_ON, 1) == 1;
161 }
162
163 /**
164 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_AP_COUNT
165 */
166 private int getApCount() {
167 return Settings.Secure.getInt(mContentResolver,
168 Settings.Secure.WIFI_WATCHDOG_AP_COUNT, 2);
169 }
170
171 /**
172 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT
173 */
174 private int getInitialIgnoredPingCount() {
175 return Settings.Secure.getInt(mContentResolver,
176 Settings.Secure.WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT , 2);
177 }
178
179 /**
180 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_COUNT
181 */
182 private int getPingCount() {
183 return Settings.Secure.getInt(mContentResolver,
184 Settings.Secure.WIFI_WATCHDOG_PING_COUNT, 4);
185 }
186
187 /**
188 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_TIMEOUT_MS
189 */
190 private int getPingTimeoutMs() {
191 return Settings.Secure.getInt(mContentResolver,
192 Settings.Secure.WIFI_WATCHDOG_PING_TIMEOUT_MS, 500);
193 }
194
195 /**
196 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_DELAY_MS
197 */
198 private int getPingDelayMs() {
199 return Settings.Secure.getInt(mContentResolver,
200 Settings.Secure.WIFI_WATCHDOG_PING_DELAY_MS, 250);
201 }
Irfan Sheriff5ea65d62011-06-05 21:29:56 -0700202
203 /**
204 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED
205 */
206 private Boolean isWalledGardenTestEnabled() {
207 return Settings.Secure.getInt(mContentResolver,
208 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, 1) == 1;
209 }
210
211 /**
212 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_URL
213 */
214 private String getWalledGardenUrl() {
215 String url = Settings.Secure.getString(mContentResolver,
216 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL);
217 if (TextUtils.isEmpty(url)) return "http://www.google.com/";
218 return url;
219 }
220
221 /**
222 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_PATTERN
223 */
224 private String getWalledGardenPattern() {
225 String pattern = Settings.Secure.getString(mContentResolver,
226 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_PATTERN);
227 if (TextUtils.isEmpty(pattern)) return "<title>.*Google.*</title>";
228 return pattern;
229 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800230
231 /**
232 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE
233 */
234 private int getAcceptablePacketLossPercentage() {
235 return Settings.Secure.getInt(mContentResolver,
236 Settings.Secure.WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE, 25);
237 }
238
239 /**
240 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_MAX_AP_CHECKS
241 */
242 private int getMaxApChecks() {
243 return Settings.Secure.getInt(mContentResolver,
244 Settings.Secure.WIFI_WATCHDOG_MAX_AP_CHECKS, 7);
245 }
246
247 /**
248 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED
249 */
250 private boolean isBackgroundCheckEnabled() {
251 return Settings.Secure.getInt(mContentResolver,
252 Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED, 1) == 1;
253 }
254
255 /**
256 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS
257 */
258 private int getBackgroundCheckDelayMs() {
259 return Settings.Secure.getInt(mContentResolver,
260 Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS, 60000);
261 }
262
263 /**
264 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS
265 */
266 private int getBackgroundCheckTimeoutMs() {
267 return Settings.Secure.getInt(mContentResolver,
268 Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS, 1000);
269 }
270
271 /**
272 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WATCH_LIST
273 * @return the comma-separated list of SSIDs
274 */
275 private String getWatchList() {
276 return Settings.Secure.getString(mContentResolver,
277 Settings.Secure.WIFI_WATCHDOG_WATCH_LIST);
278 }
279
280 /**
281 * Registers to receive the necessary Wi-Fi broadcasts.
282 */
283 private void registerForWifiBroadcasts() {
284 IntentFilter intentFilter = new IntentFilter();
285 intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800286 intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
287 mContext.registerReceiver(mReceiver, intentFilter);
288 }
289
290 /**
291 * Unregisters from receiving the Wi-Fi broadcasts.
292 */
293 private void unregisterForWifiBroadcasts() {
294 mContext.unregisterReceiver(mReceiver);
295 }
296
297 /**
298 * Creates the main watchdog thread, including waiting for the handler to be
299 * created.
300 */
301 private void createThread() {
302 mThread = new WifiWatchdogThread();
303 mThread.start();
304 waitForHandlerCreation();
305 }
306
307 /**
Irfan Sheriff7b009782010-03-11 16:37:45 -0800308 * Unregister broadcasts and quit the watchdog thread
309 */
Irfan Sheriffa2a1b912010-06-07 09:03:04 -0700310 //TODO: Change back to running WWS when needed
311// private void quit() {
312// unregisterForWifiBroadcasts();
313// mContext.getContentResolver().unregisterContentObserver(mContentObserver);
314// mHandler.removeAllActions();
315// mHandler.getLooper().quit();
316// }
Irfan Sheriff7b009782010-03-11 16:37:45 -0800317
318 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800319 * Waits for the main watchdog thread to create the handler.
320 */
321 private void waitForHandlerCreation() {
322 synchronized(this) {
323 while (mHandler == null) {
324 try {
325 // Wait for the handler to be set by the other thread
326 wait();
327 } catch (InterruptedException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800328 Slog.e(TAG, "Interrupted while waiting on handler.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800329 }
330 }
331 }
332 }
333
334 // Utility methods
335
336 /**
337 * Logs with the current thread.
338 */
339 private static void myLogV(String message) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800340 Slog.v(TAG, "(" + Thread.currentThread().getName() + ") " + message);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800341 }
342
343 private static void myLogD(String message) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800344 Slog.d(TAG, "(" + Thread.currentThread().getName() + ") " + message);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800345 }
346
347 /**
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800348 * Gets the first DNS of the current AP.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800349 *
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800350 * @return The first DNS of the current AP.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800351 */
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800352 private InetAddress getDns() {
353 if (mConnectivityManager == null) {
354 mConnectivityManager = (ConnectivityManager)mContext.getSystemService(
355 Context.CONNECTIVITY_SERVICE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800356 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800357
358 LinkProperties linkProperties = mConnectivityManager.getLinkProperties(
359 ConnectivityManager.TYPE_WIFI);
360 if (linkProperties == null) return null;
361
362 Collection<InetAddress> dnses = linkProperties.getDnses();
363 if (dnses == null || dnses.size() == 0) return null;
364
365 return dnses.iterator().next();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800366 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800367
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800368 /**
369 * Checks whether the DNS can be reached using multiple attempts according
370 * to the current setting values.
371 *
372 * @return Whether the DNS is reachable
373 */
374 private boolean checkDnsConnectivity() {
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800375 InetAddress dns = getDns();
376 if (dns == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800377 if (V) {
378 myLogV("checkDnsConnectivity: Invalid DNS, returning false");
379 }
380 return false;
381 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800382
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800383 if (V) {
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800384 myLogV("checkDnsConnectivity: Checking " + dns.getHostAddress() + " for connectivity");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800385 }
386
387 int numInitialIgnoredPings = getInitialIgnoredPingCount();
388 int numPings = getPingCount();
389 int pingDelay = getPingDelayMs();
390 int acceptableLoss = getAcceptablePacketLossPercentage();
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800391
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800392 /** See {@link Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT} */
393 int ignoredPingCounter = 0;
394 int pingCounter = 0;
395 int successCounter = 0;
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800396
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800397 // No connectivity check needed
398 if (numPings == 0) {
399 return true;
400 }
401
402 // Do the initial pings that we ignore
403 for (; ignoredPingCounter < numInitialIgnoredPings; ignoredPingCounter++) {
404 if (shouldCancel()) return false;
405
406 boolean dnsAlive = DnsPinger.isDnsReachable(dns, getPingTimeoutMs());
407 if (dnsAlive) {
408 /*
409 * Successful "ignored" pings are *not* ignored (they count in the total number
410 * of pings), but failures are really ignored.
411 */
Isaac Levy188cecf2011-06-08 13:38:24 -0700412
413 // TODO: This is confusing logic and should be rewitten
414 // Here, successful 'ignored' pings are interpreted as a success in the below loop
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800415 pingCounter++;
416 successCounter++;
417 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800418
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800419 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800420 Slog.v(TAG, (dnsAlive ? " +" : " Ignored: -"));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800421 }
422
423 if (shouldCancel()) return false;
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800424
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800425 try {
426 Thread.sleep(pingDelay);
427 } catch (InterruptedException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800428 Slog.w(TAG, "Interrupted while pausing between pings", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800429 }
430 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800431
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800432 // Do the pings that we use to measure packet loss
433 for (; pingCounter < numPings; pingCounter++) {
434 if (shouldCancel()) return false;
435
436 if (DnsPinger.isDnsReachable(dns, getPingTimeoutMs())) {
437 successCounter++;
438 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800439 Slog.v(TAG, " +");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800440 }
441 } else {
442 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800443 Slog.v(TAG, " -");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800444 }
445 }
446
447 if (shouldCancel()) return false;
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800448
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800449 try {
450 Thread.sleep(pingDelay);
451 } catch (InterruptedException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800452 Slog.w(TAG, "Interrupted while pausing between pings", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800453 }
454 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800455
Isaac Levy188cecf2011-06-08 13:38:24 -0700456 //TODO: Integer division might cause problems down the road...
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800457 int packetLossPercentage = 100 * (numPings - successCounter) / numPings;
458 if (D) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800459 Slog.d(TAG, packetLossPercentage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800460 + "% packet loss (acceptable is " + acceptableLoss + "%)");
461 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800462
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800463 return !shouldCancel() && (packetLossPercentage <= acceptableLoss);
464 }
465
466 private boolean backgroundCheckDnsConnectivity() {
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800467 InetAddress dns = getDns();
468
469 if (dns == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800470 if (V) {
471 myLogV("backgroundCheckDnsConnectivity: DNS is empty, returning false");
472 }
473 return false;
474 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800475
Isaac Levy188cecf2011-06-08 13:38:24 -0700476 if (V) {
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800477 myLogV("backgroundCheckDnsConnectivity: Background checking " +
478 dns.getHostAddress() + " for connectivity");
479 }
480
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800481 return DnsPinger.isDnsReachable(dns, getBackgroundCheckTimeoutMs());
482 }
Robert Greenwalt8e9abc52011-02-16 10:37:50 -0800483
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800484 /**
485 * Signals the current action to cancel.
486 */
487 private void cancelCurrentAction() {
488 mShouldCancel = true;
489 }
490
491 /**
492 * Helper to check whether to cancel.
493 *
494 * @return Whether to cancel processing the action.
495 */
496 private boolean shouldCancel() {
497 if (V && mShouldCancel) {
498 myLogV("shouldCancel: Cancelling");
499 }
500
501 return mShouldCancel;
502 }
503
504 // Wi-Fi initiated callbacks (could be executed in another thread)
505
506 /**
507 * Called when connected to an AP (this can be the next AP in line, or
508 * it can be a completely different network).
509 *
510 * @param ssid The SSID of the access point.
511 * @param bssid The BSSID of the access point.
512 */
513 private void onConnected(String ssid, String bssid) {
514 if (V) {
515 myLogV("onConnected: SSID: " + ssid + ", BSSID: " + bssid);
516 }
517
518 /*
519 * The current action being processed by the main watchdog thread is now
520 * stale, so cancel it.
521 */
522 cancelCurrentAction();
523
524 if ((mSsid == null) || !mSsid.equals(ssid)) {
525 /*
526 * This is a different network than what the main watchdog thread is
527 * processing, dispatch the network change message on the main thread.
528 */
529 mHandler.dispatchNetworkChanged(ssid);
530 }
531
532 if (requiresWatchdog(ssid, bssid)) {
533 if (D) {
534 myLogD(ssid + " (" + bssid + ") requires the watchdog");
535 }
536
537 // This access point requires a watchdog, so queue the check on the main thread
538 mHandler.checkAp(new AccessPoint(ssid, bssid));
539
540 } else {
541 if (D) {
542 myLogD(ssid + " (" + bssid + ") does not require the watchdog");
543 }
544
545 // This access point does not require a watchdog, so queue idle on the main thread
546 mHandler.idle();
547 }
Irfan Sheriff5ea65d62011-06-05 21:29:56 -0700548 if (isWalledGardenTestEnabled()) mHandler.checkWalledGarden(ssid);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800549 }
550
551 /**
552 * Called when Wi-Fi is enabled.
553 */
554 private void onEnabled() {
555 cancelCurrentAction();
556 // Queue a hard-reset of the state on the main thread
557 mHandler.reset();
558 }
559
560 /**
561 * Called when disconnected (or some other event similar to being disconnected).
562 */
563 private void onDisconnected() {
564 if (V) {
565 myLogV("onDisconnected");
566 }
567
568 /*
569 * Disconnected from an access point, the action being processed by the
570 * watchdog thread is now stale, so cancel it.
571 */
572 cancelCurrentAction();
573 // Dispatch the disconnected to the main watchdog thread
574 mHandler.dispatchDisconnected();
575 // Queue the action to go idle
576 mHandler.idle();
577 }
578
579 /**
580 * Checks whether an access point requires watchdog monitoring.
581 *
582 * @param ssid The SSID of the access point.
583 * @param bssid The BSSID of the access point.
584 * @return Whether the access point/network should be monitored by the
585 * watchdog.
586 */
587 private boolean requiresWatchdog(String ssid, String bssid) {
588 if (V) {
589 myLogV("requiresWatchdog: SSID: " + ssid + ", BSSID: " + bssid);
590 }
591
592 WifiInfo info = null;
593 if (ssid == null) {
594 /*
595 * This is called from a Wi-Fi callback, so assume the WifiInfo does
596 * not have stale data.
597 */
598 info = mWifiManager.getConnectionInfo();
599 ssid = info.getSSID();
600 if (ssid == null) {
601 // It's still null, give up
602 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800603 Slog.v(TAG, " Invalid SSID, returning false");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800604 }
605 return false;
606 }
607 }
608
609 if (TextUtils.isEmpty(bssid)) {
610 // Similar as above
611 if (info == null) {
612 info = mWifiManager.getConnectionInfo();
613 }
614 bssid = info.getBSSID();
615 if (TextUtils.isEmpty(bssid)) {
616 // It's still null, give up
617 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800618 Slog.v(TAG, " Invalid BSSID, returning false");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800619 }
620 return false;
621 }
622 }
623
624 if (!isOnWatchList(ssid)) {
625 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800626 Slog.v(TAG, " SSID not on watch list, returning false");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800627 }
628 return false;
629 }
630
631 // The watchdog only monitors networks with multiple APs
632 if (!hasRequiredNumberOfAps(ssid)) {
633 return false;
634 }
635
636 return true;
637 }
638
639 private boolean isOnWatchList(String ssid) {
640 String watchList;
641
642 if (ssid == null || (watchList = getWatchList()) == null) {
643 return false;
644 }
645
646 String[] list = watchList.split(" *, *");
647
648 for (String name : list) {
649 if (ssid.equals(name)) {
650 return true;
651 }
652 }
653
654 return false;
655 }
656
657 /**
658 * Checks if the current scan results have multiple access points with an SSID.
659 *
660 * @param ssid The SSID to check.
661 * @return Whether the SSID has multiple access points.
662 */
663 private boolean hasRequiredNumberOfAps(String ssid) {
664 List<ScanResult> results = mWifiManager.getScanResults();
665 if (results == null) {
666 if (V) {
667 myLogV("hasRequiredNumberOfAps: Got null scan results, returning false");
668 }
669 return false;
670 }
671
672 int numApsRequired = getApCount();
673 int numApsFound = 0;
674 int resultsSize = results.size();
675 for (int i = 0; i < resultsSize; i++) {
676 ScanResult result = results.get(i);
677 if (result == null) continue;
678 if (result.SSID == null) continue;
679
680 if (result.SSID.equals(ssid)) {
681 numApsFound++;
682
683 if (numApsFound >= numApsRequired) {
684 if (V) {
685 myLogV("hasRequiredNumberOfAps: SSID: " + ssid + ", returning true");
686 }
687 return true;
688 }
689 }
690 }
691
692 if (V) {
693 myLogV("hasRequiredNumberOfAps: SSID: " + ssid + ", returning false");
694 }
695 return false;
696 }
697
698 // Watchdog logic (assume all of these methods will be in our main thread)
699
700 /**
701 * Handles a Wi-Fi network change (for example, from networkA to networkB).
702 */
703 private void handleNetworkChanged(String ssid) {
704 // Set the SSID being monitored to the new SSID
705 mSsid = ssid;
706 // Set various state to that when being idle
707 setIdleState(true);
708 }
709
710 /**
711 * Handles checking whether an AP is a "good" AP. If not, it will be blacklisted.
712 *
713 * @param ap The access point to check.
714 */
715 private void handleCheckAp(AccessPoint ap) {
716 // Reset the cancel state since this is the entry point of this action
717 mShouldCancel = false;
718
719 if (V) {
720 myLogV("handleCheckAp: AccessPoint: " + ap);
721 }
722
723 // Make sure we are not sleeping
724 if (mState == WatchdogState.SLEEP) {
725 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800726 Slog.v(TAG, " Sleeping (in " + mSsid + "), so returning");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800727 }
728 return;
729 }
730
731 mState = WatchdogState.CHECKING_AP;
732
733 /*
734 * Checks to make sure we haven't exceeded the max number of checks
735 * we're allowed per network
736 */
Isaac Levy188cecf2011-06-08 13:38:24 -0700737 incrementBssidCheckCount();
738 if (getBssidCheckCount() > getMaxApChecks()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800739 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800740 Slog.v(TAG, " Passed the max attempts (" + getMaxApChecks()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800741 + "), going to sleep for " + mSsid);
742 }
743 mHandler.sleep(mSsid);
744 return;
745 }
746
747 // Do the check
748 boolean isApAlive = checkDnsConnectivity();
749
750 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800751 Slog.v(TAG, " Is it alive: " + isApAlive);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800752 }
753
754 // Take action based on results
755 if (isApAlive) {
756 handleApAlive(ap);
757 } else {
758 handleApUnresponsive(ap);
759 }
760 }
761
762 /**
763 * Handles the case when an access point is alive.
764 *
765 * @param ap The access point.
766 */
767 private void handleApAlive(AccessPoint ap) {
768 // Check whether we are stale and should cancel
769 if (shouldCancel()) return;
770 // We're satisfied with this AP, so go idle
771 setIdleState(false);
772
773 if (D) {
774 myLogD("AP is alive: " + ap.toString());
775 }
776
777 // Queue the next action to be a background check
778 mHandler.backgroundCheckAp(ap);
779 }
780
781 /**
782 * Handles an unresponsive AP by blacklisting it.
783 *
784 * @param ap The access point.
785 */
786 private void handleApUnresponsive(AccessPoint ap) {
787 // Check whether we are stale and should cancel
788 if (shouldCancel()) return;
789 // This AP is "bad", switch to another
790 mState = WatchdogState.SWITCHING_AP;
791
792 if (D) {
793 myLogD("AP is dead: " + ap.toString());
794 }
795
796 // Black list this "bad" AP, this will cause an attempt to connect to another
797 blacklistAp(ap.bssid);
Irfan Sheriff0049a1b2010-01-14 12:37:49 -0800798 // Initiate an association to an alternate AP
Irfan Sheriff0d255342010-07-28 09:35:20 -0700799 mWifiManager.reassociate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800800 }
801
802 private void blacklistAp(String bssid) {
803 if (TextUtils.isEmpty(bssid)) {
804 return;
805 }
806
807 // Before taking action, make sure we should not cancel our processing
808 if (shouldCancel()) return;
809
Irfan Sheriff0d255342010-07-28 09:35:20 -0700810 mWifiManager.addToBlacklist(bssid);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800811
812 if (D) {
813 myLogD("Blacklisting " + bssid);
814 }
815 }
816
817 /**
818 * Handles a single background check. If it fails, it should trigger a
819 * normal check. If it succeeds, it should queue another background check.
820 *
821 * @param ap The access point to do a background check for. If this is no
822 * longer the current AP, it is okay to return without any
823 * processing.
824 */
825 private void handleBackgroundCheckAp(AccessPoint ap) {
826 // Reset the cancel state since this is the entry point of this action
827 mShouldCancel = false;
828
Isaac Levy188cecf2011-06-08 13:38:24 -0700829 if (V) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800830 myLogV("handleBackgroundCheckAp: AccessPoint: " + ap);
831 }
832
833 // Make sure we are not sleeping
834 if (mState == WatchdogState.SLEEP) {
835 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800836 Slog.v(TAG, " handleBackgroundCheckAp: Sleeping (in " + mSsid + "), so returning");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800837 }
838 return;
839 }
840
841 // Make sure the AP we're supposed to be background checking is still the active one
842 WifiInfo info = mWifiManager.getConnectionInfo();
843 if (info.getSSID() == null || !info.getSSID().equals(ap.ssid)) {
844 if (V) {
845 myLogV("handleBackgroundCheckAp: We are no longer connected to "
846 + ap + ", and instead are on " + info);
847 }
848 return;
849 }
850
851 if (info.getBSSID() == null || !info.getBSSID().equals(ap.bssid)) {
852 if (V) {
853 myLogV("handleBackgroundCheckAp: We are no longer connected to "
854 + ap + ", and instead are on " + info);
855 }
856 return;
857 }
858
859 // Do the check
860 boolean isApAlive = backgroundCheckDnsConnectivity();
861
862 if (V && !isApAlive) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800863 Slog.v(TAG, " handleBackgroundCheckAp: Is it alive: " + isApAlive);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800864 }
865
866 if (shouldCancel()) {
867 return;
868 }
869
870 // Take action based on results
871 if (isApAlive) {
872 // Queue another background check
873 mHandler.backgroundCheckAp(ap);
874
875 } else {
876 if (D) {
877 myLogD("Background check failed for " + ap.toString());
878 }
879
880 // Queue a normal check, so it can take proper action
881 mHandler.checkAp(ap);
882 }
883 }
884
885 /**
886 * Handles going to sleep for this network. Going to sleep means we will not
887 * monitor this network anymore.
888 *
889 * @param ssid The network that will not be monitored anymore.
890 */
891 private void handleSleep(String ssid) {
892 // Make sure the network we're trying to sleep in is still the current network
893 if (ssid != null && ssid.equals(mSsid)) {
894 mState = WatchdogState.SLEEP;
895
896 if (D) {
897 myLogD("Going to sleep for " + ssid);
898 }
899
900 /*
901 * Before deciding to go to sleep, we may have checked a few APs
902 * (and blacklisted them). Clear the blacklist so the AP with best
903 * signal is chosen.
904 */
Irfan Sheriff0d255342010-07-28 09:35:20 -0700905 mWifiManager.clearBlacklist();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800906
907 if (V) {
908 myLogV("handleSleep: Set state to SLEEP and cleared blacklist");
909 }
910 }
911 }
912
913 /**
914 * Handles an access point disconnection.
915 */
916 private void handleDisconnected() {
917 /*
918 * We purposefully do not change mSsid to null. This is to handle
919 * disconnected followed by connected better (even if there is some
920 * duration in between). For example, if the watchdog went to sleep in a
921 * network, and then the phone goes to sleep, when the phone wakes up we
922 * still want to be in the sleeping state. When the phone went to sleep,
923 * we would have gotten a disconnected event which would then set mSsid
924 * = null. This is bad, since the following connect would cause us to do
925 * the "network is good?" check all over again. */
926
927 /*
928 * Set the state as if we were idle (don't come out of sleep, only
929 * hard reset and network changed should do that.
930 */
931 setIdleState(false);
932 }
933
934 /**
935 * Handles going idle. Idle means we are satisfied with the current state of
936 * things, but if a new connection occurs we'll re-evaluate.
937 */
938 private void handleIdle() {
939 // Reset the cancel state since this is the entry point for this action
940 mShouldCancel = false;
941
942 if (V) {
943 myLogV("handleSwitchToIdle");
944 }
945
946 // If we're sleeping, don't do anything
947 if (mState == WatchdogState.SLEEP) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800948 Slog.v(TAG, " Sleeping (in " + mSsid + "), so returning");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800949 return;
950 }
951
952 // Set the idle state
953 setIdleState(false);
954
955 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800956 Slog.v(TAG, " Set state to IDLE");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800957 }
958 }
959
960 /**
961 * Sets the state as if we are going idle.
962 */
963 private void setIdleState(boolean forceIdleState) {
964 // Setting idle state does not kick us out of sleep unless the forceIdleState is set
965 if (forceIdleState || (mState != WatchdogState.SLEEP)) {
966 mState = WatchdogState.IDLE;
967 }
Isaac Levy188cecf2011-06-08 13:38:24 -0700968 resetBssidCheckCount();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800969 }
970
971 /**
972 * Handles a hard reset. A hard reset is rarely used, but when used it
973 * should revert anything done by the watchdog monitoring.
974 */
975 private void handleReset() {
Irfan Sheriff0d255342010-07-28 09:35:20 -0700976 mWifiManager.clearBlacklist();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800977 setIdleState(true);
978 }
979
980 // Inner classes
981
982 /**
983 * Possible states for the watchdog to be in.
984 */
985 private static enum WatchdogState {
986 /** The watchdog is currently idle, but it is still responsive to future AP checks in this network. */
987 IDLE,
988 /** The watchdog is sleeping, so it will not try any AP checks for the network. */
989 SLEEP,
990 /** The watchdog is currently checking an AP for connectivity. */
991 CHECKING_AP,
992 /** The watchdog is switching to another AP in the network. */
993 SWITCHING_AP
994 }
995
Isaac Levy188cecf2011-06-08 13:38:24 -0700996 private int getBssidCheckCount() {
997 return mBssidCheckCount;
998 }
999
1000 private void incrementBssidCheckCount() {
1001 mBssidCheckCount++;
1002 }
1003
1004 private void resetBssidCheckCount() {
1005 this.mBssidCheckCount = 0;
1006 }
1007
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001008 /**
1009 * The main thread for the watchdog monitoring. This will be turned into a
1010 * {@link Looper} thread.
1011 */
1012 private class WifiWatchdogThread extends Thread {
1013 WifiWatchdogThread() {
1014 super("WifiWatchdogThread");
1015 }
1016
1017 @Override
1018 public void run() {
1019 // Set this thread up so the handler will work on it
1020 Looper.prepare();
1021
1022 synchronized(WifiWatchdogService.this) {
1023 mHandler = new WifiWatchdogHandler();
1024
1025 // Notify that the handler has been created
1026 WifiWatchdogService.this.notify();
1027 }
1028
1029 // Listen for messages to the handler
1030 Looper.loop();
1031 }
1032 }
1033
1034 /**
1035 * The main thread's handler. There are 'actions', and just general
1036 * 'messages'. There should only ever be one 'action' in the queue (aside
1037 * from the one being processed, if any). There may be multiple messages in
1038 * the queue. So, actions are replaced by more recent actions, where as
1039 * messages will be executed for sure. Messages end up being used to just
1040 * change some state, and not really take any action.
1041 * <p>
1042 * There is little logic inside this class, instead methods of the form
1043 * "handle___" are called in the main {@link WifiWatchdogService}.
1044 */
1045 private class WifiWatchdogHandler extends Handler {
1046 /** Check whether the AP is "good". The object will be an {@link AccessPoint}. */
1047 static final int ACTION_CHECK_AP = 1;
1048 /** Go into the idle state. */
1049 static final int ACTION_IDLE = 2;
1050 /**
1051 * Performs a periodic background check whether the AP is still "good".
1052 * The object will be an {@link AccessPoint}.
1053 */
1054 static final int ACTION_BACKGROUND_CHECK_AP = 3;
Irfan Sheriff2f5f4bc2011-05-02 11:43:28 -07001055 /** Check whether the connection is a walled garden */
1056 static final int ACTION_CHECK_WALLED_GARDEN = 4;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001057
1058 /**
1059 * Go to sleep for the current network. We are conservative with making
1060 * this a message rather than action. We want to make sure our main
1061 * thread sees this message, but if it were an action it could be
1062 * removed from the queue and replaced by another action. The main
1063 * thread will ensure when it sees the message that the state is still
1064 * valid for going to sleep.
1065 * <p>
1066 * For an explanation of sleep, see {@link android.provider.Settings.Secure#WIFI_WATCHDOG_MAX_AP_CHECKS}.
1067 */
1068 static final int MESSAGE_SLEEP = 101;
1069 /** Disables the watchdog. */
1070 static final int MESSAGE_DISABLE_WATCHDOG = 102;
1071 /** The network has changed. */
1072 static final int MESSAGE_NETWORK_CHANGED = 103;
1073 /** The current access point has disconnected. */
1074 static final int MESSAGE_DISCONNECTED = 104;
1075 /** Performs a hard-reset on the watchdog state. */
1076 static final int MESSAGE_RESET = 105;
Irfan Sheriff2f5f4bc2011-05-02 11:43:28 -07001077
1078 /* Walled garden detection */
1079 private String mLastSsid;
1080 private long mLastTime;
1081 private final long MIN_WALLED_GARDEN_TEST_INTERVAL = 15 * 60 * 1000; //15 minutes
1082
1083 void checkWalledGarden(String ssid) {
1084 sendMessage(obtainMessage(ACTION_CHECK_WALLED_GARDEN, ssid));
1085 }
1086
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001087 void checkAp(AccessPoint ap) {
1088 removeAllActions();
1089 sendMessage(obtainMessage(ACTION_CHECK_AP, ap));
1090 }
1091
1092 void backgroundCheckAp(AccessPoint ap) {
1093 if (!isBackgroundCheckEnabled()) return;
1094
1095 removeAllActions();
1096 sendMessageDelayed(obtainMessage(ACTION_BACKGROUND_CHECK_AP, ap),
1097 getBackgroundCheckDelayMs());
1098 }
1099
1100 void idle() {
1101 removeAllActions();
1102 sendMessage(obtainMessage(ACTION_IDLE));
1103 }
1104
1105 void sleep(String ssid) {
1106 removeAllActions();
1107 sendMessage(obtainMessage(MESSAGE_SLEEP, ssid));
1108 }
1109
1110 void disableWatchdog() {
1111 removeAllActions();
1112 sendMessage(obtainMessage(MESSAGE_DISABLE_WATCHDOG));
1113 }
1114
1115 void dispatchNetworkChanged(String ssid) {
1116 removeAllActions();
1117 sendMessage(obtainMessage(MESSAGE_NETWORK_CHANGED, ssid));
1118 }
1119
1120 void dispatchDisconnected() {
1121 removeAllActions();
1122 sendMessage(obtainMessage(MESSAGE_DISCONNECTED));
1123 }
1124
1125 void reset() {
1126 removeAllActions();
1127 sendMessage(obtainMessage(MESSAGE_RESET));
1128 }
1129
1130 private void removeAllActions() {
1131 removeMessages(ACTION_CHECK_AP);
1132 removeMessages(ACTION_IDLE);
1133 removeMessages(ACTION_BACKGROUND_CHECK_AP);
1134 }
1135
1136 @Override
1137 public void handleMessage(Message msg) {
Isaac Levy188cecf2011-06-08 13:38:24 -07001138 if (V) {
1139 myLogV("handleMessage: " + msg.what);
1140 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001141 switch (msg.what) {
1142 case MESSAGE_NETWORK_CHANGED:
1143 handleNetworkChanged((String) msg.obj);
1144 break;
1145 case ACTION_CHECK_AP:
1146 handleCheckAp((AccessPoint) msg.obj);
1147 break;
1148 case ACTION_BACKGROUND_CHECK_AP:
1149 handleBackgroundCheckAp((AccessPoint) msg.obj);
1150 break;
Irfan Sheriff2f5f4bc2011-05-02 11:43:28 -07001151 case ACTION_CHECK_WALLED_GARDEN:
1152 handleWalledGardenCheck((String) msg.obj);
1153 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001154 case MESSAGE_SLEEP:
1155 handleSleep((String) msg.obj);
1156 break;
1157 case ACTION_IDLE:
1158 handleIdle();
1159 break;
1160 case MESSAGE_DISABLE_WATCHDOG:
1161 handleIdle();
1162 break;
1163 case MESSAGE_DISCONNECTED:
1164 handleDisconnected();
1165 break;
1166 case MESSAGE_RESET:
1167 handleReset();
1168 break;
1169 }
1170 }
Irfan Sheriff2f5f4bc2011-05-02 11:43:28 -07001171
Irfan Sheriff5ea65d62011-06-05 21:29:56 -07001172 /**
1173 * DNS based detection techniques do not work at all hotspots. The one sure way to check
1174 * a walled garden is to see if a URL fetch on a known address fetches the data we
1175 * expect
1176 */
Irfan Sheriff2f5f4bc2011-05-02 11:43:28 -07001177 private boolean isWalledGardenConnection() {
Irfan Sheriff5ea65d62011-06-05 21:29:56 -07001178 InputStream in = null;
1179 HttpURLConnection urlConnection = null;
Irfan Sheriff2f5f4bc2011-05-02 11:43:28 -07001180 try {
Irfan Sheriff5ea65d62011-06-05 21:29:56 -07001181 URL url = new URL(getWalledGardenUrl());
1182 urlConnection = (HttpURLConnection) url.openConnection();
1183 in = new BufferedInputStream(urlConnection.getInputStream());
1184 Scanner scanner = new Scanner(in);
1185 if (scanner.findInLine(getWalledGardenPattern()) != null) {
1186 return false;
1187 } else {
1188 return true;
1189 }
1190 } catch (IOException e) {
Irfan Sheriff2f5f4bc2011-05-02 11:43:28 -07001191 return false;
Irfan Sheriff5ea65d62011-06-05 21:29:56 -07001192 } finally {
1193 if (in != null) {
1194 try {
1195 in.close();
1196 } catch (IOException e) {
1197 }
1198 }
1199 if (urlConnection != null) urlConnection.disconnect();
Irfan Sheriff2f5f4bc2011-05-02 11:43:28 -07001200 }
Irfan Sheriff2f5f4bc2011-05-02 11:43:28 -07001201 }
1202
1203 private void handleWalledGardenCheck(String ssid) {
1204 long currentTime = System.currentTimeMillis();
1205 //Avoid a walled garden test on the same network if one was already done
1206 //within MIN_WALLED_GARDEN_TEST_INTERVAL. This will handle scenarios where
1207 //there are frequent network disconnections
1208 if (ssid.equals(mLastSsid) &&
1209 (currentTime - mLastTime) < MIN_WALLED_GARDEN_TEST_INTERVAL) {
1210 return;
1211 }
1212
1213 mLastTime = currentTime;
1214 mLastSsid = ssid;
1215
1216 if (isWalledGardenConnection()) {
1217 Uri uri = Uri.parse("http://www.google.com");
1218 Intent intent = new Intent(Intent.ACTION_VIEW, uri);
1219 intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
1220 Intent.FLAG_ACTIVITY_NEW_TASK);
1221 mContext.startActivity(intent);
1222 }
1223 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001224 }
1225
1226 /**
1227 * Receives Wi-Fi broadcasts.
1228 * <p>
1229 * There is little logic in this class, instead methods of the form "on___"
1230 * are called in the {@link WifiWatchdogService}.
1231 */
1232 private BroadcastReceiver mReceiver = new BroadcastReceiver() {
1233
1234 @Override
1235 public void onReceive(Context context, Intent intent) {
1236 final String action = intent.getAction();
1237 if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
1238 handleNetworkStateChanged(
1239 (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001240 } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
1241 handleWifiStateChanged(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
1242 WifiManager.WIFI_STATE_UNKNOWN));
1243 }
1244 }
1245
1246 private void handleNetworkStateChanged(NetworkInfo info) {
1247 if (V) {
1248 myLogV("Receiver.handleNetworkStateChanged: NetworkInfo: "
1249 + info);
1250 }
1251
1252 switch (info.getState()) {
1253 case CONNECTED:
1254 WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
1255 if (wifiInfo.getSSID() == null || wifiInfo.getBSSID() == null) {
1256 if (V) {
1257 myLogV("handleNetworkStateChanged: Got connected event but SSID or BSSID are null. SSID: "
1258 + wifiInfo.getSSID()
1259 + ", BSSID: "
1260 + wifiInfo.getBSSID() + ", ignoring event");
1261 }
1262 return;
1263 }
1264 onConnected(wifiInfo.getSSID(), wifiInfo.getBSSID());
1265 break;
1266
1267 case DISCONNECTED:
1268 onDisconnected();
1269 break;
1270 }
1271 }
1272
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001273 private void handleWifiStateChanged(int wifiState) {
1274 if (wifiState == WifiManager.WIFI_STATE_DISABLED) {
Irfan Sheriffa2a1b912010-06-07 09:03:04 -07001275 onDisconnected();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001276 } else if (wifiState == WifiManager.WIFI_STATE_ENABLED) {
1277 onEnabled();
1278 }
1279 }
1280 };
1281
1282 /**
1283 * Describes an access point by its SSID and BSSID.
Isaac Levy188cecf2011-06-08 13:38:24 -07001284 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001285 */
1286 private static class AccessPoint {
1287 String ssid;
1288 String bssid;
1289
Isaac Levy188cecf2011-06-08 13:38:24 -07001290 /**
1291 * @param ssid cannot be null
1292 * @param bssid cannot be null
1293 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001294 AccessPoint(String ssid, String bssid) {
Isaac Levy188cecf2011-06-08 13:38:24 -07001295 if (ssid == null || bssid == null) {
1296 Slog.e(TAG, String.format("(%s) INVALID ACCESSPOINT: (%s, %s)",
1297 Thread.currentThread().getName(),ssid,bssid));
1298 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001299 this.ssid = ssid;
1300 this.bssid = bssid;
1301 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001302
1303 @Override
1304 public boolean equals(Object o) {
1305 if (!(o instanceof AccessPoint)) return false;
1306 AccessPoint otherAp = (AccessPoint) o;
Isaac Levy188cecf2011-06-08 13:38:24 -07001307
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001308 // Either we both have a null, or our SSIDs and BSSIDs are equal
Isaac Levy188cecf2011-06-08 13:38:24 -07001309 return ssid.equals(otherAp.ssid) && bssid.equals(otherAp.bssid);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001310 }
1311
1312 @Override
1313 public int hashCode() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001314 return ssid.hashCode() + bssid.hashCode();
1315 }
1316
1317 @Override
1318 public String toString() {
1319 return ssid + " (" + bssid + ")";
1320 }
1321 }
1322
1323 /**
1324 * Performs a simple DNS "ping" by sending a "server status" query packet to
1325 * the DNS server. As long as the server replies, we consider it a success.
1326 * <p>
1327 * We do not use a simple hostname lookup because that could be cached and
1328 * the API may not differentiate between a time out and a failure lookup
1329 * (which we really care about).
1330 */
1331 private static class DnsPinger {
1332
1333 /** Number of bytes for the query */
1334 private static final int DNS_QUERY_BASE_SIZE = 33;
1335
1336 /** The DNS port */
1337 private static final int DNS_PORT = 53;
1338
1339 /** Used to generate IDs */
1340 private static Random sRandom = new Random();
Robert Greenwalt8e9abc52011-02-16 10:37:50 -08001341
1342 static boolean isDnsReachable(InetAddress dnsAddress, int timeout) {
Nick Kralevich929b4852010-06-09 14:27:43 -07001343 DatagramSocket socket = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001344 try {
Nick Kralevich929b4852010-06-09 14:27:43 -07001345 socket = new DatagramSocket();
Robert Greenwalt8e9abc52011-02-16 10:37:50 -08001346
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001347 // Set some socket properties
1348 socket.setSoTimeout(timeout);
Robert Greenwalt8e9abc52011-02-16 10:37:50 -08001349
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001350 byte[] buf = new byte[DNS_QUERY_BASE_SIZE];
1351 fillQuery(buf);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001352
Robert Greenwalt8e9abc52011-02-16 10:37:50 -08001353 // Send the DNS query
1354
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001355 DatagramPacket packet = new DatagramPacket(buf,
1356 buf.length, dnsAddress, DNS_PORT);
1357 socket.send(packet);
Robert Greenwalt8e9abc52011-02-16 10:37:50 -08001358
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001359 // Wait for reply (blocks for the above timeout)
1360 DatagramPacket replyPacket = new DatagramPacket(buf, buf.length);
1361 socket.receive(replyPacket);
1362
1363 // If a timeout occurred, an exception would have been thrown. We got a reply!
1364 return true;
Robert Greenwalt8e9abc52011-02-16 10:37:50 -08001365
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001366 } catch (SocketException e) {
1367 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001368 Slog.v(TAG, "DnsPinger.isReachable received SocketException", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001369 }
1370 return false;
Robert Greenwalt8e9abc52011-02-16 10:37:50 -08001371
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001372 } catch (UnknownHostException e) {
1373 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001374 Slog.v(TAG, "DnsPinger.isReachable is unable to resolve the DNS host", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001375 }
1376 return false;
1377
1378 } catch (SocketTimeoutException e) {
1379 return false;
1380
1381 } catch (IOException e) {
1382 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001383 Slog.v(TAG, "DnsPinger.isReachable got an IOException", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001384 }
1385 return false;
1386
1387 } catch (Exception e) {
Isaac Levy188cecf2011-06-08 13:38:24 -07001388 if (V) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001389 Slog.d(TAG, "DnsPinger.isReachable got an unknown exception", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001390 }
1391 return false;
Nick Kralevich929b4852010-06-09 14:27:43 -07001392 } finally {
1393 if (socket != null) {
1394 socket.close();
1395 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001396 }
1397 }
1398
1399 private static void fillQuery(byte[] buf) {
1400
1401 /*
1402 * See RFC2929 (though the bit tables in there are misleading for
1403 * us. For example, the recursion desired bit is the 0th bit for us,
1404 * but looking there it would appear as the 7th bit of the byte
1405 */
1406
1407 // Make sure it's all zeroed out
1408 for (int i = 0; i < buf.length; i++) buf[i] = 0;
1409
1410 // Form a query for www.android.com
1411
1412 // [0-1] bytes are an ID, generate random ID for this query
1413 buf[0] = (byte) sRandom.nextInt(256);
1414 buf[1] = (byte) sRandom.nextInt(256);
1415
1416 // [2-3] bytes are for flags.
1417 buf[2] = 1; // Recursion desired
1418
1419 // [4-5] bytes are for the query count
1420 buf[5] = 1; // One query
1421
1422 // [6-7] [8-9] [10-11] are all counts of other fields we don't use
1423
1424 // [12-15] for www
1425 writeString(buf, 12, "www");
1426
1427 // [16-23] for android
1428 writeString(buf, 16, "android");
1429
1430 // [24-27] for com
1431 writeString(buf, 24, "com");
1432
1433 // [29-30] bytes are for QTYPE, set to 1
1434 buf[30] = 1;
1435
1436 // [31-32] bytes are for QCLASS, set to 1
1437 buf[32] = 1;
1438 }
1439
1440 private static void writeString(byte[] buf, int startPos, String string) {
1441 int pos = startPos;
1442
1443 // Write the length first
1444 buf[pos++] = (byte) string.length();
1445 for (int i = 0; i < string.length(); i++) {
1446 buf[pos++] = (byte) string.charAt(i);
1447 }
1448 }
1449 }
1450}