blob: 0eb73b7e8a95efb92c6445ff1510924d8012fe5d [file] [log] [blame]
Isaac Levy654f5092011-07-13 17:41:45 -07001/*
2 * Copyright (C) 2011 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 android.net.wifi;
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.ConnectivityManager;
26import android.net.DnsPinger;
27import android.net.NetworkInfo;
28import android.net.Uri;
29import android.os.Message;
30import android.os.SystemClock;
31import android.provider.Settings;
32import android.text.TextUtils;
33import android.util.Slog;
34
35import com.android.internal.util.Protocol;
36import com.android.internal.util.State;
37import com.android.internal.util.StateMachine;
38
39import java.io.BufferedInputStream;
40import java.io.IOException;
41import java.io.InputStream;
42import java.io.PrintWriter;
43import java.net.HttpURLConnection;
44import java.net.URL;
45import java.util.HashSet;
46import java.util.List;
47import java.util.Scanner;
48
49/**
50 * {@link WifiWatchdogStateMachine} monitors the initial connection to a Wi-Fi
51 * network with multiple access points. After the framework successfully
52 * connects to an access point, the watchdog verifies connectivity by 'pinging'
53 * the configured DNS server using {@link DnsPinger}.
54 * <p>
55 * On DNS check failure, the BSSID is blacklisted if it is reasonably likely
56 * that another AP might have internet access; otherwise the SSID is disabled.
57 * <p>
58 * On DNS success, the WatchdogService initiates a walled garden check via an
59 * http get. A browser window is activated if a walled garden is detected.
60 *
61 * @hide
62 */
63public class WifiWatchdogStateMachine extends StateMachine {
64
65 private static final boolean VDBG = false;
66 private static final boolean DBG = true;
67 private static final String WWSM_TAG = "WifiWatchdogStateMachine";
68
69 private static final int WIFI_SIGNAL_LEVELS = 4;
70 /**
71 * Low signal is defined as less than or equal to cut off
72 */
73 private static final int LOW_SIGNAL_CUTOFF = 1;
74
75 private static final long MIN_LOW_SIGNAL_CHECK_INTERVAL_MS = 2 * 60 * 1000;
76 private static final long MIN_SINGLE_DNS_CHECK_INTERVAL_MS = 10 * 60 * 1000;
77 private static final long MIN_WALLED_GARDEN_INTERVAL_MS = 30 * 60 * 1000;
78
79 private static final int MAX_CHECKS_PER_SSID = 7;
80 private static final int NUM_DNS_PINGS = 5;
81 private static final double MIN_DNS_RESPONSE_RATE = 0.50;
82
83 private static final int DNS_PING_TIMEOUT_MS = 800;
84 private static final long DNS_PING_INTERVAL_MS = 100;
85
86 private static final long BLACKLIST_FOLLOWUP_INTERVAL_MS = 15 * 1000;
87
88 private static final int BASE = Protocol.BASE_WIFI_WATCHDOG;
89
90 /**
91 * Indicates the enable setting of WWS may have changed
92 */
93 private static final int EVENT_WATCHDOG_TOGGLED = BASE + 1;
94
95 /**
96 * Indicates the wifi network state has changed. Passed w/ original intent
97 * which has a non-null networkInfo object
98 */
99 private static final int EVENT_NETWORK_STATE_CHANGE = BASE + 2;
100 /**
101 * Indicates the signal has changed. Passed with arg1
102 * {@link #mNetEventCounter} and arg2 [raw signal strength]
103 */
104 private static final int EVENT_RSSI_CHANGE = BASE + 3;
105 private static final int EVENT_SCAN_RESULTS_AVAILABLE = BASE + 4;
106 private static final int EVENT_WIFI_RADIO_STATE_CHANGE = BASE + 5;
107
108 private static final int MESSAGE_CHECK_STEP = BASE + 100;
109 private static final int MESSAGE_HANDLE_WALLED_GARDEN = BASE + 101;
110 private static final int MESSAGE_HANDLE_BAD_AP = BASE + 102;
111 /**
112 * arg1 == mOnlineWatchState.checkCount
113 */
114 private static final int MESSAGE_SINGLE_DNS_CHECK = BASE + 103;
115 private static final int MESSAGE_NETWORK_FOLLOWUP = BASE + 104;
116
117 private Context mContext;
118 private ContentResolver mContentResolver;
119 private WifiManager mWifiManager;
120 private DnsPinger mDnsPinger;
121 private IntentFilter mIntentFilter;
122 private BroadcastReceiver mBroadcastReceiver;
123
124 private DefaultState mDefaultState = new DefaultState();
125 private WatchdogDisabledState mWatchdogDisabledState = new WatchdogDisabledState();
126 private WatchdogEnabledState mWatchdogEnabledState = new WatchdogEnabledState();
127 private NotConnectedState mNotConnectedState = new NotConnectedState();
128 private ConnectedState mConnectedState = new ConnectedState();
129 private DnsCheckingState mDnsCheckingState = new DnsCheckingState();
130 private OnlineWatchState mOnlineWatchState = new OnlineWatchState();
131 private DnsCheckFailureState mDnsCheckFailureState = new DnsCheckFailureState();
132 private WalledGardenState mWalledGardenState = new WalledGardenState();
133 private BlacklistedApState mBlacklistedApState = new BlacklistedApState();
134
135 /**
136 * The {@link WifiInfo} object passed to WWSM on network broadcasts
137 */
138 private WifiInfo mInitialConnInfo;
139 private int mNetEventCounter = 0;
140
141 /**
142 * Currently maintained but not used, TODO
143 */
144 private HashSet<String> mBssids = new HashSet<String>();
145 private int mNumFullDNSchecks = 0;
146
147 private Long mLastWalledGardenCheckTime = null;
148
149 /**
150 * This is set by the blacklisted state and reset when connected to a new AP.
151 * It triggers a disableNetwork call if a DNS check fails.
152 */
153 public boolean mDisableAPNextFailure = false;
154
155 /**
156 * STATE MAP
157 * Default
158 * / \
159 * Disabled Enabled
160 * / \
161 * Disconnected Connected
162 * /---------\
163 * (all other states)
164 */
165 private WifiWatchdogStateMachine(Context context) {
166 super(WWSM_TAG);
167 mContext = context;
168 mContentResolver = context.getContentResolver();
169 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
170 mDnsPinger = new DnsPinger("WifiWatchdogServer.DnsPinger", context,
171 ConnectivityManager.TYPE_WIFI);
172
173 setupNetworkReceiver();
174
175 // The content observer to listen needs a handler
176 registerForSettingsChanges();
177 addState(mDefaultState);
178 addState(mWatchdogDisabledState, mDefaultState);
179 addState(mWatchdogEnabledState, mDefaultState);
180 addState(mNotConnectedState, mWatchdogEnabledState);
181 addState(mConnectedState, mWatchdogEnabledState);
182 addState(mDnsCheckingState, mConnectedState);
183 addState(mDnsCheckFailureState, mConnectedState);
184 addState(mWalledGardenState, mConnectedState);
185 addState(mBlacklistedApState, mConnectedState);
186 addState(mOnlineWatchState, mConnectedState);
187
188 setInitialState(mWatchdogDisabledState);
189 }
190
191 public static WifiWatchdogStateMachine makeWifiWatchdogStateMachine(Context context) {
192 WifiWatchdogStateMachine wwsm = new WifiWatchdogStateMachine(context);
193 wwsm.start();
194 wwsm.sendMessage(EVENT_WATCHDOG_TOGGLED);
195 return wwsm;
196 }
197
198 /**
199 *
200 */
201 private void setupNetworkReceiver() {
202 mBroadcastReceiver = new BroadcastReceiver() {
203 @Override
204 public void onReceive(Context context, Intent intent) {
205 String action = intent.getAction();
206 if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
207 sendMessage(EVENT_NETWORK_STATE_CHANGE, intent);
208 } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
209 obtainMessage(EVENT_RSSI_CHANGE, mNetEventCounter,
210 intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200)).sendToTarget();
211 } else if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
212 sendMessage(EVENT_SCAN_RESULTS_AVAILABLE);
213 } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
214 sendMessage(EVENT_WIFI_RADIO_STATE_CHANGE,
215 intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
216 WifiManager.WIFI_STATE_UNKNOWN));
217 }
218 }
219 };
220
221 mIntentFilter = new IntentFilter();
222 mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
223 mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
224 mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
225 mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
226 }
227
228 /**
229 * Observes the watchdog on/off setting, and takes action when changed.
230 */
231 private void registerForSettingsChanges() {
232 ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
233 @Override
234 public void onChange(boolean selfChange) {
235 sendMessage(EVENT_WATCHDOG_TOGGLED);
236 }
237 };
238
239 mContext.getContentResolver().registerContentObserver(
240 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON),
241 false, contentObserver);
242 }
243
244 /**
245 * DNS based detection techniques do not work at all hotspots. The one sure
246 * way to check a walled garden is to see if a URL fetch on a known address
247 * fetches the data we expect
248 */
249 private boolean isWalledGardenConnection() {
250 InputStream in = null;
251 HttpURLConnection urlConnection = null;
252 try {
253 URL url = new URL(getWalledGardenUrl());
254 urlConnection = (HttpURLConnection) url.openConnection();
255 in = new BufferedInputStream(urlConnection.getInputStream());
256 Scanner scanner = new Scanner(in);
257 if (scanner.findInLine(getWalledGardenPattern()) != null) {
258 return false;
259 } else {
260 return true;
261 }
262 } catch (IOException e) {
263 return false;
264 } finally {
265 if (in != null) {
266 try {
267 in.close();
268 } catch (IOException e) {
269 }
270 }
271 if (urlConnection != null)
272 urlConnection.disconnect();
273 }
274 }
275
276 private boolean rssiStrengthAboveCutoff(int rssi) {
277 return WifiManager.calculateSignalLevel(rssi, WIFI_SIGNAL_LEVELS) > LOW_SIGNAL_CUTOFF;
278 }
279
280 public void dump(PrintWriter pw) {
281 pw.print("WatchdogStatus: ");
282 pw.print("State " + getCurrentState());
283 pw.println(", network [" + mInitialConnInfo + "]");
284 pw.print("checkCount " + mNumFullDNSchecks);
285 pw.println(", bssids: " + mBssids);
286 pw.println("lastSingleCheck: " + mOnlineWatchState.lastCheckTime);
287 }
288
289 /**
290 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED
291 */
292 private Boolean isWalledGardenTestEnabled() {
293 return Settings.Secure.getInt(mContentResolver,
294 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, 1) == 1;
295 }
296
297 /**
298 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_URL
299 */
300 private String getWalledGardenUrl() {
301 String url = Settings.Secure.getString(mContentResolver,
302 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL);
303 if (TextUtils.isEmpty(url))
304 return "http://www.google.com/";
305 return url;
306 }
307
308 /**
309 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_PATTERN
310 */
311 private String getWalledGardenPattern() {
312 String pattern = Settings.Secure.getString(mContentResolver,
313 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_PATTERN);
314 if (TextUtils.isEmpty(pattern))
315 return "<title>.*Google.*</title>";
316 return pattern;
317 }
318
319 /**
320 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ON
321 */
322 private boolean isWatchdogEnabled() {
323 return Settings.Secure.getInt(mContentResolver,
324 Settings.Secure.WIFI_WATCHDOG_ON, 1) == 1;
325 }
326
327
328 /**
329 * Helper to return wait time left given a min interval and last run
330 *
331 * @param interval minimum wait interval
332 * @param lastTime last time action was performed in
333 * SystemClock.elapsedRealtime(). Null if never.
334 * @return non negative time to wait
335 */
336 private static long waitTime(long interval, Long lastTime) {
337 if (lastTime == null)
338 return 0;
339 long wait = interval + lastTime - SystemClock.elapsedRealtime();
340 return wait > 0 ? wait : 0;
341 }
342
343 private static String wifiInfoToStr(WifiInfo wifiInfo) {
344 if (wifiInfo == null)
345 return "null";
346 return "(" + wifiInfo.getSSID() + ", " + wifiInfo.getBSSID() + ")";
347 }
348
349 /**
350 *
351 */
352 private void resetWatchdogState() {
353 mInitialConnInfo = null;
354 mDisableAPNextFailure = false;
355 mLastWalledGardenCheckTime = null;
356 mNumFullDNSchecks = 0;
357 mBssids.clear();
358 }
359
360 private void popUpBrowser() {
361 Uri uri = Uri.parse("http://www.google.com");
362 Intent intent = new Intent(Intent.ACTION_VIEW, uri);
363 intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
364 Intent.FLAG_ACTIVITY_NEW_TASK);
365 mContext.startActivity(intent);
366 }
367
368 private void sendCheckStepMessage(long delay) {
369 sendMessageDelayed(obtainMessage(MESSAGE_CHECK_STEP, mNetEventCounter, 0), delay);
370 }
371
372 class DefaultState extends State {
373 @Override
374 public boolean processMessage(Message msg) {
375 if (VDBG) {
376 Slog.v(WWSM_TAG, "Caught message " + msg.what + " in state " +
377 getCurrentState().getName());
378 }
379 return HANDLED;
380 }
381 }
382
383 class WatchdogDisabledState extends State {
384 @Override
385 public boolean processMessage(Message msg) {
386 switch (msg.what) {
387 case EVENT_WATCHDOG_TOGGLED:
388 if (isWatchdogEnabled())
389 transitionTo(mNotConnectedState);
390 return HANDLED;
391 }
392 return NOT_HANDLED;
393 }
394 }
395
396 class WatchdogEnabledState extends State {
397 @Override
398 public void enter() {
399 resetWatchdogState();
400 mContext.registerReceiver(mBroadcastReceiver, mIntentFilter);
401 Slog.i(WWSM_TAG, "WifiWatchdogService enabled");
402 }
403
404 @Override
405 public boolean processMessage(Message msg) {
406 switch (msg.what) {
407 case EVENT_WATCHDOG_TOGGLED:
408 if (!isWatchdogEnabled())
409 transitionTo(mWatchdogDisabledState);
410 return HANDLED;
411 case EVENT_NETWORK_STATE_CHANGE:
412 Intent stateChangeIntent = (Intent) msg.obj;
413 NetworkInfo networkInfo = (NetworkInfo)
414 stateChangeIntent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
415
416 switch (networkInfo.getState()) {
417 case CONNECTED:
418 // WifiInfo wifiInfo = (WifiInfo)
419 // stateChangeIntent
420 // .getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
421 // TODO : Replace with above code when API is changed
422 WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
423 if (wifiInfo == null) {
424 Slog.e(WWSM_TAG, "Connected --> WifiInfo object null!");
425 return HANDLED;
426 }
427
428 if (wifiInfo.getSSID() == null || wifiInfo.getBSSID() == null) {
429 Slog.e(WWSM_TAG, "Received wifiInfo object with null elts: "
430 + wifiInfoToStr(wifiInfo));
431 return HANDLED;
432 }
433
434 initConnection(wifiInfo);
435 transitionTo(mDnsCheckingState);
436 mNetEventCounter++;
437 return HANDLED;
438 case DISCONNECTED:
439 case DISCONNECTING:
440 mNetEventCounter++;
441 transitionTo(mNotConnectedState);
442 return HANDLED;
443 }
444 return HANDLED;
445 case EVENT_WIFI_RADIO_STATE_CHANGE:
446 if ((Integer) msg.obj == WifiManager.WIFI_STATE_DISABLING) {
447 Slog.i(WWSM_TAG, "WifiStateDisabling -- Resetting WatchdogState");
448 resetWatchdogState();
449 mNetEventCounter++;
450 transitionTo(mNotConnectedState);
451 }
452 return HANDLED;
453 }
454
455 return NOT_HANDLED;
456 }
457
458 /**
459 * @param wifiInfo Info object with non-null ssid and bssid
460 */
461 private void initConnection(WifiInfo wifiInfo) {
462 if (VDBG) {
463 Slog.v(WWSM_TAG, "Connected:: old " + wifiInfoToStr(mInitialConnInfo) +
464 " ==> new " + wifiInfoToStr(wifiInfo));
465 }
466
467 if (mInitialConnInfo == null || !wifiInfo.getSSID().equals(mInitialConnInfo.getSSID())) {
468 resetWatchdogState();
469 } else if (!wifiInfo.getBSSID().equals(mInitialConnInfo.getBSSID())) {
470 mDisableAPNextFailure = false;
471 }
472 mInitialConnInfo = wifiInfo;
473 }
474
475 @Override
476 public void exit() {
477 mContext.unregisterReceiver(mBroadcastReceiver);
478 Slog.i(WWSM_TAG, "WifiWatchdogService disabled");
479 }
480 }
481
482 class NotConnectedState extends State {
483 }
484
485 class ConnectedState extends State {
486 @Override
487 public boolean processMessage(Message msg) {
488 switch (msg.what) {
489 case EVENT_SCAN_RESULTS_AVAILABLE:
490 String curSsid = mInitialConnInfo.getSSID();
491 List<ScanResult> results = mWifiManager.getScanResults();
492 int oldNumBssids = mBssids.size();
493
494 if (results == null) {
495 if (DBG) {
496 Slog.d(WWSM_TAG, "updateBssids: Got null scan results!");
497 }
498 return HANDLED;
499 }
500
501 for (ScanResult result : results) {
502 if (result == null || result.SSID == null) {
503 if (VDBG) {
504 Slog.v(WWSM_TAG, "Received invalid scan result: " + result);
505 }
506 continue;
507 }
508 if (curSsid.equals(result.SSID))
509 mBssids.add(result.BSSID);
510 }
511 return HANDLED;
512 }
513 return NOT_HANDLED;
514 }
515
516 }
517
518 class DnsCheckingState extends State {
519 int dnsCheckTries = 0;
520 int dnsCheckSuccesses = 0;
521 String dnsCheckLogStr = "";
522
523 @Override
524 public void enter() {
525 mNumFullDNSchecks++;
526 dnsCheckSuccesses = 0;
527 dnsCheckTries = 0;
528 if (DBG) {
529 Slog.d(WWSM_TAG, "Starting DNS pings at " + SystemClock.elapsedRealtime());
530 dnsCheckLogStr = String.format("Dns Check %d. Pinging %s on ssid [%s]: ",
531 mNumFullDNSchecks, mDnsPinger.getDns(), mInitialConnInfo.getSSID());
532 }
533
534 sendCheckStepMessage(0);
535 }
536
537 @Override
538 public boolean processMessage(Message msg) {
539 if (msg.what != MESSAGE_CHECK_STEP) {
540 return NOT_HANDLED;
541 }
542 if (msg.arg1 != mNetEventCounter) {
543 Slog.d(WWSM_TAG, "Check step out of sync, ignoring...");
544 return HANDLED;
545 }
546
547 long pingResponseTime = mDnsPinger.pingDns(mDnsPinger.getDns(),
548 DNS_PING_TIMEOUT_MS);
549
550 dnsCheckTries++;
551 if (pingResponseTime >= 0)
552 dnsCheckSuccesses++;
553
554 if (DBG) {
555 if (pingResponseTime >= 0) {
556 dnsCheckLogStr += "|" + pingResponseTime;
557 } else {
558 dnsCheckLogStr += "|x";
559 }
560 }
561
562 if (VDBG) {
563 Slog.v(WWSM_TAG, dnsCheckLogStr);
564 }
565
566 /**
567 * After a full ping count, if we have more responses than this
568 * cutoff, the outcome is success; else it is 'failure'.
569 */
570 double pingResponseCutoff = MIN_DNS_RESPONSE_RATE * NUM_DNS_PINGS;
571 int remainingChecks = NUM_DNS_PINGS - dnsCheckTries;
572
573 /**
574 * Our final success count will be at least this big, so we're
575 * guaranteed to succeed.
576 */
577 if (dnsCheckSuccesses >= pingResponseCutoff) {
578 // DNS CHECKS OK, NOW WALLED GARDEN
579 if (DBG) {
580 Slog.d(WWSM_TAG, dnsCheckLogStr + "| SUCCESS");
581 }
582
583 if (!shouldCheckWalledGarden()) {
584 transitionTo(mOnlineWatchState);
585 return HANDLED;
586 }
587
588 mLastWalledGardenCheckTime = SystemClock.elapsedRealtime();
589 if (isWalledGardenConnection()) {
590 if (DBG)
591 Slog.d(WWSM_TAG,
592 "Walled garden test complete - walled garden detected");
593 transitionTo(mWalledGardenState);
594 } else {
595 if (DBG)
596 Slog.d(WWSM_TAG, "Walled garden test complete - online");
597 transitionTo(mOnlineWatchState);
598 }
599 return HANDLED;
600 }
601
602 /**
603 * Our final count will be at most the current count plus the
604 * remaining pings - we're guaranteed to fail.
605 */
606 if (remainingChecks + dnsCheckSuccesses < pingResponseCutoff) {
607 if (DBG) {
608 Slog.d(WWSM_TAG, dnsCheckLogStr + "| FAILURE");
609 }
610 transitionTo(mDnsCheckFailureState);
611 return HANDLED;
612 }
613
614 // Still in dns check step
615 sendCheckStepMessage(DNS_PING_INTERVAL_MS);
616 return HANDLED;
617 }
618
619 private boolean shouldCheckWalledGarden() {
620 if (!isWalledGardenTestEnabled()) {
621 if (VDBG)
622 Slog.v(WWSM_TAG, "Skipping walled garden check - disabled");
623 return false;
624 }
625 long waitTime = waitTime(MIN_WALLED_GARDEN_INTERVAL_MS,
626 mLastWalledGardenCheckTime);
627 if (waitTime > 0) {
628 if (DBG) {
629 Slog.d(WWSM_TAG, "Skipping walled garden check - wait " +
630 waitTime + " ms.");
631 }
632 return false;
633 }
634 return true;
635 }
636
637 }
638
639 class OnlineWatchState extends State {
640 /**
641 * Signals a short-wait message is enqueued for the current 'guard' counter
642 */
643 boolean unstableSignalChecks = false;
644
645 /**
646 * The signal is unstable. We should enqueue a short-wait check, if one is enqueued
647 * already
648 */
649 boolean signalUnstable = false;
650
651 /**
652 * A monotonic counter to ensure that at most one check message will be processed from any
653 * set of check messages currently enqueued. Avoids duplicate checks when a low-signal
654 * event is observed.
655 */
656 int checkGuard = 0;
657 Long lastCheckTime = null;
658
659 @Override
660 public void enter() {
661 lastCheckTime = SystemClock.elapsedRealtime();
662 signalUnstable = false;
663 checkGuard++;
664 unstableSignalChecks = false;
665 triggerSingleDnsCheck();
666 }
667
668 @Override
669 public boolean processMessage(Message msg) {
670 switch (msg.what) {
671 case EVENT_RSSI_CHANGE:
672 if (msg.arg1 != mNetEventCounter) {
673 if (DBG) {
674 Slog.d(WWSM_TAG, "Rssi change message out of sync, ignoring");
675 }
676 return HANDLED;
677 }
678 int newRssi = msg.arg2;
679 signalUnstable = !rssiStrengthAboveCutoff(newRssi);
680 if (VDBG) {
681 Slog.v(WWSM_TAG, "OnlineWatchState:: new rssi " + newRssi + " --> level " +
682 WifiManager.calculateSignalLevel(newRssi, WIFI_SIGNAL_LEVELS));
683 }
684
685 if (signalUnstable && !unstableSignalChecks) {
686 if (VDBG) {
687 Slog.v(WWSM_TAG, "Sending triggered check msg");
688 }
689 triggerSingleDnsCheck();
690 }
691 return HANDLED;
692 case MESSAGE_SINGLE_DNS_CHECK:
693 if (msg.arg1 != checkGuard) {
694 if (VDBG) {
695 Slog.v(WWSM_TAG, "Single check msg out of sync, ignoring.");
696 }
697 return HANDLED;
698 }
699 lastCheckTime = SystemClock.elapsedRealtime();
700 long responseTime = mDnsPinger.pingDns(mDnsPinger.getDns(),
701 DNS_PING_TIMEOUT_MS);
702 if (responseTime >= 0) {
703 if (VDBG) {
704 Slog.v(WWSM_TAG, "Ran a single DNS ping. Response time: "
705 + responseTime);
706 }
707
708 checkGuard++;
709 unstableSignalChecks = false;
710 triggerSingleDnsCheck();
711 } else {
712 if (DBG) {
713 Slog.d(WWSM_TAG, "Single dns ping failure. Starting full checks.");
714 }
715 transitionTo(mDnsCheckingState);
716 }
717 return HANDLED;
718 }
719 return NOT_HANDLED;
720 }
721
722 /**
723 * Times a dns check with an interval based on {@link #curSignalStable}
724 */
725 private void triggerSingleDnsCheck() {
726 long waitInterval;
727 if (signalUnstable) {
728 waitInterval = MIN_LOW_SIGNAL_CHECK_INTERVAL_MS;
729 unstableSignalChecks = true;
730 } else {
731 waitInterval = MIN_SINGLE_DNS_CHECK_INTERVAL_MS;
732 }
733 sendMessageDelayed(obtainMessage(MESSAGE_SINGLE_DNS_CHECK, checkGuard, 0),
734 waitTime(waitInterval, lastCheckTime));
735 }
736 }
737
738 class DnsCheckFailureState extends State {
739 @Override
740 public void enter() {
741 obtainMessage(MESSAGE_HANDLE_BAD_AP, mNetEventCounter, 0).sendToTarget();
742 }
743
744 @Override
745 public boolean processMessage(Message msg) {
746 if (msg.what != MESSAGE_HANDLE_BAD_AP) {
747 return NOT_HANDLED;
748 }
749
750 if (msg.arg1 != mNetEventCounter) {
751 if (VDBG) {
752 Slog.v(WWSM_TAG, "Msg out of sync, ignoring...");
753 }
754 return HANDLED;
755 }
756
757 if (mDisableAPNextFailure || mNumFullDNSchecks >= MAX_CHECKS_PER_SSID) {
758 // TODO : Unban networks if they had low signal ?
759 Slog.i(WWSM_TAG, "Disabling current SSID " + wifiInfoToStr(mInitialConnInfo)
760 + ". " +
761 "numChecks " + mNumFullDNSchecks + ", numAPs " + mBssids.size());
762 mWifiManager.disableNetwork(mInitialConnInfo.getNetworkId());
763 transitionTo(mNotConnectedState);
764 } else {
765 Slog.i(WWSM_TAG, "Blacklisting current BSSID. " + wifiInfoToStr(mInitialConnInfo) +
766 "numChecks " + mNumFullDNSchecks + ", numAPs " + mBssids.size());
767
768 mWifiManager.addToBlacklist(mInitialConnInfo.getBSSID());
769 mWifiManager.reassociate();
770 transitionTo(mBlacklistedApState);
771 }
772 return HANDLED;
773 }
774 }
775
776 class WalledGardenState extends State {
777 @Override
778 public void enter() {
779 obtainMessage(MESSAGE_HANDLE_WALLED_GARDEN, mNetEventCounter, 0).sendToTarget();
780 }
781
782 @Override
783 public boolean processMessage(Message msg) {
784 if (msg.what != MESSAGE_HANDLE_WALLED_GARDEN) {
785 return NOT_HANDLED;
786 }
787
788 if (msg.arg1 != mNetEventCounter) {
789 if (VDBG) {
790 Slog.v(WWSM_TAG, "WalledGardenState::Msg out of sync, ignoring...");
791 }
792 return HANDLED;
793 }
794 popUpBrowser();
795 transitionTo(mOnlineWatchState);
796 return HANDLED;
797 }
798 }
799
800 class BlacklistedApState extends State {
801 @Override
802 public void enter() {
803 mDisableAPNextFailure = true;
804 sendMessageDelayed(obtainMessage(MESSAGE_NETWORK_FOLLOWUP, mNetEventCounter, 0),
805 BLACKLIST_FOLLOWUP_INTERVAL_MS);
806 }
807
808 @Override
809 public boolean processMessage(Message msg) {
810 if (msg.what != MESSAGE_NETWORK_FOLLOWUP) {
811 return NOT_HANDLED;
812 }
813
814 if (msg.arg1 != mNetEventCounter) {
815 if (VDBG) {
816 Slog.v(WWSM_TAG, "BlacklistedApState::Msg out of sync, ignoring...");
817 }
818 return HANDLED;
819 }
820
821 transitionTo(mDnsCheckingState);
822 return HANDLED;
823 }
824 }
825}