1 /* 2 * Copyright (C) 2008 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 com.android.server; 18 19 import android.content.BroadcastReceiver; 20 import android.content.ContentResolver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.database.ContentObserver; 25 import android.net.NetworkInfo; 26 import android.net.DhcpInfo; 27 import android.net.wifi.ScanResult; 28 import android.net.wifi.WifiInfo; 29 import android.net.wifi.WifiManager; 30 import android.net.wifi.WifiStateTracker; 31 import android.os.Handler; 32 import android.os.Looper; 33 import android.os.Message; 34 import android.provider.Settings; 35 import android.text.TextUtils; 36 import android.util.Config; 37 import android.util.Slog; 38 39 import java.io.IOException; 40 import java.net.DatagramPacket; 41 import java.net.DatagramSocket; 42 import java.net.InetAddress; 43 import java.net.SocketException; 44 import java.net.SocketTimeoutException; 45 import java.net.UnknownHostException; 46 import java.util.List; 47 import java.util.Random; 48 49 /** 50 * {@link WifiWatchdogService} monitors the initial connection to a Wi-Fi 51 * network with multiple access points. After the framework successfully 52 * connects to an access point, the watchdog verifies whether the DNS server is 53 * reachable. If not, the watchdog blacklists the current access point, leading 54 * to a connection on another access point within the same network. 55 * <p> 56 * The watchdog has a few safeguards: 57 * <ul> 58 * <li>Only monitor networks with multiple access points 59 * <li>Only check at most {@link #getMaxApChecks()} different access points 60 * within the network before giving up 61 * <p> 62 * The watchdog checks for connectivity on an access point by ICMP pinging the 63 * DNS. There are settings that allow disabling the watchdog, or tweaking the 64 * acceptable packet loss (and other various parameters). 65 * <p> 66 * The core logic of the watchdog is done on the main watchdog thread. Wi-Fi 67 * callbacks can come in on other threads, so we must queue messages to the main 68 * watchdog thread's handler. Most (if not all) state is only written to from 69 * the main thread. 70 * 71 * {@hide} 72 */ 73 public class WifiWatchdogService { 74 private static final String TAG = "WifiWatchdogService"; 75 private static final boolean V = false || Config.LOGV; 76 private static final boolean D = true || Config.LOGD; 77 78 private Context mContext; 79 private ContentResolver mContentResolver; 80 private WifiStateTracker mWifiStateTracker; 81 private WifiManager mWifiManager; 82 83 /** 84 * The main watchdog thread. 85 */ 86 private WifiWatchdogThread mThread; 87 /** 88 * The handler for the main watchdog thread. 89 */ 90 private WifiWatchdogHandler mHandler; 91 92 private ContentObserver mContentObserver; 93 94 /** 95 * The current watchdog state. Only written from the main thread! 96 */ 97 private WatchdogState mState = WatchdogState.IDLE; 98 /** 99 * The SSID of the network that the watchdog is currently monitoring. Only 100 * touched in the main thread! 101 */ 102 private String mSsid; 103 /** 104 * The number of access points in the current network ({@link #mSsid}) that 105 * have been checked. Only touched in the main thread! 106 */ 107 private int mNumApsChecked; 108 /** Whether the current AP check should be canceled. */ 109 private boolean mShouldCancel; 110 111 WifiWatchdogService(Context context, WifiStateTracker wifiStateTracker) { 112 mContext = context; 113 mContentResolver = context.getContentResolver(); 114 mWifiStateTracker = wifiStateTracker; 115 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 116 117 createThread(); 118 119 // The content observer to listen needs a handler, which createThread creates 120 registerForSettingsChanges(); 121 if (isWatchdogEnabled()) { 122 registerForWifiBroadcasts(); 123 } 124 125 if (V) { 126 myLogV("WifiWatchdogService: Created"); 127 } 128 } 129 130 /** 131 * Observes the watchdog on/off setting, and takes action when changed. 132 */ 133 private void registerForSettingsChanges() { 134 ContentResolver contentResolver = mContext.getContentResolver(); 135 contentResolver.registerContentObserver( 136 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON), false, 137 mContentObserver = new ContentObserver(mHandler) { 138 @Override 139 public void onChange(boolean selfChange) { 140 if (isWatchdogEnabled()) { 141 registerForWifiBroadcasts(); 142 } else { 143 unregisterForWifiBroadcasts(); 144 if (mHandler != null) { 145 mHandler.disableWatchdog(); 146 } 147 } 148 } 149 }); 150 } 151 152 /** 153 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ON 154 */ 155 private boolean isWatchdogEnabled() { 156 return Settings.Secure.getInt(mContentResolver, Settings.Secure.WIFI_WATCHDOG_ON, 1) == 1; 157 } 158 159 /** 160 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_AP_COUNT 161 */ 162 private int getApCount() { 163 return Settings.Secure.getInt(mContentResolver, 164 Settings.Secure.WIFI_WATCHDOG_AP_COUNT, 2); 165 } 166 167 /** 168 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT 169 */ 170 private int getInitialIgnoredPingCount() { 171 return Settings.Secure.getInt(mContentResolver, 172 Settings.Secure.WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT , 2); 173 } 174 175 /** 176 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_COUNT 177 */ 178 private int getPingCount() { 179 return Settings.Secure.getInt(mContentResolver, 180 Settings.Secure.WIFI_WATCHDOG_PING_COUNT, 4); 181 } 182 183 /** 184 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_TIMEOUT_MS 185 */ 186 private int getPingTimeoutMs() { 187 return Settings.Secure.getInt(mContentResolver, 188 Settings.Secure.WIFI_WATCHDOG_PING_TIMEOUT_MS, 500); 189 } 190 191 /** 192 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_DELAY_MS 193 */ 194 private int getPingDelayMs() { 195 return Settings.Secure.getInt(mContentResolver, 196 Settings.Secure.WIFI_WATCHDOG_PING_DELAY_MS, 250); 197 } 198 199 /** 200 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE 201 */ 202 private int getAcceptablePacketLossPercentage() { 203 return Settings.Secure.getInt(mContentResolver, 204 Settings.Secure.WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE, 25); 205 } 206 207 /** 208 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_MAX_AP_CHECKS 209 */ 210 private int getMaxApChecks() { 211 return Settings.Secure.getInt(mContentResolver, 212 Settings.Secure.WIFI_WATCHDOG_MAX_AP_CHECKS, 7); 213 } 214 215 /** 216 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED 217 */ 218 private boolean isBackgroundCheckEnabled() { 219 return Settings.Secure.getInt(mContentResolver, 220 Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED, 1) == 1; 221 } 222 223 /** 224 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS 225 */ 226 private int getBackgroundCheckDelayMs() { 227 return Settings.Secure.getInt(mContentResolver, 228 Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS, 60000); 229 } 230 231 /** 232 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS 233 */ 234 private int getBackgroundCheckTimeoutMs() { 235 return Settings.Secure.getInt(mContentResolver, 236 Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS, 1000); 237 } 238 239 /** 240 * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WATCH_LIST 241 * @return the comma-separated list of SSIDs 242 */ 243 private String getWatchList() { 244 return Settings.Secure.getString(mContentResolver, 245 Settings.Secure.WIFI_WATCHDOG_WATCH_LIST); 246 } 247 248 /** 249 * Registers to receive the necessary Wi-Fi broadcasts. 250 */ 251 private void registerForWifiBroadcasts() { 252 IntentFilter intentFilter = new IntentFilter(); 253 intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); 254 intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); 255 mContext.registerReceiver(mReceiver, intentFilter); 256 } 257 258 /** 259 * Unregisters from receiving the Wi-Fi broadcasts. 260 */ 261 private void unregisterForWifiBroadcasts() { 262 mContext.unregisterReceiver(mReceiver); 263 } 264 265 /** 266 * Creates the main watchdog thread, including waiting for the handler to be 267 * created. 268 */ 269 private void createThread() { 270 mThread = new WifiWatchdogThread(); 271 mThread.start(); 272 waitForHandlerCreation(); 273 } 274 275 /** 276 * Unregister broadcasts and quit the watchdog thread 277 */ 278 private void quit() { 279 unregisterForWifiBroadcasts(); 280 mContext.getContentResolver().unregisterContentObserver(mContentObserver); 281 mHandler.removeAllActions(); 282 mHandler.getLooper().quit(); 283 } 284 285 /** 286 * Waits for the main watchdog thread to create the handler. 287 */ 288 private void waitForHandlerCreation() { 289 synchronized(this) { 290 while (mHandler == null) { 291 try { 292 // Wait for the handler to be set by the other thread 293 wait(); 294 } catch (InterruptedException e) { 295 Slog.e(TAG, "Interrupted while waiting on handler."); 296 } 297 } 298 } 299 } 300 301 // Utility methods 302 303 /** 304 * Logs with the current thread. 305 */ 306 private static void myLogV(String message) { 307 Slog.v(TAG, "(" + Thread.currentThread().getName() + ") " + message); 308 } 309 310 private static void myLogD(String message) { 311 Slog.d(TAG, "(" + Thread.currentThread().getName() + ") " + message); 312 } 313 314 /** 315 * Gets the DNS of the current AP. 316 * 317 * @return The DNS of the current AP. 318 */ 319 private int getDns() { 320 DhcpInfo addressInfo = mWifiManager.getDhcpInfo(); 321 if (addressInfo != null) { 322 return addressInfo.dns1; 323 } else { 324 return -1; 325 } 326 } 327 328 /** 329 * Checks whether the DNS can be reached using multiple attempts according 330 * to the current setting values. 331 * 332 * @return Whether the DNS is reachable 333 */ 334 private boolean checkDnsConnectivity() { 335 int dns = getDns(); 336 if (dns == -1) { 337 if (V) { 338 myLogV("checkDnsConnectivity: Invalid DNS, returning false"); 339 } 340 return false; 341 } 342 343 if (V) { 344 myLogV("checkDnsConnectivity: Checking 0x" + 345 Integer.toHexString(Integer.reverseBytes(dns)) + " for connectivity"); 346 } 347 348 int numInitialIgnoredPings = getInitialIgnoredPingCount(); 349 int numPings = getPingCount(); 350 int pingDelay = getPingDelayMs(); 351 int acceptableLoss = getAcceptablePacketLossPercentage(); 352 353 /** See {@link Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT} */ 354 int ignoredPingCounter = 0; 355 int pingCounter = 0; 356 int successCounter = 0; 357 358 // No connectivity check needed 359 if (numPings == 0) { 360 return true; 361 } 362 363 // Do the initial pings that we ignore 364 for (; ignoredPingCounter < numInitialIgnoredPings; ignoredPingCounter++) { 365 if (shouldCancel()) return false; 366 367 boolean dnsAlive = DnsPinger.isDnsReachable(dns, getPingTimeoutMs()); 368 if (dnsAlive) { 369 /* 370 * Successful "ignored" pings are *not* ignored (they count in the total number 371 * of pings), but failures are really ignored. 372 */ 373 pingCounter++; 374 successCounter++; 375 } 376 377 if (V) { 378 Slog.v(TAG, (dnsAlive ? " +" : " Ignored: -")); 379 } 380 381 if (shouldCancel()) return false; 382 383 try { 384 Thread.sleep(pingDelay); 385 } catch (InterruptedException e) { 386 Slog.w(TAG, "Interrupted while pausing between pings", e); 387 } 388 } 389 390 // Do the pings that we use to measure packet loss 391 for (; pingCounter < numPings; pingCounter++) { 392 if (shouldCancel()) return false; 393 394 if (DnsPinger.isDnsReachable(dns, getPingTimeoutMs())) { 395 successCounter++; 396 if (V) { 397 Slog.v(TAG, " +"); 398 } 399 } else { 400 if (V) { 401 Slog.v(TAG, " -"); 402 } 403 } 404 405 if (shouldCancel()) return false; 406 407 try { 408 Thread.sleep(pingDelay); 409 } catch (InterruptedException e) { 410 Slog.w(TAG, "Interrupted while pausing between pings", e); 411 } 412 } 413 414 int packetLossPercentage = 100 * (numPings - successCounter) / numPings; 415 if (D) { 416 Slog.d(TAG, packetLossPercentage 417 + "% packet loss (acceptable is " + acceptableLoss + "%)"); 418 } 419 420 return !shouldCancel() && (packetLossPercentage <= acceptableLoss); 421 } 422 423 private boolean backgroundCheckDnsConnectivity() { 424 int dns = getDns(); 425 if (false && V) { 426 myLogV("backgroundCheckDnsConnectivity: Background checking " + dns + 427 " for connectivity"); 428 } 429 430 if (dns == -1) { 431 if (V) { 432 myLogV("backgroundCheckDnsConnectivity: DNS is empty, returning false"); 433 } 434 return false; 435 } 436 437 return DnsPinger.isDnsReachable(dns, getBackgroundCheckTimeoutMs()); 438 } 439 440 /** 441 * Signals the current action to cancel. 442 */ 443 private void cancelCurrentAction() { 444 mShouldCancel = true; 445 } 446 447 /** 448 * Helper to check whether to cancel. 449 * 450 * @return Whether to cancel processing the action. 451 */ 452 private boolean shouldCancel() { 453 if (V && mShouldCancel) { 454 myLogV("shouldCancel: Cancelling"); 455 } 456 457 return mShouldCancel; 458 } 459 460 // Wi-Fi initiated callbacks (could be executed in another thread) 461 462 /** 463 * Called when connected to an AP (this can be the next AP in line, or 464 * it can be a completely different network). 465 * 466 * @param ssid The SSID of the access point. 467 * @param bssid The BSSID of the access point. 468 */ 469 private void onConnected(String ssid, String bssid) { 470 if (V) { 471 myLogV("onConnected: SSID: " + ssid + ", BSSID: " + bssid); 472 } 473 474 /* 475 * The current action being processed by the main watchdog thread is now 476 * stale, so cancel it. 477 */ 478 cancelCurrentAction(); 479 480 if ((mSsid == null) || !mSsid.equals(ssid)) { 481 /* 482 * This is a different network than what the main watchdog thread is 483 * processing, dispatch the network change message on the main thread. 484 */ 485 mHandler.dispatchNetworkChanged(ssid); 486 } 487 488 if (requiresWatchdog(ssid, bssid)) { 489 if (D) { 490 myLogD(ssid + " (" + bssid + ") requires the watchdog"); 491 } 492 493 // This access point requires a watchdog, so queue the check on the main thread 494 mHandler.checkAp(new AccessPoint(ssid, bssid)); 495 496 } else { 497 if (D) { 498 myLogD(ssid + " (" + bssid + ") does not require the watchdog"); 499 } 500 501 // This access point does not require a watchdog, so queue idle on the main thread 502 mHandler.idle(); 503 } 504 } 505 506 /** 507 * Called when Wi-Fi is enabled. 508 */ 509 private void onEnabled() { 510 cancelCurrentAction(); 511 // Queue a hard-reset of the state on the main thread 512 mHandler.reset(); 513 } 514 515 /** 516 * Called when disconnected (or some other event similar to being disconnected). 517 */ 518 private void onDisconnected() { 519 if (V) { 520 myLogV("onDisconnected"); 521 } 522 523 /* 524 * Disconnected from an access point, the action being processed by the 525 * watchdog thread is now stale, so cancel it. 526 */ 527 cancelCurrentAction(); 528 // Dispatch the disconnected to the main watchdog thread 529 mHandler.dispatchDisconnected(); 530 // Queue the action to go idle 531 mHandler.idle(); 532 } 533 534 /** 535 * Checks whether an access point requires watchdog monitoring. 536 * 537 * @param ssid The SSID of the access point. 538 * @param bssid The BSSID of the access point. 539 * @return Whether the access point/network should be monitored by the 540 * watchdog. 541 */ 542 private boolean requiresWatchdog(String ssid, String bssid) { 543 if (V) { 544 myLogV("requiresWatchdog: SSID: " + ssid + ", BSSID: " + bssid); 545 } 546 547 WifiInfo info = null; 548 if (ssid == null) { 549 /* 550 * This is called from a Wi-Fi callback, so assume the WifiInfo does 551 * not have stale data. 552 */ 553 info = mWifiManager.getConnectionInfo(); 554 ssid = info.getSSID(); 555 if (ssid == null) { 556 // It's still null, give up 557 if (V) { 558 Slog.v(TAG, " Invalid SSID, returning false"); 559 } 560 return false; 561 } 562 } 563 564 if (TextUtils.isEmpty(bssid)) { 565 // Similar as above 566 if (info == null) { 567 info = mWifiManager.getConnectionInfo(); 568 } 569 bssid = info.getBSSID(); 570 if (TextUtils.isEmpty(bssid)) { 571 // It's still null, give up 572 if (V) { 573 Slog.v(TAG, " Invalid BSSID, returning false"); 574 } 575 return false; 576 } 577 } 578 579 if (!isOnWatchList(ssid)) { 580 if (V) { 581 Slog.v(TAG, " SSID not on watch list, returning false"); 582 } 583 return false; 584 } 585 586 // The watchdog only monitors networks with multiple APs 587 if (!hasRequiredNumberOfAps(ssid)) { 588 return false; 589 } 590 591 return true; 592 } 593 594 private boolean isOnWatchList(String ssid) { 595 String watchList; 596 597 if (ssid == null || (watchList = getWatchList()) == null) { 598 return false; 599 } 600 601 String[] list = watchList.split(" *, *"); 602 603 for (String name : list) { 604 if (ssid.equals(name)) { 605 return true; 606 } 607 } 608 609 return false; 610 } 611 612 /** 613 * Checks if the current scan results have multiple access points with an SSID. 614 * 615 * @param ssid The SSID to check. 616 * @return Whether the SSID has multiple access points. 617 */ 618 private boolean hasRequiredNumberOfAps(String ssid) { 619 List<ScanResult> results = mWifiManager.getScanResults(); 620 if (results == null) { 621 if (V) { 622 myLogV("hasRequiredNumberOfAps: Got null scan results, returning false"); 623 } 624 return false; 625 } 626 627 int numApsRequired = getApCount(); 628 int numApsFound = 0; 629 int resultsSize = results.size(); 630 for (int i = 0; i < resultsSize; i++) { 631 ScanResult result = results.get(i); 632 if (result == null) continue; 633 if (result.SSID == null) continue; 634 635 if (result.SSID.equals(ssid)) { 636 numApsFound++; 637 638 if (numApsFound >= numApsRequired) { 639 if (V) { 640 myLogV("hasRequiredNumberOfAps: SSID: " + ssid + ", returning true"); 641 } 642 return true; 643 } 644 } 645 } 646 647 if (V) { 648 myLogV("hasRequiredNumberOfAps: SSID: " + ssid + ", returning false"); 649 } 650 return false; 651 } 652 653 // Watchdog logic (assume all of these methods will be in our main thread) 654 655 /** 656 * Handles a Wi-Fi network change (for example, from networkA to networkB). 657 */ 658 private void handleNetworkChanged(String ssid) { 659 // Set the SSID being monitored to the new SSID 660 mSsid = ssid; 661 // Set various state to that when being idle 662 setIdleState(true); 663 } 664 665 /** 666 * Handles checking whether an AP is a "good" AP. If not, it will be blacklisted. 667 * 668 * @param ap The access point to check. 669 */ 670 private void handleCheckAp(AccessPoint ap) { 671 // Reset the cancel state since this is the entry point of this action 672 mShouldCancel = false; 673 674 if (V) { 675 myLogV("handleCheckAp: AccessPoint: " + ap); 676 } 677 678 // Make sure we are not sleeping 679 if (mState == WatchdogState.SLEEP) { 680 if (V) { 681 Slog.v(TAG, " Sleeping (in " + mSsid + "), so returning"); 682 } 683 return; 684 } 685 686 mState = WatchdogState.CHECKING_AP; 687 688 /* 689 * Checks to make sure we haven't exceeded the max number of checks 690 * we're allowed per network 691 */ 692 mNumApsChecked++; 693 if (mNumApsChecked > getMaxApChecks()) { 694 if (V) { 695 Slog.v(TAG, " Passed the max attempts (" + getMaxApChecks() 696 + "), going to sleep for " + mSsid); 697 } 698 mHandler.sleep(mSsid); 699 return; 700 } 701 702 // Do the check 703 boolean isApAlive = checkDnsConnectivity(); 704 705 if (V) { 706 Slog.v(TAG, " Is it alive: " + isApAlive); 707 } 708 709 // Take action based on results 710 if (isApAlive) { 711 handleApAlive(ap); 712 } else { 713 handleApUnresponsive(ap); 714 } 715 } 716 717 /** 718 * Handles the case when an access point is alive. 719 * 720 * @param ap The access point. 721 */ 722 private void handleApAlive(AccessPoint ap) { 723 // Check whether we are stale and should cancel 724 if (shouldCancel()) return; 725 // We're satisfied with this AP, so go idle 726 setIdleState(false); 727 728 if (D) { 729 myLogD("AP is alive: " + ap.toString()); 730 } 731 732 // Queue the next action to be a background check 733 mHandler.backgroundCheckAp(ap); 734 } 735 736 /** 737 * Handles an unresponsive AP by blacklisting it. 738 * 739 * @param ap The access point. 740 */ 741 private void handleApUnresponsive(AccessPoint ap) { 742 // Check whether we are stale and should cancel 743 if (shouldCancel()) return; 744 // This AP is "bad", switch to another 745 mState = WatchdogState.SWITCHING_AP; 746 747 if (D) { 748 myLogD("AP is dead: " + ap.toString()); 749 } 750 751 // Black list this "bad" AP, this will cause an attempt to connect to another 752 blacklistAp(ap.bssid); 753 // Initiate an association to an alternate AP 754 mWifiStateTracker.reassociate(); 755 } 756 757 private void blacklistAp(String bssid) { 758 if (TextUtils.isEmpty(bssid)) { 759 return; 760 } 761 762 // Before taking action, make sure we should not cancel our processing 763 if (shouldCancel()) return; 764 765 if (!mWifiStateTracker.addToBlacklist(bssid)) { 766 // There's a known bug where this method returns failure on success 767 //Slog.e(TAG, "Blacklisting " + bssid + " failed"); 768 } 769 770 if (D) { 771 myLogD("Blacklisting " + bssid); 772 } 773 } 774 775 /** 776 * Handles a single background check. If it fails, it should trigger a 777 * normal check. If it succeeds, it should queue another background check. 778 * 779 * @param ap The access point to do a background check for. If this is no 780 * longer the current AP, it is okay to return without any 781 * processing. 782 */ 783 private void handleBackgroundCheckAp(AccessPoint ap) { 784 // Reset the cancel state since this is the entry point of this action 785 mShouldCancel = false; 786 787 if (false && V) { 788 myLogV("handleBackgroundCheckAp: AccessPoint: " + ap); 789 } 790 791 // Make sure we are not sleeping 792 if (mState == WatchdogState.SLEEP) { 793 if (V) { 794 Slog.v(TAG, " handleBackgroundCheckAp: Sleeping (in " + mSsid + "), so returning"); 795 } 796 return; 797 } 798 799 // Make sure the AP we're supposed to be background checking is still the active one 800 WifiInfo info = mWifiManager.getConnectionInfo(); 801 if (info.getSSID() == null || !info.getSSID().equals(ap.ssid)) { 802 if (V) { 803 myLogV("handleBackgroundCheckAp: We are no longer connected to " 804 + ap + ", and instead are on " + info); 805 } 806 return; 807 } 808 809 if (info.getBSSID() == null || !info.getBSSID().equals(ap.bssid)) { 810 if (V) { 811 myLogV("handleBackgroundCheckAp: We are no longer connected to " 812 + ap + ", and instead are on " + info); 813 } 814 return; 815 } 816 817 // Do the check 818 boolean isApAlive = backgroundCheckDnsConnectivity(); 819 820 if (V && !isApAlive) { 821 Slog.v(TAG, " handleBackgroundCheckAp: Is it alive: " + isApAlive); 822 } 823 824 if (shouldCancel()) { 825 return; 826 } 827 828 // Take action based on results 829 if (isApAlive) { 830 // Queue another background check 831 mHandler.backgroundCheckAp(ap); 832 833 } else { 834 if (D) { 835 myLogD("Background check failed for " + ap.toString()); 836 } 837 838 // Queue a normal check, so it can take proper action 839 mHandler.checkAp(ap); 840 } 841 } 842 843 /** 844 * Handles going to sleep for this network. Going to sleep means we will not 845 * monitor this network anymore. 846 * 847 * @param ssid The network that will not be monitored anymore. 848 */ 849 private void handleSleep(String ssid) { 850 // Make sure the network we're trying to sleep in is still the current network 851 if (ssid != null && ssid.equals(mSsid)) { 852 mState = WatchdogState.SLEEP; 853 854 if (D) { 855 myLogD("Going to sleep for " + ssid); 856 } 857 858 /* 859 * Before deciding to go to sleep, we may have checked a few APs 860 * (and blacklisted them). Clear the blacklist so the AP with best 861 * signal is chosen. 862 */ 863 if (!mWifiStateTracker.clearBlacklist()) { 864 // There's a known bug where this method returns failure on success 865 //Slog.e(TAG, "Clearing blacklist failed"); 866 } 867 868 if (V) { 869 myLogV("handleSleep: Set state to SLEEP and cleared blacklist"); 870 } 871 } 872 } 873 874 /** 875 * Handles an access point disconnection. 876 */ 877 private void handleDisconnected() { 878 /* 879 * We purposefully do not change mSsid to null. This is to handle 880 * disconnected followed by connected better (even if there is some 881 * duration in between). For example, if the watchdog went to sleep in a 882 * network, and then the phone goes to sleep, when the phone wakes up we 883 * still want to be in the sleeping state. When the phone went to sleep, 884 * we would have gotten a disconnected event which would then set mSsid 885 * = null. This is bad, since the following connect would cause us to do 886 * the "network is good?" check all over again. */ 887 888 /* 889 * Set the state as if we were idle (don't come out of sleep, only 890 * hard reset and network changed should do that. 891 */ 892 setIdleState(false); 893 } 894 895 /** 896 * Handles going idle. Idle means we are satisfied with the current state of 897 * things, but if a new connection occurs we'll re-evaluate. 898 */ 899 private void handleIdle() { 900 // Reset the cancel state since this is the entry point for this action 901 mShouldCancel = false; 902 903 if (V) { 904 myLogV("handleSwitchToIdle"); 905 } 906 907 // If we're sleeping, don't do anything 908 if (mState == WatchdogState.SLEEP) { 909 Slog.v(TAG, " Sleeping (in " + mSsid + "), so returning"); 910 return; 911 } 912 913 // Set the idle state 914 setIdleState(false); 915 916 if (V) { 917 Slog.v(TAG, " Set state to IDLE"); 918 } 919 } 920 921 /** 922 * Sets the state as if we are going idle. 923 */ 924 private void setIdleState(boolean forceIdleState) { 925 // Setting idle state does not kick us out of sleep unless the forceIdleState is set 926 if (forceIdleState || (mState != WatchdogState.SLEEP)) { 927 mState = WatchdogState.IDLE; 928 } 929 mNumApsChecked = 0; 930 } 931 932 /** 933 * Handles a hard reset. A hard reset is rarely used, but when used it 934 * should revert anything done by the watchdog monitoring. 935 */ 936 private void handleReset() { 937 mWifiStateTracker.clearBlacklist(); 938 setIdleState(true); 939 } 940 941 // Inner classes 942 943 /** 944 * Possible states for the watchdog to be in. 945 */ 946 private static enum WatchdogState { 947 /** The watchdog is currently idle, but it is still responsive to future AP checks in this network. */ 948 IDLE, 949 /** The watchdog is sleeping, so it will not try any AP checks for the network. */ 950 SLEEP, 951 /** The watchdog is currently checking an AP for connectivity. */ 952 CHECKING_AP, 953 /** The watchdog is switching to another AP in the network. */ 954 SWITCHING_AP 955 } 956 957 /** 958 * The main thread for the watchdog monitoring. This will be turned into a 959 * {@link Looper} thread. 960 */ 961 private class WifiWatchdogThread extends Thread { 962 WifiWatchdogThread() { 963 super("WifiWatchdogThread"); 964 } 965 966 @Override 967 public void run() { 968 // Set this thread up so the handler will work on it 969 Looper.prepare(); 970 971 synchronized(WifiWatchdogService.this) { 972 mHandler = new WifiWatchdogHandler(); 973 974 // Notify that the handler has been created 975 WifiWatchdogService.this.notify(); 976 } 977 978 // Listen for messages to the handler 979 Looper.loop(); 980 } 981 } 982 983 /** 984 * The main thread's handler. There are 'actions', and just general 985 * 'messages'. There should only ever be one 'action' in the queue (aside 986 * from the one being processed, if any). There may be multiple messages in 987 * the queue. So, actions are replaced by more recent actions, where as 988 * messages will be executed for sure. Messages end up being used to just 989 * change some state, and not really take any action. 990 * <p> 991 * There is little logic inside this class, instead methods of the form 992 * "handle___" are called in the main {@link WifiWatchdogService}. 993 */ 994 private class WifiWatchdogHandler extends Handler { 995 /** Check whether the AP is "good". The object will be an {@link AccessPoint}. */ 996 static final int ACTION_CHECK_AP = 1; 997 /** Go into the idle state. */ 998 static final int ACTION_IDLE = 2; 999 /** 1000 * Performs a periodic background check whether the AP is still "good". 1001 * The object will be an {@link AccessPoint}. 1002 */ 1003 static final int ACTION_BACKGROUND_CHECK_AP = 3; 1004 1005 /** 1006 * Go to sleep for the current network. We are conservative with making 1007 * this a message rather than action. We want to make sure our main 1008 * thread sees this message, but if it were an action it could be 1009 * removed from the queue and replaced by another action. The main 1010 * thread will ensure when it sees the message that the state is still 1011 * valid for going to sleep. 1012 * <p> 1013 * For an explanation of sleep, see {@link android.provider.Settings.Secure#WIFI_WATCHDOG_MAX_AP_CHECKS}. 1014 */ 1015 static final int MESSAGE_SLEEP = 101; 1016 /** Disables the watchdog. */ 1017 static final int MESSAGE_DISABLE_WATCHDOG = 102; 1018 /** The network has changed. */ 1019 static final int MESSAGE_NETWORK_CHANGED = 103; 1020 /** The current access point has disconnected. */ 1021 static final int MESSAGE_DISCONNECTED = 104; 1022 /** Performs a hard-reset on the watchdog state. */ 1023 static final int MESSAGE_RESET = 105; 1024 1025 void checkAp(AccessPoint ap) { 1026 removeAllActions(); 1027 sendMessage(obtainMessage(ACTION_CHECK_AP, ap)); 1028 } 1029 1030 void backgroundCheckAp(AccessPoint ap) { 1031 if (!isBackgroundCheckEnabled()) return; 1032 1033 removeAllActions(); 1034 sendMessageDelayed(obtainMessage(ACTION_BACKGROUND_CHECK_AP, ap), 1035 getBackgroundCheckDelayMs()); 1036 } 1037 1038 void idle() { 1039 removeAllActions(); 1040 sendMessage(obtainMessage(ACTION_IDLE)); 1041 } 1042 1043 void sleep(String ssid) { 1044 removeAllActions(); 1045 sendMessage(obtainMessage(MESSAGE_SLEEP, ssid)); 1046 } 1047 1048 void disableWatchdog() { 1049 removeAllActions(); 1050 sendMessage(obtainMessage(MESSAGE_DISABLE_WATCHDOG)); 1051 } 1052 1053 void dispatchNetworkChanged(String ssid) { 1054 removeAllActions(); 1055 sendMessage(obtainMessage(MESSAGE_NETWORK_CHANGED, ssid)); 1056 } 1057 1058 void dispatchDisconnected() { 1059 removeAllActions(); 1060 sendMessage(obtainMessage(MESSAGE_DISCONNECTED)); 1061 } 1062 1063 void reset() { 1064 removeAllActions(); 1065 sendMessage(obtainMessage(MESSAGE_RESET)); 1066 } 1067 1068 private void removeAllActions() { 1069 removeMessages(ACTION_CHECK_AP); 1070 removeMessages(ACTION_IDLE); 1071 removeMessages(ACTION_BACKGROUND_CHECK_AP); 1072 } 1073 1074 @Override 1075 public void handleMessage(Message msg) { 1076 switch (msg.what) { 1077 case MESSAGE_NETWORK_CHANGED: 1078 handleNetworkChanged((String) msg.obj); 1079 break; 1080 case ACTION_CHECK_AP: 1081 handleCheckAp((AccessPoint) msg.obj); 1082 break; 1083 case ACTION_BACKGROUND_CHECK_AP: 1084 handleBackgroundCheckAp((AccessPoint) msg.obj); 1085 break; 1086 case MESSAGE_SLEEP: 1087 handleSleep((String) msg.obj); 1088 break; 1089 case ACTION_IDLE: 1090 handleIdle(); 1091 break; 1092 case MESSAGE_DISABLE_WATCHDOG: 1093 handleIdle(); 1094 break; 1095 case MESSAGE_DISCONNECTED: 1096 handleDisconnected(); 1097 break; 1098 case MESSAGE_RESET: 1099 handleReset(); 1100 break; 1101 } 1102 } 1103 } 1104 1105 /** 1106 * Receives Wi-Fi broadcasts. 1107 * <p> 1108 * There is little logic in this class, instead methods of the form "on___" 1109 * are called in the {@link WifiWatchdogService}. 1110 */ 1111 private BroadcastReceiver mReceiver = new BroadcastReceiver() { 1112 1113 @Override 1114 public void onReceive(Context context, Intent intent) { 1115 final String action = intent.getAction(); 1116 if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { 1117 handleNetworkStateChanged( 1118 (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO)); 1119 } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { 1120 handleWifiStateChanged(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 1121 WifiManager.WIFI_STATE_UNKNOWN)); 1122 } 1123 } 1124 1125 private void handleNetworkStateChanged(NetworkInfo info) { 1126 if (V) { 1127 myLogV("Receiver.handleNetworkStateChanged: NetworkInfo: " 1128 + info); 1129 } 1130 1131 switch (info.getState()) { 1132 case CONNECTED: 1133 WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); 1134 if (wifiInfo.getSSID() == null || wifiInfo.getBSSID() == null) { 1135 if (V) { 1136 myLogV("handleNetworkStateChanged: Got connected event but SSID or BSSID are null. SSID: " 1137 + wifiInfo.getSSID() 1138 + ", BSSID: " 1139 + wifiInfo.getBSSID() + ", ignoring event"); 1140 } 1141 return; 1142 } 1143 onConnected(wifiInfo.getSSID(), wifiInfo.getBSSID()); 1144 break; 1145 1146 case DISCONNECTED: 1147 onDisconnected(); 1148 break; 1149 } 1150 } 1151 1152 private void handleWifiStateChanged(int wifiState) { 1153 if (wifiState == WifiManager.WIFI_STATE_DISABLED) { 1154 quit(); 1155 } else if (wifiState == WifiManager.WIFI_STATE_ENABLED) { 1156 onEnabled(); 1157 } 1158 } 1159 }; 1160 1161 /** 1162 * Describes an access point by its SSID and BSSID. 1163 */ 1164 private static class AccessPoint { 1165 String ssid; 1166 String bssid; 1167 1168 AccessPoint(String ssid, String bssid) { 1169 this.ssid = ssid; 1170 this.bssid = bssid; 1171 } 1172 1173 private boolean hasNull() { 1174 return ssid == null || bssid == null; 1175 } 1176 1177 @Override 1178 public boolean equals(Object o) { 1179 if (!(o instanceof AccessPoint)) return false; 1180 AccessPoint otherAp = (AccessPoint) o; 1181 boolean iHaveNull = hasNull(); 1182 // Either we both have a null, or our SSIDs and BSSIDs are equal 1183 return (iHaveNull && otherAp.hasNull()) || 1184 (otherAp.bssid != null && ssid.equals(otherAp.ssid) 1185 && bssid.equals(otherAp.bssid)); 1186 } 1187 1188 @Override 1189 public int hashCode() { 1190 if (ssid == null || bssid == null) return 0; 1191 return ssid.hashCode() + bssid.hashCode(); 1192 } 1193 1194 @Override 1195 public String toString() { 1196 return ssid + " (" + bssid + ")"; 1197 } 1198 } 1199 1200 /** 1201 * Performs a simple DNS "ping" by sending a "server status" query packet to 1202 * the DNS server. As long as the server replies, we consider it a success. 1203 * <p> 1204 * We do not use a simple hostname lookup because that could be cached and 1205 * the API may not differentiate between a time out and a failure lookup 1206 * (which we really care about). 1207 */ 1208 private static class DnsPinger { 1209 1210 /** Number of bytes for the query */ 1211 private static final int DNS_QUERY_BASE_SIZE = 33; 1212 1213 /** The DNS port */ 1214 private static final int DNS_PORT = 53; 1215 1216 /** Used to generate IDs */ 1217 private static Random sRandom = new Random(); 1218 1219 static boolean isDnsReachable(int dns, int timeout) { 1220 DatagramSocket socket = null; 1221 try { 1222 socket = new DatagramSocket(); 1223 1224 // Set some socket properties 1225 socket.setSoTimeout(timeout); 1226 1227 byte[] buf = new byte[DNS_QUERY_BASE_SIZE]; 1228 fillQuery(buf); 1229 1230 // Send the DNS query 1231 byte parts[] = new byte[4]; 1232 parts[0] = (byte)(dns & 0xff); 1233 parts[1] = (byte)((dns >> 8) & 0xff); 1234 parts[2] = (byte)((dns >> 16) & 0xff); 1235 parts[3] = (byte)((dns >> 24) & 0xff); 1236 1237 InetAddress dnsAddress = InetAddress.getByAddress(parts); 1238 DatagramPacket packet = new DatagramPacket(buf, 1239 buf.length, dnsAddress, DNS_PORT); 1240 socket.send(packet); 1241 1242 // Wait for reply (blocks for the above timeout) 1243 DatagramPacket replyPacket = new DatagramPacket(buf, buf.length); 1244 socket.receive(replyPacket); 1245 1246 // If a timeout occurred, an exception would have been thrown. We got a reply! 1247 return true; 1248 1249 } catch (SocketException e) { 1250 if (V) { 1251 Slog.v(TAG, "DnsPinger.isReachable received SocketException", e); 1252 } 1253 return false; 1254 1255 } catch (UnknownHostException e) { 1256 if (V) { 1257 Slog.v(TAG, "DnsPinger.isReachable is unable to resolve the DNS host", e); 1258 } 1259 return false; 1260 1261 } catch (SocketTimeoutException e) { 1262 return false; 1263 1264 } catch (IOException e) { 1265 if (V) { 1266 Slog.v(TAG, "DnsPinger.isReachable got an IOException", e); 1267 } 1268 return false; 1269 1270 } catch (Exception e) { 1271 if (V || Config.LOGD) { 1272 Slog.d(TAG, "DnsPinger.isReachable got an unknown exception", e); 1273 } 1274 return false; 1275 } finally { 1276 if (socket != null) { 1277 socket.close(); 1278 } 1279 } 1280 } 1281 1282 private static void fillQuery(byte[] buf) { 1283 1284 /* 1285 * See RFC2929 (though the bit tables in there are misleading for 1286 * us. For example, the recursion desired bit is the 0th bit for us, 1287 * but looking there it would appear as the 7th bit of the byte 1288 */ 1289 1290 // Make sure it's all zeroed out 1291 for (int i = 0; i < buf.length; i++) buf[i] = 0; 1292 1293 // Form a query for www.android.com 1294 1295 // [0-1] bytes are an ID, generate random ID for this query 1296 buf[0] = (byte) sRandom.nextInt(256); 1297 buf[1] = (byte) sRandom.nextInt(256); 1298 1299 // [2-3] bytes are for flags. 1300 buf[2] = 1; // Recursion desired 1301 1302 // [4-5] bytes are for the query count 1303 buf[5] = 1; // One query 1304 1305 // [6-7] [8-9] [10-11] are all counts of other fields we don't use 1306 1307 // [12-15] for www 1308 writeString(buf, 12, "www"); 1309 1310 // [16-23] for android 1311 writeString(buf, 16, "android"); 1312 1313 // [24-27] for com 1314 writeString(buf, 24, "com"); 1315 1316 // [29-30] bytes are for QTYPE, set to 1 1317 buf[30] = 1; 1318 1319 // [31-32] bytes are for QCLASS, set to 1 1320 buf[32] = 1; 1321 } 1322 1323 private static void writeString(byte[] buf, int startPos, String string) { 1324 int pos = startPos; 1325 1326 // Write the length first 1327 buf[pos++] = (byte) string.length(); 1328 for (int i = 0; i < string.length(); i++) { 1329 buf[pos++] = (byte) string.charAt(i); 1330 } 1331 } 1332 } 1333 } 1334