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