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