blob: 3ba9c14cf4d780592137cbb402b9ba4f5861f11a [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;
Isaac Levyb1ef2922011-06-27 10:02:50 -070025import android.net.ConnectivityManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.net.NetworkInfo;
Isaac Levybc7dfb52011-06-06 15:34:01 -070027import android.net.Uri;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.net.wifi.ScanResult;
Isaac Levybc7dfb52011-06-06 15:34:01 -070029import android.net.wifi.SupplicantState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.net.wifi.WifiInfo;
31import android.net.wifi.WifiManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import android.os.Handler;
Isaac Levybc7dfb52011-06-06 15:34:01 -070033import android.os.HandlerThread;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034import android.os.Looper;
35import android.os.Message;
Isaac Levybc7dfb52011-06-06 15:34:01 -070036import android.os.SystemClock;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037import android.provider.Settings;
38import android.text.TextUtils;
Joe Onorato8a9b2202010-02-26 18:56:32 -080039import android.util.Slog;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040
Irfan Sheriff5ea65d62011-06-05 21:29:56 -070041import java.io.BufferedInputStream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042import java.io.IOException;
Isaac Levybc7dfb52011-06-06 15:34:01 -070043import java.io.InputStream;
44import java.io.PrintWriter;
Irfan Sheriff5ea65d62011-06-05 21:29:56 -070045import java.net.HttpURLConnection;
Irfan Sheriff5ea65d62011-06-05 21:29:56 -070046import java.net.URL;
Isaac Levybc7dfb52011-06-06 15:34:01 -070047import java.util.HashSet;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048import java.util.List;
Irfan Sheriff5ea65d62011-06-05 21:29:56 -070049import java.util.Scanner;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050
51/**
52 * {@link WifiWatchdogService} monitors the initial connection to a Wi-Fi
53 * network with multiple access points. After the framework successfully
Isaac Levybc7dfb52011-06-06 15:34:01 -070054 * connects to an access point, the watchdog verifies connectivity by 'pinging'
55 * the configured DNS server using {@link DnsPinger}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056 * <p>
Isaac Levybc7dfb52011-06-06 15:34:01 -070057 * On DNS check failure, the BSSID is blacklisted if it is reasonably likely
58 * that another AP might have internet access; otherwise the SSID is disabled.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080059 * <p>
Isaac Levybc7dfb52011-06-06 15:34:01 -070060 * On DNS success, the WatchdogService initiates a walled garden check via an
61 * http get. A browser windows is activated if a walled garden is detected.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062 *
Isaac Levybc7dfb52011-06-06 15:34:01 -070063 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080064 */
65public class WifiWatchdogService {
Isaac Levybc7dfb52011-06-06 15:34:01 -070066
67 private static final String WWS_TAG = "WifiWatchdogService";
68
69 private static final boolean VDBG = true;
70 private static final boolean DBG = true;
71
72 // Used for verbose logging
73 private String mDNSCheckLogStr;
Isaac Levy188cecf2011-06-08 13:38:24 -070074
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080075 private Context mContext;
76 private ContentResolver mContentResolver;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080077 private WifiManager mWifiManager;
Robert Greenwalt8e9abc52011-02-16 10:37:50 -080078
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080079 private WifiWatchdogHandler mHandler;
80
Isaac Levybc7dfb52011-06-06 15:34:01 -070081 private DnsPinger mDnsPinger;
82
83 private IntentFilter mIntentFilter;
84 private BroadcastReceiver mBroadcastReceiver;
85 private boolean mBroadcastsEnabled;
86
87 private static final int WIFI_SIGNAL_LEVELS = 4;
Irfan Sheriff7b009782010-03-11 16:37:45 -080088
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080089 /**
Isaac Levybc7dfb52011-06-06 15:34:01 -070090 * Low signal is defined as less than or equal to cut off
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080091 */
Isaac Levybc7dfb52011-06-06 15:34:01 -070092 private static final int LOW_SIGNAL_CUTOFF = 0;
93
94 private static final long MIN_LOW_SIGNAL_CHECK_INTERVAL = 2 * 60 * 1000;
95 private static final long MIN_SINGLE_DNS_CHECK_INTERVAL = 10 * 60 * 1000;
96 private static final long MIN_WALLED_GARDEN_INTERVAL = 15 * 60 * 1000;
97
98 private static final int MAX_CHECKS_PER_SSID = 7;
99 private static final int NUM_DNS_PINGS = 5;
100 private static double MIN_RESPONSE_RATE = 0.50;
101
102 // TODO : Adjust multiple DNS downward to 250 on repeated failure
103 // private static final int MULTI_DNS_PING_TIMEOUT_MS = 250;
104
105 private static final int DNS_PING_TIMEOUT_MS = 1000;
106 private static final long DNS_PING_INTERVAL = 250;
107
108 private static final long BLACKLIST_FOLLOWUP_INTERVAL = 15 * 1000;
109
110 private Status mStatus = new Status();
111
112 private static class Status {
113 String bssid = "";
114 String ssid = "";
115
116 HashSet<String> allBssids = new HashSet<String>();
117 int numFullDNSchecks = 0;
118
119 long lastSingleCheckTime = -24 * 60 * 60 * 1000;
120 long lastWalledGardenCheckTime = -24 * 60 * 60 * 1000;
121
122 WatchdogState state = WatchdogState.INACTIVE;
123
124 // Info for dns check
125 int dnsCheckTries = 0;
126 int dnsCheckSuccesses = 0;
127
128 public int signal = -200;
129
130 }
131
132 private enum WatchdogState {
133 /**
134 * Full DNS check in progress
135 */
136 DNS_FULL_CHECK,
137
138 /**
139 * Walled Garden detected, will pop up browser next round.
140 */
141 WALLED_GARDEN_DETECTED,
142
143 /**
144 * DNS failed, will blacklist/disable AP next round
145 */
146 DNS_CHECK_FAILURE,
147
148 /**
149 * Online or displaying walled garden auth page
150 */
151 CHECKS_COMPLETE,
152
153 /**
154 * Watchdog idle, network has been blacklisted or received disconnect
155 * msg
156 */
157 INACTIVE,
158
159 BLACKLISTED_AP
160 }
Isaac Levy188cecf2011-06-08 13:38:24 -0700161
Irfan Sheriff0d255342010-07-28 09:35:20 -0700162 WifiWatchdogService(Context context) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800163 mContext = context;
164 mContentResolver = context.getContentResolver();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800165 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
Isaac Levyb1ef2922011-06-27 10:02:50 -0700166 mDnsPinger = new DnsPinger("WifiWatchdogServer.DnsPinger", context,
167 ConnectivityManager.TYPE_WIFI);
Isaac Levybc7dfb52011-06-06 15:34:01 -0700168
169 HandlerThread handlerThread = new HandlerThread("WifiWatchdogServiceThread");
170 handlerThread.start();
171 mHandler = new WifiWatchdogHandler(handlerThread.getLooper());
172
173 setupNetworkReceiver();
174
175 // The content observer to listen needs a handler, which createThread
176 // creates
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800177 registerForSettingsChanges();
Isaac Levybc7dfb52011-06-06 15:34:01 -0700178
179 // Start things off
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800180 if (isWatchdogEnabled()) {
Isaac Levybc7dfb52011-06-06 15:34:01 -0700181 mHandler.sendEmptyMessage(WifiWatchdogHandler.MESSAGE_CONTEXT_EVENT);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800182 }
Isaac Levybc7dfb52011-06-06 15:34:01 -0700183 }
184
185 /**
186 *
187 */
188 private void setupNetworkReceiver() {
189 mBroadcastReceiver = new BroadcastReceiver() {
190 @Override
191 public void onReceive(Context context, Intent intent) {
192 String action = intent.getAction();
193 if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
194 mHandler.sendMessage(mHandler.obtainMessage(
195 WifiWatchdogHandler.MESSAGE_NETWORK_EVENT,
196 intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO)
197 ));
198 } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
199 mHandler.sendEmptyMessage(WifiWatchdogHandler.RSSI_CHANGE_EVENT);
200 } else if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
201 mHandler.sendEmptyMessage(WifiWatchdogHandler.SCAN_RESULTS_AVAILABLE);
202 } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
203 mHandler.sendMessage(mHandler.obtainMessage(
204 WifiWatchdogHandler.WIFI_STATE_CHANGE,
205 intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 4)));
206 }
207 }
208 };
209
210 mIntentFilter = new IntentFilter();
211 mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
212 mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
213 mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
214 mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800215 }
216
217 /**
218 * Observes the watchdog on/off setting, and takes action when changed.
219 */
220 private void registerForSettingsChanges() {
Isaac Levybc7dfb52011-06-06 15:34:01 -0700221 ContentObserver contentObserver = new ContentObserver(mHandler) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800222 @Override
223 public void onChange(boolean selfChange) {
Isaac Levybc7dfb52011-06-06 15:34:01 -0700224 mHandler.sendEmptyMessage((WifiWatchdogHandler.MESSAGE_CONTEXT_EVENT));
225 }
226 };
227
228 mContext.getContentResolver().registerContentObserver(
229 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON),
230 false, contentObserver);
231 }
232
233 private void handleNewConnection() {
234 WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
235 String newSsid = wifiInfo.getSSID();
236 String newBssid = wifiInfo.getBSSID();
237
238 if (VDBG) {
239 Slog.v(WWS_TAG, String.format("handleConnected:: old (%s, %s) ==> new (%s, %s)",
240 mStatus.ssid, mStatus.bssid, newSsid, newBssid));
241 }
242
243 if (TextUtils.isEmpty(newSsid) || TextUtils.isEmpty(newBssid)) {
244 return;
245 }
246
247 if (!TextUtils.equals(mStatus.ssid, newSsid)) {
248 mStatus = new Status();
249 mStatus.ssid = newSsid;
250 }
251
252 mStatus.bssid = newBssid;
253 mStatus.allBssids.add(newBssid);
254 mStatus.signal = WifiManager.calculateSignalLevel(wifiInfo.getRssi(), WIFI_SIGNAL_LEVELS);
255
256 initDnsFullCheck();
257 }
258
259 public void updateRssi() {
260 WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
261 if (!TextUtils.equals(mStatus.ssid, wifiInfo.getSSID()) ||
262 !TextUtils.equals(mStatus.bssid, wifiInfo.getBSSID())) {
263 return;
264 }
265
266 mStatus.signal = WifiManager.calculateSignalLevel(wifiInfo.getRssi(), WIFI_SIGNAL_LEVELS);
267 }
268
269 /**
270 * Single step in state machine
271 */
272 private void handleStateStep() {
273 // Slog.v(WWS_TAG, "handleStateStep:: " + mStatus.state);
274
275 switch (mStatus.state) {
276 case DNS_FULL_CHECK:
277 if (VDBG) {
278 Slog.v(WWS_TAG, "DNS_FULL_CHECK: " + mDNSCheckLogStr);
279 }
280
281 long pingResponseTime = mDnsPinger.pingDns(mDnsPinger.getDns(),
282 DNS_PING_TIMEOUT_MS);
283
284 mStatus.dnsCheckTries++;
285 if (pingResponseTime >= 0)
286 mStatus.dnsCheckSuccesses++;
287
288 if (DBG) {
289 if (pingResponseTime >= 0) {
290 mDNSCheckLogStr += " | " + pingResponseTime;
291 } else {
292 mDNSCheckLogStr += " | " + "x";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800293 }
294 }
Isaac Levybc7dfb52011-06-06 15:34:01 -0700295
296 switch (currentDnsCheckStatus()) {
297 case SUCCESS:
298 if (DBG) {
299 Slog.d(WWS_TAG, mDNSCheckLogStr + " -- Success");
300 }
301 doWalledGardenCheck();
302 break;
303 case FAILURE:
304 if (DBG) {
305 Slog.d(WWS_TAG, mDNSCheckLogStr + " -- Failure");
306 }
307 mStatus.state = WatchdogState.DNS_CHECK_FAILURE;
308 break;
309 case INCOMPLETE:
310 // Taking no action
311 break;
312 }
313 break;
314 case DNS_CHECK_FAILURE:
315 WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
316 if (!mStatus.ssid.equals(wifiInfo.getSSID()) ||
317 !mStatus.bssid.equals(wifiInfo.getBSSID())) {
318 Slog.i(WWS_TAG, "handleState DNS_CHECK_FAILURE:: network has changed!");
319 mStatus.state = WatchdogState.INACTIVE;
320 break;
321 }
322
323 if (mStatus.numFullDNSchecks >= mStatus.allBssids.size() ||
324 mStatus.numFullDNSchecks >= MAX_CHECKS_PER_SSID) {
325 disableAP(wifiInfo);
326 } else {
327 blacklistAP();
328 }
329 break;
330 case WALLED_GARDEN_DETECTED:
331 popUpBrowser();
332 mStatus.state = WatchdogState.CHECKS_COMPLETE;
333 break;
334 case BLACKLISTED_AP:
335 WifiInfo wifiInfo2 = mWifiManager.getConnectionInfo();
336 if (wifiInfo2.getSupplicantState() != SupplicantState.COMPLETED) {
337 Slog.d(WWS_TAG,
338 "handleState::BlacklistedAP - offline, but didn't get disconnect!");
339 mStatus.state = WatchdogState.INACTIVE;
340 break;
341 }
342 if (mStatus.bssid.equals(wifiInfo2.getBSSID())) {
343 Slog.d(WWS_TAG, "handleState::BlacklistedAP - connected to same bssid");
344 if (!handleSingleDnsCheck()) {
345 disableAP(wifiInfo2);
346 break;
347 }
348 }
349
350 Slog.d(WWS_TAG, "handleState::BlacklistedAP - Simiulating a new connection");
351 handleNewConnection();
352 break;
353 }
354 }
355
356 private void doWalledGardenCheck() {
357 if (!isWalledGardenTestEnabled()) {
358 if (VDBG)
359 Slog.v(WWS_TAG, "Skipping walled garden check - disabled");
360 mStatus.state = WatchdogState.CHECKS_COMPLETE;
361 return;
362 }
363 long waitTime = waitTime(MIN_WALLED_GARDEN_INTERVAL,
364 mStatus.lastWalledGardenCheckTime);
365 if (waitTime > 0) {
366 if (VDBG) {
367 Slog.v(WWS_TAG, "Skipping walled garden check - wait " +
368 waitTime + " ms.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800369 }
Isaac Levybc7dfb52011-06-06 15:34:01 -0700370 mStatus.state = WatchdogState.CHECKS_COMPLETE;
371 return;
372 }
373
374 mStatus.lastWalledGardenCheckTime = SystemClock.elapsedRealtime();
375 if (isWalledGardenConnection()) {
376 if (DBG)
377 Slog.d(WWS_TAG,
378 "Walled garden test complete - walled garden detected");
379 mStatus.state = WatchdogState.WALLED_GARDEN_DETECTED;
380 } else {
381 if (DBG)
382 Slog.d(WWS_TAG, "Walled garden test complete - online");
383 mStatus.state = WatchdogState.CHECKS_COMPLETE;
384 }
385 }
386
387 private boolean handleSingleDnsCheck() {
388 mStatus.lastSingleCheckTime = SystemClock.elapsedRealtime();
389 long responseTime = mDnsPinger.pingDns(mDnsPinger.getDns(),
390 DNS_PING_TIMEOUT_MS);
391 if (DBG) {
392 Slog.d(WWS_TAG, "Ran a single DNS ping. Response time: " + responseTime);
393 }
394 if (responseTime < 0) {
395 return false;
396 }
397 return true;
398
399 }
400
401 /**
402 * @return Delay in MS before next single DNS check can proceed.
403 */
404 private long timeToNextScheduledDNSCheck() {
405 if (mStatus.signal > LOW_SIGNAL_CUTOFF) {
406 return waitTime(MIN_SINGLE_DNS_CHECK_INTERVAL, mStatus.lastSingleCheckTime);
407 } else {
408 return waitTime(MIN_LOW_SIGNAL_CHECK_INTERVAL, mStatus.lastSingleCheckTime);
409 }
410 }
411
412 /**
413 * Helper to return wait time left given a min interval and last run
414 *
415 * @param interval minimum wait interval
416 * @param lastTime last time action was performed in
417 * SystemClock.elapsedRealtime()
418 * @return non negative time to wait
419 */
420 private static long waitTime(long interval, long lastTime) {
421 long wait = interval + lastTime - SystemClock.elapsedRealtime();
422 return wait > 0 ? wait : 0;
423 }
424
425 private void popUpBrowser() {
426 Uri uri = Uri.parse("http://www.google.com");
427 Intent intent = new Intent(Intent.ACTION_VIEW, uri);
428 intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
429 Intent.FLAG_ACTIVITY_NEW_TASK);
430 mContext.startActivity(intent);
431 }
432
433 private void disableAP(WifiInfo info) {
434 // TODO : Unban networks if they had low signal ?
435 Slog.i(WWS_TAG, String.format("Disabling current SSID, %s [bssid %s]. " +
436 "numChecks %d, numAPs %d", mStatus.ssid, mStatus.bssid,
437 mStatus.numFullDNSchecks, mStatus.allBssids.size()));
438 mWifiManager.disableNetwork(info.getNetworkId());
439 mStatus.state = WatchdogState.INACTIVE;
440 }
441
442 private void blacklistAP() {
443 Slog.i(WWS_TAG, String.format("Blacklisting current BSSID %s [ssid %s]. " +
444 "numChecks %d, numAPs %d", mStatus.bssid, mStatus.ssid,
445 mStatus.numFullDNSchecks, mStatus.allBssids.size()));
446
447 mWifiManager.addToBlacklist(mStatus.bssid);
448 mWifiManager.reassociate();
449 mStatus.state = WatchdogState.BLACKLISTED_AP;
450 }
451
452 /**
453 * Checks the scan for new BBIDs using current mSsid
454 */
455 private void updateBssids() {
456 String curSsid = mStatus.ssid;
457 HashSet<String> bssids = mStatus.allBssids;
458 List<ScanResult> results = mWifiManager.getScanResults();
459 int oldNumBssids = bssids.size();
460
461 if (results == null) {
462 if (VDBG) {
463 Slog.v(WWS_TAG, "updateBssids: Got null scan results!");
464 }
465 return;
466 }
467
468 for (ScanResult result : results) {
469 if (result != null && curSsid.equals(result.SSID))
470 bssids.add(result.BSSID);
471 }
472
473 // if (VDBG && bssids.size() - oldNumBssids > 0) {
474 // Slog.v(WWS_TAG,
475 // String.format("updateBssids:: Found %d new APs (total %d) on SSID %s",
476 // bssids.size() - oldNumBssids, bssids.size(), curSsid));
477 // }
478 }
479
480 enum DnsCheckStatus {
481 SUCCESS,
482 FAILURE,
483 INCOMPLETE
484 }
485
486 /**
487 * Computes the current results of the dns check, ends early if outcome is
488 * assured.
489 */
490 private DnsCheckStatus currentDnsCheckStatus() {
491 /**
492 * After a full ping count, if we have more responses than this cutoff,
493 * the outcome is success; else it is 'failure'.
494 */
495 double pingResponseCutoff = MIN_RESPONSE_RATE * NUM_DNS_PINGS;
496 int remainingChecks = NUM_DNS_PINGS - mStatus.dnsCheckTries;
497
498 /**
499 * Our final success count will be at least this big, so we're
500 * guaranteed to succeed.
501 */
502 if (mStatus.dnsCheckSuccesses >= pingResponseCutoff) {
503 return DnsCheckStatus.SUCCESS;
504 }
505
506 /**
507 * Our final count will be at most the current count plus the remaining
508 * pings - we're guaranteed to fail.
509 */
510 if (remainingChecks + mStatus.dnsCheckSuccesses < pingResponseCutoff) {
511 return DnsCheckStatus.FAILURE;
512 }
513
514 return DnsCheckStatus.INCOMPLETE;
515 }
516
517 private void initDnsFullCheck() {
518 if (DBG) {
519 Slog.d(WWS_TAG, "Starting DNS pings at " + SystemClock.elapsedRealtime());
520 }
521 mStatus.numFullDNSchecks++;
522 mStatus.dnsCheckSuccesses = 0;
523 mStatus.dnsCheckTries = 0;
524 mStatus.state = WatchdogState.DNS_FULL_CHECK;
525
526 if (DBG) {
527 mDNSCheckLogStr = String.format("Dns Check %d. Pinging %s on ssid [%s]: ",
Isaac Levyb1ef2922011-06-27 10:02:50 -0700528 mStatus.numFullDNSchecks, mDnsPinger.getDns(),
Isaac Levybc7dfb52011-06-06 15:34:01 -0700529 mStatus.ssid);
530 }
531 }
532
533 /**
534 * DNS based detection techniques do not work at all hotspots. The one sure
535 * way to check a walled garden is to see if a URL fetch on a known address
536 * fetches the data we expect
537 */
538 private boolean isWalledGardenConnection() {
539 InputStream in = null;
540 HttpURLConnection urlConnection = null;
541 try {
542 URL url = new URL(getWalledGardenUrl());
543 urlConnection = (HttpURLConnection) url.openConnection();
544 in = new BufferedInputStream(urlConnection.getInputStream());
545 Scanner scanner = new Scanner(in);
546 if (scanner.findInLine(getWalledGardenPattern()) != null) {
547 return false;
548 } else {
549 return true;
550 }
551 } catch (IOException e) {
552 return false;
553 } finally {
554 if (in != null) {
555 try {
556 in.close();
557 } catch (IOException e) {
558 }
559 }
560 if (urlConnection != null)
561 urlConnection.disconnect();
562 }
563 }
564
565 /**
566 * There is little logic inside this class, instead methods of the form
567 * "handle___" are called in the main {@link WifiWatchdogService}.
568 */
569 private class WifiWatchdogHandler extends Handler {
570 /**
571 * Major network event, object is NetworkInfo
572 */
573 static final int MESSAGE_NETWORK_EVENT = 1;
574 /**
575 * Change in settings, no object
576 */
577 static final int MESSAGE_CONTEXT_EVENT = 2;
578
579 /**
580 * Change in signal strength
581 */
582 static final int RSSI_CHANGE_EVENT = 3;
583 static final int SCAN_RESULTS_AVAILABLE = 4;
584
585 static final int WIFI_STATE_CHANGE = 5;
586
587 /**
588 * Single step of state machine. One DNS check, or one WalledGarden
589 * check, or one external action. We separate out external actions to
590 * increase chance of detecting that a check failure is caused by change
591 * in network status. Messages should have an arg1 which to sync status
592 * messages.
593 */
594 static final int CHECK_SEQUENCE_STEP = 10;
595 static final int SINGLE_DNS_CHECK = 11;
596
597 /**
598 * @param looper
599 */
600 public WifiWatchdogHandler(Looper looper) {
601 super(looper);
602 }
603
604 boolean singleCheckQueued = false;
605 long queuedSingleDnsCheckArrival;
606
607 /**
608 * Sends a singleDnsCheck message with shortest time - guards against
609 * multiple.
610 */
611 private boolean queueSingleDnsCheck() {
612 long delay = timeToNextScheduledDNSCheck();
613 long newArrival = delay + SystemClock.elapsedRealtime();
614 if (singleCheckQueued && queuedSingleDnsCheckArrival <= newArrival)
615 return true;
616 queuedSingleDnsCheckArrival = newArrival;
617 singleCheckQueued = true;
618 removeMessages(SINGLE_DNS_CHECK);
619 return sendMessageDelayed(obtainMessage(SINGLE_DNS_CHECK), delay);
620 }
621
622 boolean checkSequenceQueued = false;
623 long queuedCheckSequenceArrival;
624
625 /**
626 * Sends a state_machine_step message if the delay requested is lower
627 * than the current delay.
628 */
629 private boolean sendCheckSequenceStep(long delay) {
630 long newArrival = delay + SystemClock.elapsedRealtime();
631 if (checkSequenceQueued && queuedCheckSequenceArrival <= newArrival)
632 return true;
633 queuedCheckSequenceArrival = newArrival;
634 checkSequenceQueued = true;
635 removeMessages(CHECK_SEQUENCE_STEP);
636 return sendMessageDelayed(obtainMessage(CHECK_SEQUENCE_STEP), delay);
637 }
638
639 @Override
640 public void handleMessage(Message msg) {
641 switch (msg.what) {
642 case CHECK_SEQUENCE_STEP:
643 checkSequenceQueued = false;
644 handleStateStep();
645 if (mStatus.state == WatchdogState.CHECKS_COMPLETE) {
646 queueSingleDnsCheck();
647 } else if (mStatus.state == WatchdogState.DNS_FULL_CHECK) {
648 sendCheckSequenceStep(DNS_PING_INTERVAL);
649 } else if (mStatus.state == WatchdogState.BLACKLISTED_AP) {
650 sendCheckSequenceStep(BLACKLIST_FOLLOWUP_INTERVAL);
651 } else if (mStatus.state != WatchdogState.INACTIVE) {
652 sendCheckSequenceStep(0);
653 }
654 return;
655 case MESSAGE_NETWORK_EVENT:
656 if (!mBroadcastsEnabled) {
657 Slog.e(WWS_TAG,
658 "MessageNetworkEvent - WatchdogService not enabled... returning");
659 return;
660 }
661 NetworkInfo info = (NetworkInfo) msg.obj;
662 switch (info.getState()) {
663 case DISCONNECTED:
664 mStatus.state = WatchdogState.INACTIVE;
665 return;
666 case CONNECTED:
667 handleNewConnection();
668 sendCheckSequenceStep(0);
669 }
670 return;
671 case SINGLE_DNS_CHECK:
672 singleCheckQueued = false;
673 if (mStatus.state != WatchdogState.CHECKS_COMPLETE) {
674 Slog.d(WWS_TAG, "Single check returning, curState: " + mStatus.state);
675 break;
676 }
677
678 if (!handleSingleDnsCheck()) {
679 initDnsFullCheck();
680 sendCheckSequenceStep(0);
681 } else {
682 queueSingleDnsCheck();
683 }
684
685 break;
686 case RSSI_CHANGE_EVENT:
687 updateRssi();
688 if (mStatus.state == WatchdogState.CHECKS_COMPLETE)
689 queueSingleDnsCheck();
690 break;
691 case SCAN_RESULTS_AVAILABLE:
692 updateBssids();
693 break;
694 case WIFI_STATE_CHANGE:
695 if ((Integer) msg.obj == WifiManager.WIFI_STATE_DISABLING) {
696 Slog.i(WWS_TAG, "WifiStateDisabling -- Resetting WatchdogState");
697 mStatus = new Status();
698 }
699 break;
700 case MESSAGE_CONTEXT_EVENT:
701 if (isWatchdogEnabled() && !mBroadcastsEnabled) {
702 mContext.registerReceiver(mBroadcastReceiver, mIntentFilter);
703 mBroadcastsEnabled = true;
704 Slog.i(WWS_TAG, "WifiWatchdogService enabled");
705 } else if (!isWatchdogEnabled() && mBroadcastsEnabled) {
706 mContext.unregisterReceiver(mBroadcastReceiver);
707 removeMessages(SINGLE_DNS_CHECK);
708 removeMessages(CHECK_SEQUENCE_STEP);
709 mBroadcastsEnabled = false;
710 Slog.i(WWS_TAG, "WifiWatchdogService disabled");
711 }
712 break;
713 }
714 }
715 }
716
717 public void dump(PrintWriter pw) {
718 pw.print("WatchdogStatus: ");
719 pw.print("State " + mStatus.state);
720 pw.println(", network [" + mStatus.ssid + ", " + mStatus.bssid + "]");
721 pw.print("checkCount " + mStatus.numFullDNSchecks);
Isaac Levyb1ef2922011-06-27 10:02:50 -0700722 pw.println(", bssids: " + mStatus.allBssids);
Isaac Levybc7dfb52011-06-06 15:34:01 -0700723 pw.print(", hasCheckMessages? " +
724 mHandler.hasMessages(WifiWatchdogHandler.CHECK_SEQUENCE_STEP));
725 pw.println(" hasSingleCheckMessages? " +
726 mHandler.hasMessages(WifiWatchdogHandler.SINGLE_DNS_CHECK));
Isaac Levyb1ef2922011-06-27 10:02:50 -0700727 pw.println("DNS check log str: " + mDNSCheckLogStr);
728 pw.println("lastSingleCheck: " + mStatus.lastSingleCheckTime);
Isaac Levybc7dfb52011-06-06 15:34:01 -0700729 }
730
731 /**
732 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED
733 */
734 private Boolean isWalledGardenTestEnabled() {
735 return Settings.Secure.getInt(mContentResolver,
736 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, 1) == 1;
737 }
738
739 /**
740 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_URL
741 */
742 private String getWalledGardenUrl() {
743 String url = Settings.Secure.getString(mContentResolver,
744 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL);
745 if (TextUtils.isEmpty(url))
746 return "http://www.google.com/";
747 return url;
748 }
749
750 /**
751 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_PATTERN
752 */
753 private String getWalledGardenPattern() {
754 String pattern = Settings.Secure.getString(mContentResolver,
755 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_PATTERN);
756 if (TextUtils.isEmpty(pattern))
757 return "<title>.*Google.*</title>";
758 return pattern;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800759 }
760
761 /**
762 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ON
763 */
764 private boolean isWatchdogEnabled() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800765 return Settings.Secure.getInt(mContentResolver,
Isaac Levybc7dfb52011-06-06 15:34:01 -0700766 Settings.Secure.WIFI_WATCHDOG_ON, 1) == 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800767 }
768}