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