WifiWatchdogStateMachine.java revision e046975503e7c6ebd78e35afaad88e3fb1ebfb5a
1/* 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.app.Notification; 20import android.app.NotificationManager; 21import android.app.PendingIntent; 22import android.content.BroadcastReceiver; 23import android.content.ContentResolver; 24import android.content.Context; 25import android.content.Intent; 26import android.content.IntentFilter; 27import android.content.res.Resources; 28import android.database.ContentObserver; 29import android.net.ConnectivityManager; 30import android.net.DnsPinger; 31import android.net.NetworkInfo; 32import android.net.Uri; 33import android.os.Message; 34import android.os.SystemClock; 35import android.provider.Settings; 36import android.provider.Settings.Secure; 37import android.util.Slog; 38 39import com.android.internal.util.Protocol; 40import com.android.internal.util.State; 41import com.android.internal.util.StateMachine; 42 43import java.io.BufferedInputStream; 44import java.io.IOException; 45import java.io.InputStream; 46import java.io.PrintWriter; 47import java.net.HttpURLConnection; 48import java.net.URL; 49import java.util.HashSet; 50import java.util.List; 51import java.util.Scanner; 52import java.util.regex.Pattern; 53 54/** 55 * {@link WifiWatchdogStateMachine} monitors the initial connection to a Wi-Fi 56 * network with multiple access points. After the framework successfully 57 * connects to an access point, the watchdog verifies connectivity by 'pinging' 58 * the configured DNS server using {@link DnsPinger}. 59 * <p> 60 * On DNS check failure, the BSSID is blacklisted if it is reasonably likely 61 * that another AP might have internet access; otherwise the SSID is disabled. 62 * <p> 63 * On DNS success, the WatchdogService initiates a walled garden check via an 64 * http get. A browser window is activated if a walled garden is detected. 65 * 66 * @hide 67 */ 68public class WifiWatchdogStateMachine extends StateMachine { 69 70 71 private static final boolean VDBG = false; 72 private static final boolean DBG = true; 73 private static final String WWSM_TAG = "WifiWatchdogStateMachine"; 74 75 private static final int WIFI_SIGNAL_LEVELS = 4; 76 /** 77 * Low signal is defined as less than or equal to cut off 78 */ 79 private static final int LOW_SIGNAL_CUTOFF = 1; 80 81 private static final long DEFAULT_DNS_CHECK_SHORT_INTERVAL_MS = 2 * 60 * 1000; 82 private static final long DEFAULT_DNS_CHECK_LONG_INTERVAL_MS = 10 * 60 * 1000; 83 private static final long DEFAULT_WALLED_GARDEN_INTERVAL_MS = 30 * 60 * 1000; 84 85 private static final int DEFAULT_MAX_SSID_BLACKLISTS = 7; 86 private static final int DEFAULT_NUM_DNS_PINGS = 5; 87 private static final int DEFAULT_MIN_DNS_RESPONSES = 3; 88 private static final long DNS_PING_INTERVAL_MS = 100; 89 90 private static final int DEFAULT_DNS_PING_TIMEOUT_MS = 1500; 91 92 private static final long DEFAULT_BLACKLIST_FOLLOWUP_INTERVAL_MS = 15 * 1000; 93 94 private static final String DEFAULT_WALLED_GARDEN_URL = "http://www.google.com/"; 95 private static final String DEFAULT_WALLED_GARDEN_PATTERN = "<title>.*Google.*</title>"; 96 97 98 private static final int BASE = Protocol.BASE_WIFI_WATCHDOG; 99 100 /** 101 * Indicates the enable setting of WWS may have changed 102 */ 103 private static final int EVENT_WATCHDOG_TOGGLED = BASE + 1; 104 105 /** 106 * Indicates the wifi network state has changed. Passed w/ original intent 107 * which has a non-null networkInfo object 108 */ 109 private static final int EVENT_NETWORK_STATE_CHANGE = BASE + 2; 110 /** 111 * Indicates the signal has changed. Passed with arg1 112 * {@link #mNetEventCounter} and arg2 [raw signal strength] 113 */ 114 private static final int EVENT_RSSI_CHANGE = BASE + 3; 115 private static final int EVENT_SCAN_RESULTS_AVAILABLE = BASE + 4; 116 private static final int EVENT_WIFI_RADIO_STATE_CHANGE = BASE + 5; 117 private static final int EVENT_WATCHDOG_SETTINGS_CHANGE = BASE + 6; 118 119 private static final int MESSAGE_CHECK_STEP = BASE + 100; 120 private static final int MESSAGE_HANDLE_WALLED_GARDEN = BASE + 101; 121 private static final int MESSAGE_HANDLE_BAD_AP = BASE + 102; 122 /** 123 * arg1 == mOnlineWatchState.checkCount 124 */ 125 private static final int MESSAGE_SINGLE_DNS_CHECK = BASE + 103; 126 private static final int MESSAGE_NETWORK_FOLLOWUP = BASE + 104; 127 128 private Context mContext; 129 private ContentResolver mContentResolver; 130 private WifiManager mWifiManager; 131 private DnsPinger mDnsPinger; 132 private IntentFilter mIntentFilter; 133 private BroadcastReceiver mBroadcastReceiver; 134 135 private DefaultState mDefaultState = new DefaultState(); 136 private WatchdogDisabledState mWatchdogDisabledState = new WatchdogDisabledState(); 137 private WatchdogEnabledState mWatchdogEnabledState = new WatchdogEnabledState(); 138 private NotConnectedState mNotConnectedState = new NotConnectedState(); 139 private ConnectedState mConnectedState = new ConnectedState(); 140 private DnsCheckingState mDnsCheckingState = new DnsCheckingState(); 141 private OnlineWatchState mOnlineWatchState = new OnlineWatchState(); 142 private DnsCheckFailureState mDnsCheckFailureState = new DnsCheckFailureState(); 143 private WalledGardenState mWalledGardenState = new WalledGardenState(); 144 private BlacklistedApState mBlacklistedApState = new BlacklistedApState(); 145 146 private long mDnsCheckShortIntervalMs; 147 private long mDnsCheckLongIntervalMs; 148 private long mWalledGardenIntervalMs; 149 private int mMaxSsidBlacklists; 150 private int mNumDnsPings; 151 private int mMinDnsResponses; 152 private int mDnsPingTimeoutMs; 153 private long mBlacklistFollowupIntervalMs; 154 private boolean mWalledGardenTestEnabled; 155 private String mWalledGardenUrl; 156 private Pattern mWalledGardenPattern; 157 158 private boolean mShowDisabledNotification; 159 /** 160 * The {@link WifiInfo} object passed to WWSM on network broadcasts 161 */ 162 private WifiInfo mInitialConnInfo; 163 private int mNetEventCounter = 0; 164 165 /** 166 * Currently maintained but not used, TODO 167 */ 168 private HashSet<String> mBssids = new HashSet<String>(); 169 private int mNumCheckFailures = 0; 170 171 private Long mLastWalledGardenCheckTime = null; 172 173 /** 174 * This is set by the blacklisted state and reset when connected to a new AP. 175 * It triggers a disableNetwork call if a DNS check fails. 176 */ 177 public boolean mDisableAPNextFailure = false; 178 179 /** 180 * STATE MAP 181 * Default 182 * / \ 183 * Disabled Enabled 184 * / \ 185 * NotConnected Connected 186 * /---------\ 187 * (all other states) 188 */ 189 private WifiWatchdogStateMachine(Context context) { 190 super(WWSM_TAG); 191 mContext = context; 192 mContentResolver = context.getContentResolver(); 193 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 194 mDnsPinger = new DnsPinger("WifiWatchdogServer.DnsPinger", context, 195 ConnectivityManager.TYPE_WIFI); 196 197 setupNetworkReceiver(); 198 199 // The content observer to listen needs a handler 200 registerForSettingsChanges(); 201 registerForWatchdogToggle(); 202 addState(mDefaultState); 203 addState(mWatchdogDisabledState, mDefaultState); 204 addState(mWatchdogEnabledState, mDefaultState); 205 addState(mNotConnectedState, mWatchdogEnabledState); 206 addState(mConnectedState, mWatchdogEnabledState); 207 addState(mDnsCheckingState, mConnectedState); 208 addState(mDnsCheckFailureState, mConnectedState); 209 addState(mWalledGardenState, mConnectedState); 210 addState(mBlacklistedApState, mConnectedState); 211 addState(mOnlineWatchState, mConnectedState); 212 213 setInitialState(mWatchdogDisabledState); 214 updateSettings(); 215 mShowDisabledNotification = getSettingsBoolean(mContentResolver, 216 Settings.Secure.WIFI_WATCHDOG_SHOW_DISABLED_NETWORK_POPUP, true); 217 } 218 219 public static WifiWatchdogStateMachine makeWifiWatchdogStateMachine(Context context) { 220 WifiWatchdogStateMachine wwsm = new WifiWatchdogStateMachine(context); 221 wwsm.start(); 222 wwsm.sendMessage(EVENT_WATCHDOG_TOGGLED); 223 return wwsm; 224 } 225 226 /** 227 * 228 */ 229 private void setupNetworkReceiver() { 230 mBroadcastReceiver = new BroadcastReceiver() { 231 @Override 232 public void onReceive(Context context, Intent intent) { 233 String action = intent.getAction(); 234 if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { 235 sendMessage(EVENT_NETWORK_STATE_CHANGE, intent); 236 } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) { 237 obtainMessage(EVENT_RSSI_CHANGE, mNetEventCounter, 238 intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200)).sendToTarget(); 239 } else if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { 240 sendMessage(EVENT_SCAN_RESULTS_AVAILABLE); 241 } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { 242 sendMessage(EVENT_WIFI_RADIO_STATE_CHANGE, 243 intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 244 WifiManager.WIFI_STATE_UNKNOWN)); 245 } 246 } 247 }; 248 249 mIntentFilter = new IntentFilter(); 250 mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); 251 mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); 252 mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION); 253 mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); 254 } 255 256 /** 257 * Observes the watchdog on/off setting, and takes action when changed. 258 */ 259 private void registerForWatchdogToggle() { 260 ContentObserver contentObserver = new ContentObserver(this.getHandler()) { 261 @Override 262 public void onChange(boolean selfChange) { 263 sendMessage(EVENT_WATCHDOG_TOGGLED); 264 } 265 }; 266 267 mContext.getContentResolver().registerContentObserver( 268 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON), 269 false, contentObserver); 270 } 271 272 /** 273 * Observes watchdogs secure setting changes. 274 */ 275 private void registerForSettingsChanges() { 276 ContentObserver contentObserver = new ContentObserver(this.getHandler()) { 277 @Override 278 public void onChange(boolean selfChange) { 279 sendMessage(EVENT_WATCHDOG_SETTINGS_CHANGE); 280 } 281 }; 282 283 mContext.getContentResolver().registerContentObserver( 284 Settings.Secure.getUriFor( 285 Settings.Secure.WIFI_WATCHDOG_DNS_CHECK_SHORT_INTERVAL_MS), 286 false, contentObserver); 287 mContext.getContentResolver().registerContentObserver( 288 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_DNS_CHECK_LONG_INTERVAL_MS), 289 false, contentObserver); 290 mContext.getContentResolver().registerContentObserver( 291 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS), 292 false, contentObserver); 293 mContext.getContentResolver().registerContentObserver( 294 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_MAX_SSID_BLACKLISTS), 295 false, contentObserver); 296 mContext.getContentResolver().registerContentObserver( 297 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_NUM_DNS_PINGS), 298 false, contentObserver); 299 mContext.getContentResolver().registerContentObserver( 300 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_MIN_DNS_RESPONSES), 301 false, contentObserver); 302 mContext.getContentResolver().registerContentObserver( 303 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_DNS_PING_TIMEOUT_MS), 304 false, contentObserver); 305 mContext.getContentResolver().registerContentObserver( 306 Settings.Secure.getUriFor( 307 Settings.Secure.WIFI_WATCHDOG_BLACKLIST_FOLLOWUP_INTERVAL_MS), 308 false, contentObserver); 309 mContext.getContentResolver().registerContentObserver( 310 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED), 311 false, contentObserver); 312 mContext.getContentResolver().registerContentObserver( 313 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL), 314 false, contentObserver); 315 mContext.getContentResolver().registerContentObserver( 316 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_PATTERN), 317 false, contentObserver); 318 } 319 320 /** 321 * DNS based detection techniques do not work at all hotspots. The one sure 322 * way to check a walled garden is to see if a URL fetch on a known address 323 * fetches the data we expect 324 */ 325 private boolean isWalledGardenConnection() { 326 InputStream in = null; 327 HttpURLConnection urlConnection = null; 328 try { 329 URL url = new URL(mWalledGardenUrl); 330 urlConnection = (HttpURLConnection) url.openConnection(); 331 in = new BufferedInputStream(urlConnection.getInputStream()); 332 Scanner scanner = new Scanner(in); 333 if (scanner.findInLine(mWalledGardenPattern) != null) { 334 return false; 335 } else { 336 return true; 337 } 338 } catch (IOException e) { 339 return false; 340 } finally { 341 if (in != null) { 342 try { 343 in.close(); 344 } catch (IOException e) { 345 } 346 } 347 if (urlConnection != null) 348 urlConnection.disconnect(); 349 } 350 } 351 352 private boolean rssiStrengthAboveCutoff(int rssi) { 353 return WifiManager.calculateSignalLevel(rssi, WIFI_SIGNAL_LEVELS) > LOW_SIGNAL_CUTOFF; 354 } 355 356 public void dump(PrintWriter pw) { 357 pw.print("WatchdogStatus: "); 358 pw.print("State " + getCurrentState()); 359 pw.println(", network [" + mInitialConnInfo + "]"); 360 pw.print("checkFailures " + mNumCheckFailures); 361 pw.println(", bssids: " + mBssids); 362 pw.println("lastSingleCheck: " + mOnlineWatchState.lastCheckTime); 363 } 364 365 private boolean isWatchdogEnabled() { 366 return getSettingsBoolean(mContentResolver, Settings.Secure.WIFI_WATCHDOG_ON, true); 367 } 368 369 private void updateSettings() { 370 mDnsCheckShortIntervalMs = Secure.getLong(mContentResolver, 371 Secure.WIFI_WATCHDOG_DNS_CHECK_SHORT_INTERVAL_MS, 372 DEFAULT_DNS_CHECK_SHORT_INTERVAL_MS); 373 mDnsCheckLongIntervalMs = Secure.getLong(mContentResolver, 374 Secure.WIFI_WATCHDOG_DNS_CHECK_LONG_INTERVAL_MS, 375 DEFAULT_DNS_CHECK_LONG_INTERVAL_MS); 376 mMaxSsidBlacklists = Secure.getInt(mContentResolver, 377 Secure.WIFI_WATCHDOG_MAX_SSID_BLACKLISTS, 378 DEFAULT_MAX_SSID_BLACKLISTS); 379 mNumDnsPings = Secure.getInt(mContentResolver, 380 Secure.WIFI_WATCHDOG_NUM_DNS_PINGS, 381 DEFAULT_NUM_DNS_PINGS); 382 mMinDnsResponses = Secure.getInt(mContentResolver, 383 Secure.WIFI_WATCHDOG_MIN_DNS_RESPONSES, 384 DEFAULT_MIN_DNS_RESPONSES); 385 mDnsPingTimeoutMs = Secure.getInt(mContentResolver, 386 Secure.WIFI_WATCHDOG_DNS_PING_TIMEOUT_MS, 387 DEFAULT_DNS_PING_TIMEOUT_MS); 388 mBlacklistFollowupIntervalMs = Secure.getLong(mContentResolver, 389 Settings.Secure.WIFI_WATCHDOG_BLACKLIST_FOLLOWUP_INTERVAL_MS, 390 DEFAULT_BLACKLIST_FOLLOWUP_INTERVAL_MS); 391 mWalledGardenTestEnabled = getSettingsBoolean(mContentResolver, 392 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, true); 393 mWalledGardenUrl = getSettingsStr(mContentResolver, 394 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL, 395 DEFAULT_WALLED_GARDEN_URL); 396 mWalledGardenPattern = Pattern.compile(getSettingsStr(mContentResolver, 397 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_PATTERN, 398 DEFAULT_WALLED_GARDEN_PATTERN)); 399 mWalledGardenIntervalMs = Secure.getLong(mContentResolver, 400 Secure.WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS, 401 DEFAULT_WALLED_GARDEN_INTERVAL_MS); 402 } 403 404 /** 405 * Helper to return wait time left given a min interval and last run 406 * 407 * @param interval minimum wait interval 408 * @param lastTime last time action was performed in 409 * SystemClock.elapsedRealtime(). Null if never. 410 * @return non negative time to wait 411 */ 412 private static long waitTime(long interval, Long lastTime) { 413 if (lastTime == null) 414 return 0; 415 long wait = interval + lastTime - SystemClock.elapsedRealtime(); 416 return wait > 0 ? wait : 0; 417 } 418 419 private static String wifiInfoToStr(WifiInfo wifiInfo) { 420 if (wifiInfo == null) 421 return "null"; 422 return "(" + wifiInfo.getSSID() + ", " + wifiInfo.getBSSID() + ")"; 423 } 424 425 /** 426 * 427 */ 428 private void resetWatchdogState() { 429 mInitialConnInfo = null; 430 mDisableAPNextFailure = false; 431 mLastWalledGardenCheckTime = null; 432 mNumCheckFailures = 0; 433 mBssids.clear(); 434 } 435 436 private void popUpBrowser() { 437 Uri uri = Uri.parse("http://www.google.com"); 438 Intent intent = new Intent(Intent.ACTION_VIEW, uri); 439 intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | 440 Intent.FLAG_ACTIVITY_NEW_TASK); 441 mContext.startActivity(intent); 442 } 443 444 private void displayDisabledNetworkNotification() { 445 Resources r = Resources.getSystem(); 446 CharSequence title = 447 r.getText(com.android.internal.R.string.wifi_watchdog_network_disabled); 448 CharSequence msg = 449 r.getText(com.android.internal.R.string.wifi_watchdog_network_disabled_detailed); 450 451 Notification wifiDisabledWarning = new Notification.Builder(mContext) 452 .setSmallIcon(com.android.internal.R.drawable.stat_sys_warning) 453 .setDefaults(Notification.DEFAULT_ALL) 454 .setTicker(title) 455 .setContentTitle(title) 456 .setContentText(msg) 457 .setContentIntent(PendingIntent.getActivity(mContext, 0, 458 new Intent(Settings.ACTION_WIFI_IP_SETTINGS) 459 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0)) 460 .setWhen(System.currentTimeMillis()) 461 .setAutoCancel(true) 462 .getNotification(); 463 464 NotificationManager notificationManager = (NotificationManager) mContext 465 .getSystemService(Context.NOTIFICATION_SERVICE); 466 467 notificationManager.notify("WifiWatchdog", wifiDisabledWarning.icon, wifiDisabledWarning); 468 } 469 470 class DefaultState extends State { 471 @Override 472 public boolean processMessage(Message msg) { 473 switch (msg.what) { 474 case EVENT_WATCHDOG_SETTINGS_CHANGE: 475 updateSettings(); 476 if (VDBG) { 477 Slog.d(WWSM_TAG, "Updating wifi-watchdog secure settings"); 478 } 479 return HANDLED; 480 } 481 if (VDBG) { 482 Slog.v(WWSM_TAG, "Caught message " + msg.what + " in state " + 483 getCurrentState().getName()); 484 } 485 return HANDLED; 486 } 487 } 488 489 class WatchdogDisabledState extends State { 490 @Override 491 public boolean processMessage(Message msg) { 492 switch (msg.what) { 493 case EVENT_WATCHDOG_TOGGLED: 494 if (isWatchdogEnabled()) 495 transitionTo(mNotConnectedState); 496 return HANDLED; 497 } 498 return NOT_HANDLED; 499 } 500 } 501 502 class WatchdogEnabledState extends State { 503 @Override 504 public void enter() { 505 resetWatchdogState(); 506 mContext.registerReceiver(mBroadcastReceiver, mIntentFilter); 507 Slog.i(WWSM_TAG, "WifiWatchdogService enabled"); 508 } 509 510 @Override 511 public boolean processMessage(Message msg) { 512 switch (msg.what) { 513 case EVENT_WATCHDOG_TOGGLED: 514 if (!isWatchdogEnabled()) 515 transitionTo(mWatchdogDisabledState); 516 return HANDLED; 517 case EVENT_NETWORK_STATE_CHANGE: 518 Intent stateChangeIntent = (Intent) msg.obj; 519 NetworkInfo networkInfo = (NetworkInfo) 520 stateChangeIntent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); 521 522 switch (networkInfo.getState()) { 523 case CONNECTED: 524 // WifiInfo wifiInfo = (WifiInfo) 525 // stateChangeIntent 526 // .getParcelableExtra(WifiManager.EXTRA_WIFI_INFO); 527 // TODO : Replace with above code when API is changed 528 WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); 529 if (wifiInfo == null) { 530 Slog.e(WWSM_TAG, "Connected --> WifiInfo object null!"); 531 return HANDLED; 532 } 533 534 if (wifiInfo.getSSID() == null || wifiInfo.getBSSID() == null) { 535 Slog.e(WWSM_TAG, "Received wifiInfo object with null elts: " 536 + wifiInfoToStr(wifiInfo)); 537 return HANDLED; 538 } 539 540 initConnection(wifiInfo); 541 transitionTo(mDnsCheckingState); 542 mNetEventCounter++; 543 return HANDLED; 544 case DISCONNECTED: 545 case DISCONNECTING: 546 mNetEventCounter++; 547 transitionTo(mNotConnectedState); 548 return HANDLED; 549 } 550 return HANDLED; 551 case EVENT_WIFI_RADIO_STATE_CHANGE: 552 if ((Integer) msg.obj == WifiManager.WIFI_STATE_DISABLING) { 553 Slog.i(WWSM_TAG, "WifiStateDisabling -- Resetting WatchdogState"); 554 resetWatchdogState(); 555 mNetEventCounter++; 556 transitionTo(mNotConnectedState); 557 } 558 return HANDLED; 559 } 560 561 return NOT_HANDLED; 562 } 563 564 /** 565 * @param wifiInfo Info object with non-null ssid and bssid 566 */ 567 private void initConnection(WifiInfo wifiInfo) { 568 if (VDBG) { 569 Slog.v(WWSM_TAG, "Connected:: old " + wifiInfoToStr(mInitialConnInfo) + 570 " ==> new " + wifiInfoToStr(wifiInfo)); 571 } 572 573 if (mInitialConnInfo == null || !wifiInfo.getSSID().equals(mInitialConnInfo.getSSID())) { 574 resetWatchdogState(); 575 } else if (!wifiInfo.getBSSID().equals(mInitialConnInfo.getBSSID())) { 576 mDisableAPNextFailure = false; 577 } 578 mInitialConnInfo = wifiInfo; 579 } 580 581 @Override 582 public void exit() { 583 mContext.unregisterReceiver(mBroadcastReceiver); 584 Slog.i(WWSM_TAG, "WifiWatchdogService disabled"); 585 } 586 } 587 588 class NotConnectedState extends State { 589 } 590 591 class ConnectedState extends State { 592 @Override 593 public boolean processMessage(Message msg) { 594 switch (msg.what) { 595 case EVENT_SCAN_RESULTS_AVAILABLE: 596 String curSsid = mInitialConnInfo.getSSID(); 597 List<ScanResult> results = mWifiManager.getScanResults(); 598 int oldNumBssids = mBssids.size(); 599 600 if (results == null) { 601 if (DBG) { 602 Slog.d(WWSM_TAG, "updateBssids: Got null scan results!"); 603 } 604 return HANDLED; 605 } 606 607 for (ScanResult result : results) { 608 if (result == null || result.SSID == null) { 609 if (VDBG) { 610 Slog.v(WWSM_TAG, "Received invalid scan result: " + result); 611 } 612 continue; 613 } 614 if (curSsid.equals(result.SSID)) 615 mBssids.add(result.BSSID); 616 } 617 return HANDLED; 618 case EVENT_WATCHDOG_SETTINGS_CHANGE: 619 // Stop current checks, but let state update 620 transitionTo(mOnlineWatchState); 621 return NOT_HANDLED; 622 } 623 return NOT_HANDLED; 624 } 625 626 } 627 628 class DnsCheckingState extends State { 629 int dnsCheckTries = 0; 630 int dnsCheckSuccesses = 0; 631 String dnsCheckLogStr = ""; 632 633 @Override 634 public void enter() { 635 dnsCheckSuccesses = 0; 636 dnsCheckTries = 0; 637 if (DBG) { 638 Slog.d(WWSM_TAG, "Starting DNS pings at " + SystemClock.elapsedRealtime()); 639 dnsCheckLogStr = String.format("Pinging %s on ssid [%s]: ", 640 mDnsPinger.getDns(), mInitialConnInfo.getSSID()); 641 } 642 643 sendCheckStepMessage(0); 644 } 645 646 @Override 647 public boolean processMessage(Message msg) { 648 if (msg.what != MESSAGE_CHECK_STEP) { 649 return NOT_HANDLED; 650 } 651 if (msg.arg1 != mNetEventCounter) { 652 Slog.d(WWSM_TAG, "Check step out of sync, ignoring..."); 653 return HANDLED; 654 } 655 656 long pingResponseTime = mDnsPinger.pingDns(mDnsPinger.getDns(), 657 mDnsPingTimeoutMs); 658 659 dnsCheckTries++; 660 if (pingResponseTime >= 0) 661 dnsCheckSuccesses++; 662 663 if (DBG) { 664 if (pingResponseTime >= 0) { 665 dnsCheckLogStr += "|" + pingResponseTime; 666 } else { 667 dnsCheckLogStr += "|x"; 668 } 669 } 670 671 if (VDBG) { 672 Slog.v(WWSM_TAG, dnsCheckLogStr); 673 } 674 675 /** 676 * After a full ping count, if we have more responses than this 677 * cutoff, the outcome is success; else it is 'failure'. 678 */ 679 680 /** 681 * Our final success count will be at least this big, so we're 682 * guaranteed to succeed. 683 */ 684 if (dnsCheckSuccesses >= mMinDnsResponses) { 685 // DNS CHECKS OK, NOW WALLED GARDEN 686 if (DBG) { 687 Slog.d(WWSM_TAG, dnsCheckLogStr + "| SUCCESS"); 688 } 689 690 if (!shouldCheckWalledGarden()) { 691 transitionTo(mOnlineWatchState); 692 return HANDLED; 693 } 694 695 mLastWalledGardenCheckTime = SystemClock.elapsedRealtime(); 696 if (isWalledGardenConnection()) { 697 if (DBG) 698 Slog.d(WWSM_TAG, 699 "Walled garden test complete - walled garden detected"); 700 transitionTo(mWalledGardenState); 701 } else { 702 if (DBG) 703 Slog.d(WWSM_TAG, "Walled garden test complete - online"); 704 transitionTo(mOnlineWatchState); 705 } 706 return HANDLED; 707 } 708 709 /** 710 * Our final count will be at most the current count plus the 711 * remaining pings - we're guaranteed to fail. 712 */ 713 int remainingChecks = mNumDnsPings - dnsCheckTries; 714 if (remainingChecks + dnsCheckSuccesses < mMinDnsResponses) { 715 if (DBG) { 716 Slog.d(WWSM_TAG, dnsCheckLogStr + "| FAILURE"); 717 } 718 transitionTo(mDnsCheckFailureState); 719 return HANDLED; 720 } 721 722 // Still in dns check step 723 sendCheckStepMessage(DNS_PING_INTERVAL_MS); 724 return HANDLED; 725 } 726 727 private boolean shouldCheckWalledGarden() { 728 if (!mWalledGardenTestEnabled) { 729 if (VDBG) 730 Slog.v(WWSM_TAG, "Skipping walled garden check - disabled"); 731 return false; 732 } 733 long waitTime = waitTime(mWalledGardenIntervalMs, 734 mLastWalledGardenCheckTime); 735 if (waitTime > 0) { 736 if (DBG) { 737 Slog.d(WWSM_TAG, "Skipping walled garden check - wait " + 738 waitTime + " ms."); 739 } 740 return false; 741 } 742 return true; 743 } 744 745 private void sendCheckStepMessage(long delay) { 746 sendMessageDelayed(obtainMessage(MESSAGE_CHECK_STEP, mNetEventCounter, 0), delay); 747 } 748 749 } 750 751 class OnlineWatchState extends State { 752 /** 753 * Signals a short-wait message is enqueued for the current 'guard' counter 754 */ 755 boolean unstableSignalChecks = false; 756 757 /** 758 * The signal is unstable. We should enqueue a short-wait check, if one is enqueued 759 * already 760 */ 761 boolean signalUnstable = false; 762 763 /** 764 * A monotonic counter to ensure that at most one check message will be processed from any 765 * set of check messages currently enqueued. Avoids duplicate checks when a low-signal 766 * event is observed. 767 */ 768 int checkGuard = 0; 769 Long lastCheckTime = null; 770 771 @Override 772 public void enter() { 773 lastCheckTime = SystemClock.elapsedRealtime(); 774 signalUnstable = false; 775 checkGuard++; 776 unstableSignalChecks = false; 777 triggerSingleDnsCheck(); 778 } 779 780 @Override 781 public boolean processMessage(Message msg) { 782 switch (msg.what) { 783 case EVENT_RSSI_CHANGE: 784 if (msg.arg1 != mNetEventCounter) { 785 if (DBG) { 786 Slog.d(WWSM_TAG, "Rssi change message out of sync, ignoring"); 787 } 788 return HANDLED; 789 } 790 int newRssi = msg.arg2; 791 signalUnstable = !rssiStrengthAboveCutoff(newRssi); 792 if (VDBG) { 793 Slog.v(WWSM_TAG, "OnlineWatchState:: new rssi " + newRssi + " --> level " + 794 WifiManager.calculateSignalLevel(newRssi, WIFI_SIGNAL_LEVELS)); 795 } 796 797 if (signalUnstable && !unstableSignalChecks) { 798 if (VDBG) { 799 Slog.v(WWSM_TAG, "Sending triggered check msg"); 800 } 801 triggerSingleDnsCheck(); 802 } 803 return HANDLED; 804 case MESSAGE_SINGLE_DNS_CHECK: 805 if (msg.arg1 != checkGuard) { 806 if (VDBG) { 807 Slog.v(WWSM_TAG, "Single check msg out of sync, ignoring."); 808 } 809 return HANDLED; 810 } 811 lastCheckTime = SystemClock.elapsedRealtime(); 812 long responseTime = mDnsPinger.pingDns(mDnsPinger.getDns(), 813 mDnsPingTimeoutMs); 814 if (responseTime >= 0) { 815 if (VDBG) { 816 Slog.v(WWSM_TAG, "Ran a single DNS ping. Response time: " 817 + responseTime); 818 } 819 820 checkGuard++; 821 unstableSignalChecks = false; 822 triggerSingleDnsCheck(); 823 } else { 824 if (DBG) { 825 Slog.d(WWSM_TAG, "Single dns ping failure. Starting full checks."); 826 } 827 transitionTo(mDnsCheckingState); 828 } 829 return HANDLED; 830 } 831 return NOT_HANDLED; 832 } 833 834 /** 835 * Times a dns check with an interval based on {@link #signalUnstable} 836 */ 837 private void triggerSingleDnsCheck() { 838 long waitInterval; 839 if (signalUnstable) { 840 waitInterval = mDnsCheckShortIntervalMs; 841 unstableSignalChecks = true; 842 } else { 843 waitInterval = mDnsCheckLongIntervalMs; 844 } 845 sendMessageDelayed(obtainMessage(MESSAGE_SINGLE_DNS_CHECK, checkGuard, 0), 846 waitTime(waitInterval, lastCheckTime)); 847 } 848 } 849 850 class DnsCheckFailureState extends State { 851 @Override 852 public void enter() { 853 mNumCheckFailures++; 854 obtainMessage(MESSAGE_HANDLE_BAD_AP, mNetEventCounter, 0).sendToTarget(); 855 } 856 857 @Override 858 public boolean processMessage(Message msg) { 859 if (msg.what != MESSAGE_HANDLE_BAD_AP) { 860 return NOT_HANDLED; 861 } 862 863 if (msg.arg1 != mNetEventCounter) { 864 if (VDBG) { 865 Slog.v(WWSM_TAG, "Msg out of sync, ignoring..."); 866 } 867 return HANDLED; 868 } 869 870 if (mDisableAPNextFailure || mNumCheckFailures >= mMaxSsidBlacklists) { 871 // TODO : Unban networks if they had low signal ? 872 Slog.i(WWSM_TAG, "Disabling current SSID " + wifiInfoToStr(mInitialConnInfo) 873 + ". " + "numCheckFailures " + mNumCheckFailures 874 + ", numAPs " + mBssids.size()); 875 mWifiManager.disableNetwork(mInitialConnInfo.getNetworkId()); 876 if (mShowDisabledNotification) { 877 displayDisabledNetworkNotification(); 878 mShowDisabledNotification = false; 879 putSettingsBoolean(mContentResolver, 880 Settings.Secure.WIFI_WATCHDOG_SHOW_DISABLED_NETWORK_POPUP, false); 881 } 882 transitionTo(mNotConnectedState); 883 } else { 884 Slog.i(WWSM_TAG, "Blacklisting current BSSID. " + wifiInfoToStr(mInitialConnInfo) 885 + "numCheckFailures " + mNumCheckFailures + ", numAPs " + mBssids.size()); 886 887 mWifiManager.addToBlacklist(mInitialConnInfo.getBSSID()); 888 mWifiManager.reassociate(); 889 transitionTo(mBlacklistedApState); 890 } 891 return HANDLED; 892 } 893 } 894 895 class WalledGardenState extends State { 896 @Override 897 public void enter() { 898 obtainMessage(MESSAGE_HANDLE_WALLED_GARDEN, mNetEventCounter, 0).sendToTarget(); 899 } 900 901 @Override 902 public boolean processMessage(Message msg) { 903 if (msg.what != MESSAGE_HANDLE_WALLED_GARDEN) { 904 return NOT_HANDLED; 905 } 906 907 if (msg.arg1 != mNetEventCounter) { 908 if (VDBG) { 909 Slog.v(WWSM_TAG, "WalledGardenState::Msg out of sync, ignoring..."); 910 } 911 return HANDLED; 912 } 913 popUpBrowser(); 914 transitionTo(mOnlineWatchState); 915 return HANDLED; 916 } 917 } 918 919 class BlacklistedApState extends State { 920 @Override 921 public void enter() { 922 mDisableAPNextFailure = true; 923 sendMessageDelayed(obtainMessage(MESSAGE_NETWORK_FOLLOWUP, mNetEventCounter, 0), 924 mBlacklistFollowupIntervalMs); 925 } 926 927 @Override 928 public boolean processMessage(Message msg) { 929 if (msg.what != MESSAGE_NETWORK_FOLLOWUP) { 930 return NOT_HANDLED; 931 } 932 933 if (msg.arg1 != mNetEventCounter) { 934 if (VDBG) { 935 Slog.v(WWSM_TAG, "BlacklistedApState::Msg out of sync, ignoring..."); 936 } 937 return HANDLED; 938 } 939 940 transitionTo(mDnsCheckingState); 941 return HANDLED; 942 } 943 } 944 945 946 /** 947 * Convenience function for retrieving a single secure settings value 948 * as a string with a default value. 949 * 950 * @param cr The ContentResolver to access. 951 * @param name The name of the setting to retrieve. 952 * @param def Value to return if the setting is not defined. 953 * 954 * @return The setting's current value, or 'def' if it is not defined 955 */ 956 private static String getSettingsStr(ContentResolver cr, String name, String def) { 957 String v = Settings.Secure.getString(cr, name); 958 return v != null ? v : def; 959 } 960 961 /** 962 * Convenience function for retrieving a single secure settings value 963 * as a boolean. Note that internally setting values are always 964 * stored as strings; this function converts the string to a boolean 965 * for you. The default value will be returned if the setting is 966 * not defined or not a valid boolean. 967 * 968 * @param cr The ContentResolver to access. 969 * @param name The name of the setting to retrieve. 970 * @param def Value to return if the setting is not defined. 971 * 972 * @return The setting's current value, or 'def' if it is not defined 973 * or not a valid boolean. 974 */ 975 private static boolean getSettingsBoolean(ContentResolver cr, String name, boolean def) { 976 return Settings.Secure.getInt(cr, name, def ? 1 : 0) == 1; 977 } 978 979 /** 980 * Convenience function for updating a single settings value as an 981 * integer. This will either create a new entry in the table if the 982 * given name does not exist, or modify the value of the existing row 983 * with that name. Note that internally setting values are always 984 * stored as strings, so this function converts the given value to a 985 * string before storing it. 986 * 987 * @param cr The ContentResolver to access. 988 * @param name The name of the setting to modify. 989 * @param value The new value for the setting. 990 * @return true if the value was set, false on database errors 991 */ 992 private static boolean putSettingsBoolean(ContentResolver cr, String name, boolean value) { 993 return Settings.Secure.putInt(cr, name, value ? 1 : 0); 994 } 995 996 997} 998