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