WifiWatchdogStateMachine.java revision 90d57dfac3113247e2d38a2235254fc35d12856a
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.arp.ArpPeer; 30import android.net.ConnectivityManager; 31import android.net.LinkAddress; 32import android.net.LinkProperties; 33import android.net.NetworkInfo; 34import android.net.RouteInfo; 35import android.net.Uri; 36import android.os.Message; 37import android.os.SystemClock; 38import android.os.SystemProperties; 39import android.provider.Settings; 40import android.provider.Settings.Secure; 41import android.util.Log; 42 43import com.android.internal.R; 44import com.android.internal.util.AsyncChannel; 45import com.android.internal.util.Protocol; 46import com.android.internal.util.State; 47import com.android.internal.util.StateMachine; 48 49import java.io.IOException; 50import java.io.PrintWriter; 51import java.net.HttpURLConnection; 52import java.net.InetAddress; 53import java.net.SocketException; 54import java.net.URL; 55 56/** 57 * WifiWatchdogStateMachine monitors the connection to a Wi-Fi 58 * network. After the framework notifies that it has connected to an 59 * acccess point and is waiting for link to be verified, the watchdog 60 * takes over and verifies if the link is good by doing ARP pings to 61 * the gateway using {@link ArpPeer}. 62 * 63 * Upon successful verification, the watchdog notifies and continues 64 * to monitor the link afterwards when the RSSI level falls below 65 * a certain threshold. 66 67 * When Wi-fi connects at L2 layer, the beacons from access point reach 68 * the device and it can maintain a connection, but the application 69 * connectivity can be flaky (due to bigger packet size exchange). 70 * 71 * We now monitor the quality of the last hop on 72 * Wi-Fi using signal strength and ARP connectivity as indicators 73 * to decide if the link is good enough to switch to Wi-Fi as the uplink. 74 * 75 * ARP pings are useful for link validation but can still get through 76 * when the application traffic fails to go through and are thus not 77 * the best indicator of real packet loss since they are tiny packets 78 * (28 bytes) and have a much low chance of packet corruption than the 79 * regular data packets. 80 * 81 * When signal strength and ARP are used together, it ends up working well in tests. 82 * The goal is to switch to Wi-Fi after validating ARP transfer 83 * and RSSI and then switching out of Wi-Fi when we hit a low 84 * signal strength threshold and then waiting until the signal strength 85 * improves and validating ARP transfer. 86 * 87 * @hide 88 */ 89public class WifiWatchdogStateMachine extends StateMachine { 90 91 /* STOPSHIP: Keep this configurable for debugging until ship */ 92 private static boolean DBG = false; 93 private static final String TAG = "WifiWatchdogStateMachine"; 94 private static final String WALLED_GARDEN_NOTIFICATION_ID = "WifiWatchdog.walledgarden"; 95 96 /* RSSI Levels as used by notification icon 97 Level 4 -55 <= RSSI 98 Level 3 -66 <= RSSI < -55 99 Level 2 -77 <= RSSI < -67 100 Level 1 -88 <= RSSI < -78 101 Level 0 RSSI < -88 */ 102 103 /* Wi-fi connection is considered poor below this 104 RSSI level threshold and the watchdog report it 105 to the WifiStateMachine */ 106 private static final int RSSI_LEVEL_CUTOFF = 0; 107 /* Wi-fi connection is monitored actively below this 108 threshold */ 109 private static final int RSSI_LEVEL_MONITOR = 1; 110 /* RSSI threshold during monitoring below which network is avoided */ 111 private static final int RSSI_MONITOR_THRESHOLD = -84; 112 /* Number of times RSSI is measured to be low before being avoided */ 113 private static final int RSSI_MONITOR_COUNT = 5; 114 private int mRssiMonitorCount = 0; 115 116 /* Avoid flapping. The interval is changed over time as long as we continue to avoid 117 * under the max interval after which we reset the interval again */ 118 private static final int MIN_INTERVAL_AVOID_BSSID_MS[] = {0, 30 * 1000, 60 * 1000, 119 5 * 60 * 1000, 30 * 60 * 1000}; 120 /* Index into the interval array MIN_INTERVAL_AVOID_BSSID_MS */ 121 private int mMinIntervalArrayIndex = 0; 122 123 private long mLastBssidAvoidedTime; 124 125 private int mCurrentSignalLevel; 126 127 private static final long DEFAULT_ARP_CHECK_INTERVAL_MS = 2 * 60 * 1000; 128 private static final long DEFAULT_RSSI_FETCH_INTERVAL_MS = 1000; 129 private static final long DEFAULT_WALLED_GARDEN_INTERVAL_MS = 30 * 60 * 1000; 130 131 private static final int DEFAULT_NUM_ARP_PINGS = 5; 132 private static final int DEFAULT_MIN_ARP_RESPONSES = 1; 133 134 private static final int DEFAULT_ARP_PING_TIMEOUT_MS = 100; 135 136 // See http://go/clientsdns for usage approval 137 private static final String DEFAULT_WALLED_GARDEN_URL = 138 "http://clients3.google.com/generate_204"; 139 private static final int WALLED_GARDEN_SOCKET_TIMEOUT_MS = 10000; 140 141 /* Some carrier apps might have support captive portal handling. Add some delay to allow 142 app authentication to be done before our test. 143 TODO: This should go away once we provide an API to apps to disable walled garden test 144 for certain SSIDs 145 */ 146 private static final int WALLED_GARDEN_START_DELAY_MS = 3000; 147 148 private static final int BASE = Protocol.BASE_WIFI_WATCHDOG; 149 150 /** 151 * Indicates the enable setting of WWS may have changed 152 */ 153 private static final int EVENT_WATCHDOG_TOGGLED = BASE + 1; 154 155 /** 156 * Indicates the wifi network state has changed. Passed w/ original intent 157 * which has a non-null networkInfo object 158 */ 159 private static final int EVENT_NETWORK_STATE_CHANGE = BASE + 2; 160 /* Passed with RSSI information */ 161 private static final int EVENT_RSSI_CHANGE = BASE + 3; 162 private static final int EVENT_WIFI_RADIO_STATE_CHANGE = BASE + 5; 163 private static final int EVENT_WATCHDOG_SETTINGS_CHANGE = BASE + 6; 164 165 /* Internal messages */ 166 private static final int CMD_ARP_CHECK = BASE + 11; 167 private static final int CMD_DELAYED_WALLED_GARDEN_CHECK = BASE + 12; 168 private static final int CMD_RSSI_FETCH = BASE + 13; 169 170 /* Notifications to WifiStateMachine */ 171 static final int POOR_LINK_DETECTED = BASE + 21; 172 static final int GOOD_LINK_DETECTED = BASE + 22; 173 static final int RSSI_FETCH = BASE + 23; 174 static final int RSSI_FETCH_SUCCEEDED = BASE + 24; 175 static final int RSSI_FETCH_FAILED = BASE + 25; 176 177 private static final int SINGLE_ARP_CHECK = 0; 178 private static final int FULL_ARP_CHECK = 1; 179 180 private Context mContext; 181 private ContentResolver mContentResolver; 182 private WifiManager mWifiManager; 183 private IntentFilter mIntentFilter; 184 private BroadcastReceiver mBroadcastReceiver; 185 private AsyncChannel mWsmChannel = new AsyncChannel();; 186 187 private DefaultState mDefaultState = new DefaultState(); 188 private WatchdogDisabledState mWatchdogDisabledState = new WatchdogDisabledState(); 189 private WatchdogEnabledState mWatchdogEnabledState = new WatchdogEnabledState(); 190 private NotConnectedState mNotConnectedState = new NotConnectedState(); 191 private VerifyingLinkState mVerifyingLinkState = new VerifyingLinkState(); 192 private ConnectedState mConnectedState = new ConnectedState(); 193 private WalledGardenCheckState mWalledGardenCheckState = new WalledGardenCheckState(); 194 /* Online and watching link connectivity */ 195 private OnlineWatchState mOnlineWatchState = new OnlineWatchState(); 196 /* RSSI level is at RSSI_LEVEL_MONITOR and needs close monitoring */ 197 private RssiMonitoringState mRssiMonitoringState = new RssiMonitoringState(); 198 /* Online and doing nothing */ 199 private OnlineState mOnlineState = new OnlineState(); 200 201 private int mArpToken = 0; 202 private long mArpCheckIntervalMs; 203 private int mRssiFetchToken = 0; 204 private long mRssiFetchIntervalMs; 205 private long mWalledGardenIntervalMs; 206 private int mNumArpPings; 207 private int mMinArpResponses; 208 private int mArpPingTimeoutMs; 209 private boolean mPoorNetworkDetectionEnabled; 210 private boolean mWalledGardenTestEnabled; 211 private String mWalledGardenUrl; 212 213 private WifiInfo mWifiInfo; 214 private LinkProperties mLinkProperties; 215 216 private long mLastWalledGardenCheckTime = 0; 217 218 private static boolean sWifiOnly = false; 219 private boolean mWalledGardenNotificationShown; 220 221 /** 222 * STATE MAP 223 * Default 224 * / \ 225 * Disabled Enabled 226 * / \ \ 227 * NotConnected Verifying Connected 228 * /---------\ 229 * (all other states) 230 */ 231 private WifiWatchdogStateMachine(Context context) { 232 super(TAG); 233 mContext = context; 234 mContentResolver = context.getContentResolver(); 235 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 236 mWsmChannel.connectSync(mContext, getHandler(), 237 mWifiManager.getWifiStateMachineMessenger()); 238 239 setupNetworkReceiver(); 240 241 // The content observer to listen needs a handler 242 registerForSettingsChanges(); 243 registerForWatchdogToggle(); 244 addState(mDefaultState); 245 addState(mWatchdogDisabledState, mDefaultState); 246 addState(mWatchdogEnabledState, mDefaultState); 247 addState(mNotConnectedState, mWatchdogEnabledState); 248 addState(mVerifyingLinkState, mWatchdogEnabledState); 249 addState(mConnectedState, mWatchdogEnabledState); 250 addState(mWalledGardenCheckState, mConnectedState); 251 addState(mOnlineWatchState, mConnectedState); 252 addState(mRssiMonitoringState, mOnlineWatchState); 253 addState(mOnlineState, mConnectedState); 254 255 if (isWatchdogEnabled()) { 256 setInitialState(mNotConnectedState); 257 } else { 258 setInitialState(mWatchdogDisabledState); 259 } 260 updateSettings(); 261 } 262 263 public static WifiWatchdogStateMachine makeWifiWatchdogStateMachine(Context context) { 264 ContentResolver contentResolver = context.getContentResolver(); 265 266 ConnectivityManager cm = (ConnectivityManager) context.getSystemService( 267 Context.CONNECTIVITY_SERVICE); 268 sWifiOnly = (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false); 269 270 // Disable for wifi only devices. 271 if (Settings.Secure.getString(contentResolver, Settings.Secure.WIFI_WATCHDOG_ON) == null 272 && sWifiOnly) { 273 log("Disabling watchog for wi-fi only device"); 274 putSettingsBoolean(contentResolver, Settings.Secure.WIFI_WATCHDOG_ON, false); 275 } 276 WifiWatchdogStateMachine wwsm = new WifiWatchdogStateMachine(context); 277 wwsm.start(); 278 return wwsm; 279 } 280 281 private void setupNetworkReceiver() { 282 mBroadcastReceiver = new BroadcastReceiver() { 283 @Override 284 public void onReceive(Context context, Intent intent) { 285 String action = intent.getAction(); 286 if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { 287 sendMessage(EVENT_NETWORK_STATE_CHANGE, intent); 288 } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) { 289 obtainMessage(EVENT_RSSI_CHANGE, 290 intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200), 0).sendToTarget(); 291 } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { 292 sendMessage(EVENT_WIFI_RADIO_STATE_CHANGE, 293 intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 294 WifiManager.WIFI_STATE_UNKNOWN)); 295 } 296 } 297 }; 298 299 mIntentFilter = new IntentFilter(); 300 mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); 301 mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); 302 mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION); 303 mContext.registerReceiver(mBroadcastReceiver, mIntentFilter); 304 } 305 306 /** 307 * Observes the watchdog on/off setting, and takes action when changed. 308 */ 309 private void registerForWatchdogToggle() { 310 ContentObserver contentObserver = new ContentObserver(this.getHandler()) { 311 @Override 312 public void onChange(boolean selfChange) { 313 sendMessage(EVENT_WATCHDOG_TOGGLED); 314 } 315 }; 316 317 mContext.getContentResolver().registerContentObserver( 318 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON), 319 false, contentObserver); 320 } 321 322 /** 323 * Observes watchdogs secure setting changes. 324 */ 325 private void registerForSettingsChanges() { 326 ContentObserver contentObserver = new ContentObserver(this.getHandler()) { 327 @Override 328 public void onChange(boolean selfChange) { 329 sendMessage(EVENT_WATCHDOG_SETTINGS_CHANGE); 330 } 331 }; 332 333 mContext.getContentResolver().registerContentObserver( 334 Settings.Secure.getUriFor( 335 Settings.Secure.WIFI_WATCHDOG_ARP_CHECK_INTERVAL_MS), 336 false, contentObserver); 337 mContext.getContentResolver().registerContentObserver( 338 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS), 339 false, contentObserver); 340 mContext.getContentResolver().registerContentObserver( 341 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_NUM_ARP_PINGS), 342 false, contentObserver); 343 mContext.getContentResolver().registerContentObserver( 344 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_MIN_ARP_RESPONSES), 345 false, contentObserver); 346 mContext.getContentResolver().registerContentObserver( 347 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ARP_PING_TIMEOUT_MS), 348 false, contentObserver); 349 mContext.getContentResolver().registerContentObserver( 350 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED), 351 false, contentObserver); 352 mContext.getContentResolver().registerContentObserver( 353 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED), 354 false, contentObserver); 355 mContext.getContentResolver().registerContentObserver( 356 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL), 357 false, contentObserver); 358 } 359 360 /** 361 * DNS based detection techniques do not work at all hotspots. The one sure 362 * way to check a walled garden is to see if a URL fetch on a known address 363 * fetches the data we expect 364 */ 365 private boolean isWalledGardenConnection() { 366 HttpURLConnection urlConnection = null; 367 try { 368 URL url = new URL(mWalledGardenUrl); 369 urlConnection = (HttpURLConnection) url.openConnection(); 370 urlConnection.setInstanceFollowRedirects(false); 371 urlConnection.setConnectTimeout(WALLED_GARDEN_SOCKET_TIMEOUT_MS); 372 urlConnection.setReadTimeout(WALLED_GARDEN_SOCKET_TIMEOUT_MS); 373 urlConnection.setUseCaches(false); 374 urlConnection.getInputStream(); 375 // We got a valid response, but not from the real google 376 return urlConnection.getResponseCode() != 204; 377 } catch (IOException e) { 378 if (DBG) { 379 log("Walled garden check - probably not a portal: exception " + e); 380 } 381 return false; 382 } finally { 383 if (urlConnection != null) { 384 urlConnection.disconnect(); 385 } 386 } 387 } 388 389 public void dump(PrintWriter pw) { 390 pw.print("WatchdogStatus: "); 391 pw.print("State: " + getCurrentState()); 392 pw.println("mWifiInfo: [" + mWifiInfo + "]"); 393 pw.println("mLinkProperties: [" + mLinkProperties + "]"); 394 pw.println("mCurrentSignalLevel: [" + mCurrentSignalLevel + "]"); 395 pw.println("mArpCheckIntervalMs: [" + mArpCheckIntervalMs+ "]"); 396 pw.println("mRssiFetchIntervalMs: [" + mRssiFetchIntervalMs + "]"); 397 pw.println("mWalledGardenIntervalMs: [" + mWalledGardenIntervalMs + "]"); 398 pw.println("mNumArpPings: [" + mNumArpPings + "]"); 399 pw.println("mMinArpResponses: [" + mMinArpResponses + "]"); 400 pw.println("mArpPingTimeoutMs: [" + mArpPingTimeoutMs + "]"); 401 pw.println("mPoorNetworkDetectionEnabled: [" + mPoorNetworkDetectionEnabled + "]"); 402 pw.println("mWalledGardenTestEnabled: [" + mWalledGardenTestEnabled + "]"); 403 pw.println("mWalledGardenUrl: [" + mWalledGardenUrl + "]"); 404 } 405 406 private boolean isWatchdogEnabled() { 407 boolean ret = getSettingsBoolean(mContentResolver, Settings.Secure.WIFI_WATCHDOG_ON, true); 408 if (DBG) log("watchdog enabled " + ret); 409 return ret; 410 } 411 412 private void updateSettings() { 413 if (DBG) log("Updating secure settings"); 414 415 mArpCheckIntervalMs = Secure.getLong(mContentResolver, 416 Secure.WIFI_WATCHDOG_ARP_CHECK_INTERVAL_MS, 417 DEFAULT_ARP_CHECK_INTERVAL_MS); 418 mRssiFetchIntervalMs = Secure.getLong(mContentResolver, 419 Secure.WIFI_WATCHDOG_RSSI_FETCH_INTERVAL_MS, 420 DEFAULT_RSSI_FETCH_INTERVAL_MS); 421 mNumArpPings = Secure.getInt(mContentResolver, 422 Secure.WIFI_WATCHDOG_NUM_ARP_PINGS, 423 DEFAULT_NUM_ARP_PINGS); 424 mMinArpResponses = Secure.getInt(mContentResolver, 425 Secure.WIFI_WATCHDOG_MIN_ARP_RESPONSES, 426 DEFAULT_MIN_ARP_RESPONSES); 427 mArpPingTimeoutMs = Secure.getInt(mContentResolver, 428 Secure.WIFI_WATCHDOG_ARP_PING_TIMEOUT_MS, 429 DEFAULT_ARP_PING_TIMEOUT_MS); 430 mPoorNetworkDetectionEnabled = getSettingsBoolean(mContentResolver, 431 Settings.Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED, true); 432 mWalledGardenTestEnabled = getSettingsBoolean(mContentResolver, 433 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, true); 434 mWalledGardenUrl = getSettingsStr(mContentResolver, 435 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL, 436 DEFAULT_WALLED_GARDEN_URL); 437 mWalledGardenIntervalMs = Secure.getLong(mContentResolver, 438 Secure.WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS, 439 DEFAULT_WALLED_GARDEN_INTERVAL_MS); 440 } 441 442 private void setWalledGardenNotificationVisible(boolean visible) { 443 // If it should be hidden and it is already hidden, then noop 444 if (!visible && !mWalledGardenNotificationShown) { 445 return; 446 } 447 448 Resources r = Resources.getSystem(); 449 NotificationManager notificationManager = (NotificationManager) mContext 450 .getSystemService(Context.NOTIFICATION_SERVICE); 451 452 if (visible) { 453 Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(mWalledGardenUrl)); 454 intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK); 455 456 CharSequence title = r.getString(R.string.wifi_available_sign_in, 0); 457 CharSequence details = r.getString(R.string.wifi_available_sign_in_detailed, 458 mWifiInfo.getSSID()); 459 460 Notification notification = new Notification(); 461 notification.when = 0; 462 notification.icon = com.android.internal.R.drawable.stat_notify_wifi_in_range; 463 notification.flags = Notification.FLAG_AUTO_CANCEL; 464 notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0); 465 notification.tickerText = title; 466 notification.setLatestEventInfo(mContext, title, details, notification.contentIntent); 467 468 notificationManager.notify(WALLED_GARDEN_NOTIFICATION_ID, 1, notification); 469 } else { 470 notificationManager.cancel(WALLED_GARDEN_NOTIFICATION_ID, 1); 471 } 472 mWalledGardenNotificationShown = visible; 473 } 474 475 class DefaultState extends State { 476 @Override 477 public void enter() { 478 if (DBG) log(getName() + "\n"); 479 } 480 481 @Override 482 public boolean processMessage(Message msg) { 483 switch (msg.what) { 484 case EVENT_WATCHDOG_SETTINGS_CHANGE: 485 updateSettings(); 486 if (DBG) { 487 log("Updating wifi-watchdog secure settings"); 488 } 489 break; 490 case EVENT_RSSI_CHANGE: 491 mCurrentSignalLevel = calculateSignalLevel(msg.arg1); 492 break; 493 case EVENT_WIFI_RADIO_STATE_CHANGE: 494 case EVENT_NETWORK_STATE_CHANGE: 495 case CMD_ARP_CHECK: 496 case CMD_DELAYED_WALLED_GARDEN_CHECK: 497 case CMD_RSSI_FETCH: 498 case RSSI_FETCH_SUCCEEDED: 499 case RSSI_FETCH_FAILED: 500 //ignore 501 break; 502 default: 503 log("Unhandled message " + msg + " in state " + getCurrentState().getName()); 504 break; 505 } 506 return HANDLED; 507 } 508 } 509 510 class WatchdogDisabledState extends State { 511 @Override 512 public void enter() { 513 if (DBG) log(getName() + "\n"); 514 } 515 516 @Override 517 public boolean processMessage(Message msg) { 518 switch (msg.what) { 519 case EVENT_WATCHDOG_TOGGLED: 520 if (isWatchdogEnabled()) 521 transitionTo(mNotConnectedState); 522 return HANDLED; 523 case EVENT_NETWORK_STATE_CHANGE: 524 Intent intent = (Intent) msg.obj; 525 NetworkInfo networkInfo = (NetworkInfo) 526 intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); 527 528 switch (networkInfo.getDetailedState()) { 529 case VERIFYING_POOR_LINK: 530 if (DBG) log("Watchdog disabled, verify link"); 531 mWsmChannel.sendMessage(GOOD_LINK_DETECTED); 532 break; 533 default: 534 break; 535 } 536 break; 537 } 538 return NOT_HANDLED; 539 } 540 } 541 542 class WatchdogEnabledState extends State { 543 @Override 544 public void enter() { 545 if (DBG) log("WifiWatchdogService enabled"); 546 } 547 548 @Override 549 public boolean processMessage(Message msg) { 550 switch (msg.what) { 551 case EVENT_WATCHDOG_TOGGLED: 552 if (!isWatchdogEnabled()) 553 transitionTo(mWatchdogDisabledState); 554 break; 555 case EVENT_NETWORK_STATE_CHANGE: 556 Intent intent = (Intent) msg.obj; 557 NetworkInfo networkInfo = (NetworkInfo) 558 intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); 559 560 if (DBG) log("network state change " + networkInfo.getDetailedState()); 561 562 switch (networkInfo.getDetailedState()) { 563 case VERIFYING_POOR_LINK: 564 mLinkProperties = (LinkProperties) intent.getParcelableExtra( 565 WifiManager.EXTRA_LINK_PROPERTIES); 566 mWifiInfo = (WifiInfo) intent.getParcelableExtra( 567 WifiManager.EXTRA_WIFI_INFO); 568 if (mPoorNetworkDetectionEnabled) { 569 if (mWifiInfo == null) { 570 log("Ignoring link verification, mWifiInfo is NULL"); 571 mWsmChannel.sendMessage(GOOD_LINK_DETECTED); 572 } else { 573 transitionTo(mVerifyingLinkState); 574 } 575 } else { 576 mWsmChannel.sendMessage(GOOD_LINK_DETECTED); 577 } 578 break; 579 case CONNECTED: 580 if (shouldCheckWalledGarden()) { 581 transitionTo(mWalledGardenCheckState); 582 } else { 583 transitionTo(mOnlineWatchState); 584 } 585 break; 586 default: 587 transitionTo(mNotConnectedState); 588 break; 589 } 590 break; 591 case EVENT_WIFI_RADIO_STATE_CHANGE: 592 if ((Integer) msg.obj == WifiManager.WIFI_STATE_DISABLING) { 593 if (DBG) log("WifiStateDisabling -- Resetting WatchdogState"); 594 transitionTo(mNotConnectedState); 595 } 596 break; 597 default: 598 return NOT_HANDLED; 599 } 600 601 setWalledGardenNotificationVisible(false); 602 return HANDLED; 603 } 604 605 @Override 606 public void exit() { 607 if (DBG) log("WifiWatchdogService disabled"); 608 } 609 } 610 611 class NotConnectedState extends State { 612 @Override 613 public void enter() { 614 if (DBG) log(getName() + "\n"); 615 } 616 } 617 618 class VerifyingLinkState extends State { 619 @Override 620 public void enter() { 621 if (DBG) log(getName() + "\n"); 622 //Treat entry as an rssi change 623 handleRssiChange(); 624 } 625 626 private void handleRssiChange() { 627 if (mCurrentSignalLevel <= RSSI_LEVEL_MONITOR) { 628 //stay here 629 if (DBG) log("enter VerifyingLinkState, stay level: " + mCurrentSignalLevel); 630 } else { 631 if (DBG) log("enter VerifyingLinkState, arp check level: " + mCurrentSignalLevel); 632 sendMessage(obtainMessage(CMD_ARP_CHECK, ++mArpToken, 0)); 633 } 634 } 635 636 @Override 637 public boolean processMessage(Message msg) { 638 switch (msg.what) { 639 case EVENT_WATCHDOG_SETTINGS_CHANGE: 640 updateSettings(); 641 if (!mPoorNetworkDetectionEnabled) { 642 mWsmChannel.sendMessage(GOOD_LINK_DETECTED); 643 } 644 break; 645 case EVENT_RSSI_CHANGE: 646 mCurrentSignalLevel = calculateSignalLevel(msg.arg1); 647 handleRssiChange(); 648 break; 649 case CMD_ARP_CHECK: 650 if (msg.arg1 == mArpToken) { 651 if (doArpTest(FULL_ARP_CHECK) == true) { 652 if (DBG) log("Notify link is good " + mCurrentSignalLevel); 653 mWsmChannel.sendMessage(GOOD_LINK_DETECTED); 654 } else { 655 if (DBG) log("Continue ARP check, rssi level: " + mCurrentSignalLevel); 656 sendMessageDelayed(obtainMessage(CMD_ARP_CHECK, ++mArpToken, 0), 657 mArpCheckIntervalMs); 658 } 659 } 660 break; 661 default: 662 return NOT_HANDLED; 663 } 664 return HANDLED; 665 } 666 } 667 668 class ConnectedState extends State { 669 @Override 670 public void enter() { 671 if (DBG) log(getName() + "\n"); 672 } 673 @Override 674 public boolean processMessage(Message msg) { 675 switch (msg.what) { 676 case EVENT_WATCHDOG_SETTINGS_CHANGE: 677 updateSettings(); 678 //STOPSHIP: Remove this at ship 679 DBG = true; 680 if (DBG) log("Updated secure settings and turned debug on"); 681 682 if (mPoorNetworkDetectionEnabled) { 683 transitionTo(mOnlineWatchState); 684 } else { 685 transitionTo(mOnlineState); 686 } 687 return HANDLED; 688 } 689 return NOT_HANDLED; 690 } 691 } 692 693 class WalledGardenCheckState extends State { 694 private int mWalledGardenToken = 0; 695 @Override 696 public void enter() { 697 if (DBG) log(getName() + "\n"); 698 sendMessageDelayed(obtainMessage(CMD_DELAYED_WALLED_GARDEN_CHECK, 699 ++mWalledGardenToken, 0), WALLED_GARDEN_START_DELAY_MS); 700 } 701 702 @Override 703 public boolean processMessage(Message msg) { 704 switch (msg.what) { 705 case CMD_DELAYED_WALLED_GARDEN_CHECK: 706 if (msg.arg1 == mWalledGardenToken) { 707 mLastWalledGardenCheckTime = SystemClock.elapsedRealtime(); 708 if (isWalledGardenConnection()) { 709 if (DBG) log("Walled garden detected"); 710 setWalledGardenNotificationVisible(true); 711 } 712 transitionTo(mOnlineWatchState); 713 } 714 break; 715 default: 716 return NOT_HANDLED; 717 } 718 return HANDLED; 719 } 720 } 721 722 class OnlineWatchState extends State { 723 public void enter() { 724 if (DBG) log(getName() + "\n"); 725 if (mPoorNetworkDetectionEnabled) { 726 //Treat entry as an rssi change 727 handleRssiChange(); 728 } else { 729 transitionTo(mOnlineState); 730 } 731 } 732 733 private void handleRssiChange() { 734 if (mCurrentSignalLevel <= RSSI_LEVEL_CUTOFF) { 735 sendPoorLinkDetected(); 736 } else if (mCurrentSignalLevel <= RSSI_LEVEL_MONITOR) { 737 transitionTo(mRssiMonitoringState); 738 } else { 739 //stay here 740 } 741 } 742 743 @Override 744 public boolean processMessage(Message msg) { 745 switch (msg.what) { 746 case EVENT_RSSI_CHANGE: 747 mCurrentSignalLevel = calculateSignalLevel(msg.arg1); 748 //Ready to avoid bssid again ? 749 long time = android.os.SystemClock.elapsedRealtime(); 750 if (time - mLastBssidAvoidedTime > MIN_INTERVAL_AVOID_BSSID_MS[ 751 mMinIntervalArrayIndex]) { 752 handleRssiChange(); 753 } else { 754 if (DBG) log("Early to avoid " + mWifiInfo + " time: " + time + 755 " last avoided: " + mLastBssidAvoidedTime + 756 " mMinIntervalArrayIndex: " + mMinIntervalArrayIndex); 757 } 758 break; 759 default: 760 return NOT_HANDLED; 761 } 762 return HANDLED; 763 } 764 } 765 766 class RssiMonitoringState extends State { 767 public void enter() { 768 if (DBG) log(getName() + "\n"); 769 sendMessage(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0)); 770 } 771 772 public boolean processMessage(Message msg) { 773 switch (msg.what) { 774 case EVENT_RSSI_CHANGE: 775 mCurrentSignalLevel = calculateSignalLevel(msg.arg1); 776 if (mCurrentSignalLevel <= RSSI_LEVEL_CUTOFF) { 777 sendPoorLinkDetected(); 778 } else if (mCurrentSignalLevel <= RSSI_LEVEL_MONITOR) { 779 //stay here; 780 } else { 781 //We dont need frequent RSSI monitoring any more 782 transitionTo(mOnlineWatchState); 783 } 784 break; 785 case CMD_RSSI_FETCH: 786 if (msg.arg1 == mRssiFetchToken) { 787 mWsmChannel.sendMessage(RSSI_FETCH); 788 sendMessageDelayed(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0), 789 mRssiFetchIntervalMs); 790 } 791 break; 792 case RSSI_FETCH_SUCCEEDED: 793 int rssi = msg.arg1; 794 if (DBG) log("RSSI_FETCH_SUCCEEDED: " + rssi); 795 if (msg.arg1 < RSSI_MONITOR_THRESHOLD) { 796 mRssiMonitorCount++; 797 } else { 798 mRssiMonitorCount = 0; 799 } 800 801 if (mRssiMonitorCount > RSSI_MONITOR_COUNT) { 802 sendPoorLinkDetected(); 803 ++mRssiFetchToken; 804 } 805 break; 806 case RSSI_FETCH_FAILED: 807 //can happen if we are waiting to get a disconnect notification 808 if (DBG) log("RSSI_FETCH_FAILED"); 809 break; 810 default: 811 return NOT_HANDLED; 812 } 813 return HANDLED; 814 } 815 } 816 817 /* Child state of ConnectedState indicating that we are online 818 * and there is nothing to do 819 */ 820 class OnlineState extends State { 821 @Override 822 public void enter() { 823 if (DBG) log(getName() + "\n"); 824 } 825 } 826 827 private boolean shouldCheckWalledGarden() { 828 if (!mWalledGardenTestEnabled) { 829 if (DBG) log("Skipping walled garden check - disabled"); 830 return false; 831 } 832 833 long waitTime = (mWalledGardenIntervalMs + mLastWalledGardenCheckTime) 834 - SystemClock.elapsedRealtime(); 835 836 if (mLastWalledGardenCheckTime != 0 && waitTime > 0) { 837 if (DBG) { 838 log("Skipping walled garden check - wait " + 839 waitTime + " ms."); 840 } 841 return false; 842 } 843 return true; 844 } 845 846 private boolean doArpTest(int type) { 847 boolean success; 848 849 String iface = mLinkProperties.getInterfaceName(); 850 String mac = mWifiInfo.getMacAddress(); 851 InetAddress inetAddress = null; 852 InetAddress gateway = null; 853 854 for (LinkAddress la : mLinkProperties.getLinkAddresses()) { 855 inetAddress = la.getAddress(); 856 break; 857 } 858 859 for (RouteInfo route : mLinkProperties.getRoutes()) { 860 gateway = route.getGateway(); 861 break; 862 } 863 864 if (DBG) log("ARP " + iface + "addr: " + inetAddress + "mac: " + mac + "gw: " + gateway); 865 866 try { 867 ArpPeer peer = new ArpPeer(iface, inetAddress, mac, gateway); 868 if (type == SINGLE_ARP_CHECK) { 869 success = (peer.doArp(mArpPingTimeoutMs) != null); 870 if (DBG) log("single ARP test result: " + success); 871 } else { 872 int responses = 0; 873 for (int i=0; i < mNumArpPings; i++) { 874 if(peer.doArp(mArpPingTimeoutMs) != null) responses++; 875 } 876 if (DBG) log("full ARP test result: " + responses + "/" + mNumArpPings); 877 success = (responses >= mMinArpResponses); 878 } 879 peer.close(); 880 } catch (SocketException se) { 881 //Consider an Arp socket creation issue as a successful Arp 882 //test to avoid any wifi connectivity issues 883 loge("ARP test initiation failure: " + se); 884 success = true; 885 } 886 887 return success; 888 } 889 890 private int calculateSignalLevel(int rssi) { 891 int signalLevel = WifiManager.calculateSignalLevel(rssi, 892 WifiManager.RSSI_LEVELS); 893 if (DBG) log("RSSI current: " + mCurrentSignalLevel + "new: " + rssi + ", " + signalLevel); 894 return signalLevel; 895 } 896 897 private void sendPoorLinkDetected() { 898 if (DBG) log("send POOR_LINK_DETECTED " + mWifiInfo); 899 mWsmChannel.sendMessage(POOR_LINK_DETECTED); 900 901 long time = android.os.SystemClock.elapsedRealtime(); 902 if (time - mLastBssidAvoidedTime > MIN_INTERVAL_AVOID_BSSID_MS[ 903 MIN_INTERVAL_AVOID_BSSID_MS.length - 1]) { 904 mMinIntervalArrayIndex = 1; 905 if (DBG) log("set mMinIntervalArrayIndex to 1"); 906 } else { 907 908 if (mMinIntervalArrayIndex < MIN_INTERVAL_AVOID_BSSID_MS.length - 1) { 909 mMinIntervalArrayIndex++; 910 } 911 if (DBG) log("mMinIntervalArrayIndex: " + mMinIntervalArrayIndex); 912 } 913 914 mLastBssidAvoidedTime = android.os.SystemClock.elapsedRealtime(); 915 } 916 917 /** 918 * Convenience function for retrieving a single secure settings value 919 * as a string with a default value. 920 * 921 * @param cr The ContentResolver to access. 922 * @param name The name of the setting to retrieve. 923 * @param def Value to return if the setting is not defined. 924 * 925 * @return The setting's current value, or 'def' if it is not defined 926 */ 927 private static String getSettingsStr(ContentResolver cr, String name, String def) { 928 String v = Settings.Secure.getString(cr, name); 929 return v != null ? v : def; 930 } 931 932 /** 933 * Convenience function for retrieving a single secure settings value 934 * as a boolean. Note that internally setting values are always 935 * stored as strings; this function converts the string to a boolean 936 * for you. The default value will be returned if the setting is 937 * not defined or not a valid boolean. 938 * 939 * @param cr The ContentResolver to access. 940 * @param name The name of the setting to retrieve. 941 * @param def Value to return if the setting is not defined. 942 * 943 * @return The setting's current value, or 'def' if it is not defined 944 * or not a valid boolean. 945 */ 946 private static boolean getSettingsBoolean(ContentResolver cr, String name, boolean def) { 947 return Settings.Secure.getInt(cr, name, def ? 1 : 0) == 1; 948 } 949 950 /** 951 * Convenience function for updating a single settings value as an 952 * integer. This will either create a new entry in the table if the 953 * given name does not exist, or modify the value of the existing row 954 * with that name. Note that internally setting values are always 955 * stored as strings, so this function converts the given value to a 956 * string before storing it. 957 * 958 * @param cr The ContentResolver to access. 959 * @param name The name of the setting to modify. 960 * @param value The new value for the setting. 961 * @return true if the value was set, false on database errors 962 */ 963 private static boolean putSettingsBoolean(ContentResolver cr, String name, boolean value) { 964 return Settings.Secure.putInt(cr, name, value ? 1 : 0); 965 } 966 967 private static void log(String s) { 968 Log.d(TAG, s); 969 } 970 971 private static void loge(String s) { 972 Log.e(TAG, s); 973 } 974} 975