1 /* 2 * Copyright (C) 2015 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 package com.android.settingslib.wifi; 17 18 import android.content.BroadcastReceiver; 19 import android.content.Context; 20 import android.content.Intent; 21 import android.content.IntentFilter; 22 import android.net.ConnectivityManager; 23 import android.net.Network; 24 import android.net.NetworkCapabilities; 25 import android.net.NetworkInfo; 26 import android.net.NetworkInfo.DetailedState; 27 import android.net.NetworkRequest; 28 import android.net.wifi.ScanResult; 29 import android.net.wifi.WifiConfiguration; 30 import android.net.wifi.WifiInfo; 31 import android.net.wifi.WifiManager; 32 import android.os.Handler; 33 import android.os.Looper; 34 import android.os.Message; 35 import android.util.Log; 36 import android.widget.Toast; 37 38 import com.android.internal.annotations.VisibleForTesting; 39 import com.android.settingslib.R; 40 41 import java.io.PrintWriter; 42 import java.util.ArrayList; 43 import java.util.Collection; 44 import java.util.Collections; 45 import java.util.HashMap; 46 import java.util.Iterator; 47 import java.util.List; 48 import java.util.Map; 49 import java.util.concurrent.atomic.AtomicBoolean; 50 51 /** 52 * Tracks saved or available wifi networks and their state. 53 */ 54 public class WifiTracker { 55 private static final String TAG = "WifiTracker"; 56 private static final boolean DBG = false; 57 58 /** verbose logging flag. this flag is set thru developer debugging options 59 * and used so as to assist with in-the-field WiFi connectivity debugging */ 60 public static int sVerboseLogging = 0; 61 62 // TODO: Allow control of this? 63 // Combo scans can take 5-6s to complete - set to 10s. 64 private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000; 65 66 private final Context mContext; 67 private final WifiManager mWifiManager; 68 private final IntentFilter mFilter; 69 private final ConnectivityManager mConnectivityManager; 70 private final NetworkRequest mNetworkRequest; 71 private WifiTrackerNetworkCallback mNetworkCallback; 72 73 private final AtomicBoolean mConnected = new AtomicBoolean(false); 74 private final WifiListener mListener; 75 private final boolean mIncludeSaved; 76 private final boolean mIncludeScans; 77 private final boolean mIncludePasspoints; 78 79 private final MainHandler mMainHandler; 80 private final WorkHandler mWorkHandler; 81 82 private boolean mSavedNetworksExist; 83 private boolean mRegistered; 84 private ArrayList<AccessPoint> mAccessPoints = new ArrayList<>(); 85 private HashMap<String, Integer> mSeenBssids = new HashMap<>(); 86 private HashMap<String, ScanResult> mScanResultCache = new HashMap<>(); 87 private Integer mScanId = 0; 88 private static final int NUM_SCANS_TO_CONFIRM_AP_LOSS = 3; 89 90 private NetworkInfo mLastNetworkInfo; 91 private WifiInfo mLastInfo; 92 93 @VisibleForTesting 94 Scanner mScanner; 95 96 public WifiTracker(Context context, WifiListener wifiListener, 97 boolean includeSaved, boolean includeScans) { 98 this(context, wifiListener, null, includeSaved, includeScans); 99 } 100 101 public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper, 102 boolean includeSaved, boolean includeScans) { 103 this(context, wifiListener, workerLooper, includeSaved, includeScans, false); 104 } 105 106 public WifiTracker(Context context, WifiListener wifiListener, 107 boolean includeSaved, boolean includeScans, boolean includePasspoints) { 108 this(context, wifiListener, null, includeSaved, includeScans, includePasspoints); 109 } 110 111 public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper, 112 boolean includeSaved, boolean includeScans, boolean includePasspoints) { 113 this(context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints, 114 context.getSystemService(WifiManager.class), 115 context.getSystemService(ConnectivityManager.class), Looper.myLooper()); 116 } 117 118 @VisibleForTesting 119 WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper, 120 boolean includeSaved, boolean includeScans, boolean includePasspoints, 121 WifiManager wifiManager, ConnectivityManager connectivityManager, 122 Looper currentLooper) { 123 if (!includeSaved && !includeScans) { 124 throw new IllegalArgumentException("Must include either saved or scans"); 125 } 126 mContext = context; 127 if (currentLooper == null) { 128 // When we aren't on a looper thread, default to the main. 129 currentLooper = Looper.getMainLooper(); 130 } 131 mMainHandler = new MainHandler(currentLooper); 132 mWorkHandler = new WorkHandler( 133 workerLooper != null ? workerLooper : currentLooper); 134 mWifiManager = wifiManager; 135 mIncludeSaved = includeSaved; 136 mIncludeScans = includeScans; 137 mIncludePasspoints = includePasspoints; 138 mListener = wifiListener; 139 mConnectivityManager = connectivityManager; 140 141 // check if verbose logging has been turned on or off 142 sVerboseLogging = mWifiManager.getVerboseLoggingLevel(); 143 144 mFilter = new IntentFilter(); 145 mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); 146 mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); 147 mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION); 148 mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); 149 mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION); 150 mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION); 151 mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); 152 153 mNetworkRequest = new NetworkRequest.Builder() 154 .clearCapabilities() 155 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) 156 .build(); 157 } 158 159 /** 160 * Forces an update of the wifi networks when not scanning. 161 */ 162 public void forceUpdate() { 163 updateAccessPoints(); 164 } 165 166 /** 167 * Force a scan for wifi networks to happen now. 168 */ 169 public void forceScan() { 170 if (mWifiManager.isWifiEnabled() && mScanner != null) { 171 mScanner.forceScan(); 172 } 173 } 174 175 /** 176 * Temporarily stop scanning for wifi networks. 177 */ 178 public void pauseScanning() { 179 if (mScanner != null) { 180 mScanner.pause(); 181 mScanner = null; 182 } 183 } 184 185 /** 186 * Resume scanning for wifi networks after it has been paused. 187 */ 188 public void resumeScanning() { 189 if (mScanner == null) { 190 mScanner = new Scanner(); 191 } 192 193 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_RESUME); 194 if (mWifiManager.isWifiEnabled()) { 195 mScanner.resume(); 196 } 197 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS); 198 } 199 200 /** 201 * Start tracking wifi networks. 202 * Registers listeners and starts scanning for wifi networks. If this is not called 203 * then forceUpdate() must be called to populate getAccessPoints(). 204 */ 205 public void startTracking() { 206 resumeScanning(); 207 if (!mRegistered) { 208 mContext.registerReceiver(mReceiver, mFilter); 209 // NetworkCallback objects cannot be reused. http://b/20701525 . 210 mNetworkCallback = new WifiTrackerNetworkCallback(); 211 mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback); 212 mRegistered = true; 213 } 214 } 215 216 /** 217 * Stop tracking wifi networks. 218 * Unregisters all listeners and stops scanning for wifi networks. This should always 219 * be called when done with a WifiTracker (if startTracking was called) to ensure 220 * proper cleanup. 221 */ 222 public void stopTracking() { 223 if (mRegistered) { 224 mWorkHandler.removeMessages(WorkHandler.MSG_UPDATE_ACCESS_POINTS); 225 mWorkHandler.removeMessages(WorkHandler.MSG_UPDATE_NETWORK_INFO); 226 mContext.unregisterReceiver(mReceiver); 227 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); 228 mRegistered = false; 229 } 230 pauseScanning(); 231 } 232 233 /** 234 * Gets the current list of access points. 235 */ 236 public List<AccessPoint> getAccessPoints() { 237 synchronized (mAccessPoints) { 238 return new ArrayList<>(mAccessPoints); 239 } 240 } 241 242 public WifiManager getManager() { 243 return mWifiManager; 244 } 245 246 public boolean isWifiEnabled() { 247 return mWifiManager.isWifiEnabled(); 248 } 249 250 /** 251 * @return true when there are saved networks on the device, regardless 252 * of whether the WifiTracker is tracking saved networks. 253 */ 254 public boolean doSavedNetworksExist() { 255 return mSavedNetworksExist; 256 } 257 258 public boolean isConnected() { 259 return mConnected.get(); 260 } 261 262 public void dump(PrintWriter pw) { 263 pw.println(" - wifi tracker ------"); 264 for (AccessPoint accessPoint : getAccessPoints()) { 265 pw.println(" " + accessPoint); 266 } 267 } 268 269 private void handleResume() { 270 mScanResultCache.clear(); 271 mSeenBssids.clear(); 272 mScanId = 0; 273 } 274 275 private Collection<ScanResult> fetchScanResults() { 276 mScanId++; 277 final List<ScanResult> newResults = mWifiManager.getScanResults(); 278 for (ScanResult newResult : newResults) { 279 if (newResult.SSID == null || newResult.SSID.isEmpty()) { 280 continue; 281 } 282 mScanResultCache.put(newResult.BSSID, newResult); 283 mSeenBssids.put(newResult.BSSID, mScanId); 284 } 285 286 if (mScanId > NUM_SCANS_TO_CONFIRM_AP_LOSS) { 287 if (DBG) Log.d(TAG, "------ Dumping SSIDs that were expired on this scan ------"); 288 Integer threshold = mScanId - NUM_SCANS_TO_CONFIRM_AP_LOSS; 289 for (Iterator<Map.Entry<String, Integer>> it = mSeenBssids.entrySet().iterator(); 290 it.hasNext(); /* nothing */) { 291 Map.Entry<String, Integer> e = it.next(); 292 if (e.getValue() < threshold) { 293 ScanResult result = mScanResultCache.get(e.getKey()); 294 if (DBG) Log.d(TAG, "Removing " + e.getKey() + ":(" + result.SSID + ")"); 295 mScanResultCache.remove(e.getKey()); 296 it.remove(); 297 } 298 } 299 if (DBG) Log.d(TAG, "---- Done Dumping SSIDs that were expired on this scan ----"); 300 } 301 302 return mScanResultCache.values(); 303 } 304 305 private WifiConfiguration getWifiConfigurationForNetworkId(int networkId) { 306 final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks(); 307 if (configs != null) { 308 for (WifiConfiguration config : configs) { 309 if (mLastInfo != null && networkId == config.networkId && 310 !(config.selfAdded && config.numAssociation == 0)) { 311 return config; 312 } 313 } 314 } 315 return null; 316 } 317 318 private void updateAccessPoints() { 319 // Swap the current access points into a cached list. 320 List<AccessPoint> cachedAccessPoints = getAccessPoints(); 321 ArrayList<AccessPoint> accessPoints = new ArrayList<>(); 322 323 // Clear out the configs so we don't think something is saved when it isn't. 324 for (AccessPoint accessPoint : cachedAccessPoints) { 325 accessPoint.clearConfig(); 326 } 327 328 /** Lookup table to more quickly update AccessPoints by only considering objects with the 329 * correct SSID. Maps SSID -> List of AccessPoints with the given SSID. */ 330 Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>(); 331 WifiConfiguration connectionConfig = null; 332 if (mLastInfo != null) { 333 connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId()); 334 } 335 336 final Collection<ScanResult> results = fetchScanResults(); 337 338 final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks(); 339 if (configs != null) { 340 mSavedNetworksExist = configs.size() != 0; 341 for (WifiConfiguration config : configs) { 342 if (config.selfAdded && config.numAssociation == 0) { 343 continue; 344 } 345 AccessPoint accessPoint = getCachedOrCreate(config, cachedAccessPoints); 346 if (mLastInfo != null && mLastNetworkInfo != null) { 347 if (config.isPasspoint() == false) { 348 accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo); 349 } 350 } 351 if (mIncludeSaved) { 352 if (!config.isPasspoint() || mIncludePasspoints) { 353 // If saved network not present in scan result then set its Rssi to MAX_VALUE 354 boolean apFound = false; 355 for (ScanResult result : results) { 356 if (result.SSID.equals(accessPoint.getSsidStr())) { 357 apFound = true; 358 break; 359 } 360 } 361 if (!apFound) { 362 accessPoint.setRssi(Integer.MAX_VALUE); 363 } 364 accessPoints.add(accessPoint); 365 } 366 367 if (config.isPasspoint() == false) { 368 apMap.put(accessPoint.getSsidStr(), accessPoint); 369 } 370 } else { 371 // If we aren't using saved networks, drop them into the cache so that 372 // we have access to their saved info. 373 cachedAccessPoints.add(accessPoint); 374 } 375 } 376 } 377 378 if (results != null) { 379 for (ScanResult result : results) { 380 // Ignore hidden and ad-hoc networks. 381 if (result.SSID == null || result.SSID.length() == 0 || 382 result.capabilities.contains("[IBSS]")) { 383 continue; 384 } 385 386 boolean found = false; 387 for (AccessPoint accessPoint : apMap.getAll(result.SSID)) { 388 if (accessPoint.update(result)) { 389 found = true; 390 break; 391 } 392 } 393 if (!found && mIncludeScans) { 394 AccessPoint accessPoint = getCachedOrCreate(result, cachedAccessPoints); 395 if (mLastInfo != null && mLastNetworkInfo != null) { 396 accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo); 397 } 398 399 if (result.isPasspointNetwork()) { 400 WifiConfiguration config = mWifiManager.getMatchingWifiConfig(result); 401 if (config != null) { 402 accessPoint.update(config); 403 } 404 } 405 406 if (mLastInfo != null && mLastInfo.getBSSID() != null 407 && mLastInfo.getBSSID().equals(result.BSSID) 408 && connectionConfig != null && connectionConfig.isPasspoint()) { 409 /* This network is connected via this passpoint config */ 410 /* SSID match is not going to work for it; so update explicitly */ 411 accessPoint.update(connectionConfig); 412 } 413 414 accessPoints.add(accessPoint); 415 apMap.put(accessPoint.getSsidStr(), accessPoint); 416 } 417 } 418 } 419 420 // Pre-sort accessPoints to speed preference insertion 421 Collections.sort(accessPoints); 422 423 // Log accesspoints that were deleted 424 if (DBG) Log.d(TAG, "------ Dumping SSIDs that were not seen on this scan ------"); 425 for (AccessPoint prevAccessPoint : mAccessPoints) { 426 if (prevAccessPoint.getSsid() == null) continue; 427 String prevSsid = prevAccessPoint.getSsidStr(); 428 boolean found = false; 429 for (AccessPoint newAccessPoint : accessPoints) { 430 if (newAccessPoint.getSsid() != null && newAccessPoint.getSsid().equals(prevSsid)) { 431 found = true; 432 break; 433 } 434 } 435 if (!found) 436 if (DBG) Log.d(TAG, "Did not find " + prevSsid + " in this scan"); 437 } 438 if (DBG) Log.d(TAG, "---- Done dumping SSIDs that were not seen on this scan ----"); 439 440 mAccessPoints = accessPoints; 441 mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED); 442 } 443 444 private AccessPoint getCachedOrCreate(ScanResult result, List<AccessPoint> cache) { 445 final int N = cache.size(); 446 for (int i = 0; i < N; i++) { 447 if (cache.get(i).matches(result)) { 448 AccessPoint ret = cache.remove(i); 449 ret.update(result); 450 return ret; 451 } 452 } 453 return new AccessPoint(mContext, result); 454 } 455 456 private AccessPoint getCachedOrCreate(WifiConfiguration config, List<AccessPoint> cache) { 457 final int N = cache.size(); 458 for (int i = 0; i < N; i++) { 459 if (cache.get(i).matches(config)) { 460 AccessPoint ret = cache.remove(i); 461 ret.loadConfig(config); 462 return ret; 463 } 464 } 465 return new AccessPoint(mContext, config); 466 } 467 468 private void updateNetworkInfo(NetworkInfo networkInfo) { 469 /* sticky broadcasts can call this when wifi is disabled */ 470 if (!mWifiManager.isWifiEnabled()) { 471 mMainHandler.sendEmptyMessage(MainHandler.MSG_PAUSE_SCANNING); 472 return; 473 } 474 475 if (networkInfo != null && 476 networkInfo.getDetailedState() == DetailedState.OBTAINING_IPADDR) { 477 mMainHandler.sendEmptyMessage(MainHandler.MSG_PAUSE_SCANNING); 478 } else { 479 mMainHandler.sendEmptyMessage(MainHandler.MSG_RESUME_SCANNING); 480 } 481 482 if (networkInfo != null) { 483 mLastNetworkInfo = networkInfo; 484 } 485 486 WifiConfiguration connectionConfig = null; 487 mLastInfo = mWifiManager.getConnectionInfo(); 488 if (mLastInfo != null) { 489 connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId()); 490 } 491 492 boolean reorder = false; 493 for (int i = mAccessPoints.size() - 1; i >= 0; --i) { 494 if (mAccessPoints.get(i).update(connectionConfig, mLastInfo, mLastNetworkInfo)) { 495 reorder = true; 496 } 497 } 498 if (reorder) { 499 synchronized (mAccessPoints) { 500 Collections.sort(mAccessPoints); 501 } 502 mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED); 503 } 504 } 505 506 private void updateWifiState(int state) { 507 mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_WIFI_STATE, state, 0).sendToTarget(); 508 } 509 510 public static List<AccessPoint> getCurrentAccessPoints(Context context, boolean includeSaved, 511 boolean includeScans, boolean includePasspoints) { 512 WifiTracker tracker = new WifiTracker(context, 513 null, null, includeSaved, includeScans, includePasspoints); 514 tracker.forceUpdate(); 515 return tracker.getAccessPoints(); 516 } 517 518 @VisibleForTesting 519 final BroadcastReceiver mReceiver = new BroadcastReceiver() { 520 @Override 521 public void onReceive(Context context, Intent intent) { 522 String action = intent.getAction(); 523 if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { 524 updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 525 WifiManager.WIFI_STATE_UNKNOWN)); 526 } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) || 527 WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) || 528 WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) { 529 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS); 530 } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) { 531 NetworkInfo info = (NetworkInfo) intent.getParcelableExtra( 532 WifiManager.EXTRA_NETWORK_INFO); 533 mConnected.set(info.isConnected()); 534 535 mMainHandler.sendEmptyMessage(MainHandler.MSG_CONNECTED_CHANGED); 536 537 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS); 538 mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO, info) 539 .sendToTarget(); 540 } 541 } 542 }; 543 544 private final class WifiTrackerNetworkCallback extends ConnectivityManager.NetworkCallback { 545 public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) { 546 if (network.equals(mWifiManager.getCurrentNetwork())) { 547 // We don't send a NetworkInfo object along with this message, because even if we 548 // fetch one from ConnectivityManager, it might be older than the most recent 549 // NetworkInfo message we got via a WIFI_STATE_CHANGED broadcast. 550 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO); 551 } 552 } 553 } 554 555 private final class MainHandler extends Handler { 556 private static final int MSG_CONNECTED_CHANGED = 0; 557 private static final int MSG_WIFI_STATE_CHANGED = 1; 558 private static final int MSG_ACCESS_POINT_CHANGED = 2; 559 private static final int MSG_RESUME_SCANNING = 3; 560 private static final int MSG_PAUSE_SCANNING = 4; 561 562 public MainHandler(Looper looper) { 563 super(looper); 564 } 565 566 @Override 567 public void handleMessage(Message msg) { 568 if (mListener == null) { 569 return; 570 } 571 switch (msg.what) { 572 case MSG_CONNECTED_CHANGED: 573 mListener.onConnectedChanged(); 574 break; 575 case MSG_WIFI_STATE_CHANGED: 576 mListener.onWifiStateChanged(msg.arg1); 577 break; 578 case MSG_ACCESS_POINT_CHANGED: 579 mListener.onAccessPointsChanged(); 580 break; 581 case MSG_RESUME_SCANNING: 582 if (mScanner != null) { 583 mScanner.resume(); 584 } 585 break; 586 case MSG_PAUSE_SCANNING: 587 if (mScanner != null) { 588 mScanner.pause(); 589 } 590 break; 591 } 592 } 593 } 594 595 private final class WorkHandler extends Handler { 596 private static final int MSG_UPDATE_ACCESS_POINTS = 0; 597 private static final int MSG_UPDATE_NETWORK_INFO = 1; 598 private static final int MSG_RESUME = 2; 599 private static final int MSG_UPDATE_WIFI_STATE = 3; 600 601 public WorkHandler(Looper looper) { 602 super(looper); 603 } 604 605 @Override 606 public void handleMessage(Message msg) { 607 switch (msg.what) { 608 case MSG_UPDATE_ACCESS_POINTS: 609 updateAccessPoints(); 610 break; 611 case MSG_UPDATE_NETWORK_INFO: 612 updateNetworkInfo((NetworkInfo) msg.obj); 613 break; 614 case MSG_RESUME: 615 handleResume(); 616 break; 617 case MSG_UPDATE_WIFI_STATE: 618 if (msg.arg1 == WifiManager.WIFI_STATE_ENABLED) { 619 if (mScanner != null) { 620 // We only need to resume if mScanner isn't null because 621 // that means we want to be scanning. 622 mScanner.resume(); 623 } 624 } else { 625 mLastInfo = null; 626 mLastNetworkInfo = null; 627 if (mScanner != null) { 628 mScanner.pause(); 629 } 630 } 631 mMainHandler.obtainMessage(MainHandler.MSG_WIFI_STATE_CHANGED, msg.arg1, 0) 632 .sendToTarget(); 633 break; 634 } 635 } 636 } 637 638 @VisibleForTesting 639 class Scanner extends Handler { 640 static final int MSG_SCAN = 0; 641 642 private int mRetry = 0; 643 644 void resume() { 645 if (!hasMessages(MSG_SCAN)) { 646 sendEmptyMessage(MSG_SCAN); 647 } 648 } 649 650 void forceScan() { 651 removeMessages(MSG_SCAN); 652 sendEmptyMessage(MSG_SCAN); 653 } 654 655 void pause() { 656 mRetry = 0; 657 removeMessages(MSG_SCAN); 658 } 659 660 @VisibleForTesting 661 boolean isScanning() { 662 return hasMessages(MSG_SCAN); 663 } 664 665 @Override 666 public void handleMessage(Message message) { 667 if (message.what != MSG_SCAN) return; 668 if (mWifiManager.startScan()) { 669 mRetry = 0; 670 } else if (++mRetry >= 3) { 671 mRetry = 0; 672 if (mContext != null) { 673 Toast.makeText(mContext, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show(); 674 } 675 return; 676 } 677 sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS); 678 } 679 } 680 681 /** A restricted multimap for use in constructAccessPoints */ 682 private static class Multimap<K,V> { 683 private final HashMap<K,List<V>> store = new HashMap<K,List<V>>(); 684 /** retrieve a non-null list of values with key K */ 685 List<V> getAll(K key) { 686 List<V> values = store.get(key); 687 return values != null ? values : Collections.<V>emptyList(); 688 } 689 690 void put(K key, V val) { 691 List<V> curVals = store.get(key); 692 if (curVals == null) { 693 curVals = new ArrayList<V>(3); 694 store.put(key, curVals); 695 } 696 curVals.add(val); 697 } 698 } 699 700 public interface WifiListener { 701 /** 702 * Called when the state of Wifi has changed, the state will be one of 703 * the following. 704 * 705 * <li>{@link WifiManager#WIFI_STATE_DISABLED}</li> 706 * <li>{@link WifiManager#WIFI_STATE_ENABLED}</li> 707 * <li>{@link WifiManager#WIFI_STATE_DISABLING}</li> 708 * <li>{@link WifiManager#WIFI_STATE_ENABLING}</li> 709 * <li>{@link WifiManager#WIFI_STATE_UNKNOWN}</li> 710 * <p> 711 * 712 * @param state The new state of wifi. 713 */ 714 void onWifiStateChanged(int state); 715 716 /** 717 * Called when the connection state of wifi has changed and isConnected 718 * should be called to get the updated state. 719 */ 720 void onConnectedChanged(); 721 722 /** 723 * Called to indicate the list of AccessPoints has been updated and 724 * getAccessPoints should be called to get the latest information. 725 */ 726 void onAccessPointsChanged(); 727 } 728 } 729