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 WifiTracker(Context context, WifiListener wifiListener, boolean includeSaved, boolean includeScans)96 public WifiTracker(Context context, WifiListener wifiListener, 97 boolean includeSaved, boolean includeScans) { 98 this(context, wifiListener, null, includeSaved, includeScans); 99 } 100 WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper, boolean includeSaved, boolean includeScans)101 public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper, 102 boolean includeSaved, boolean includeScans) { 103 this(context, wifiListener, workerLooper, includeSaved, includeScans, false); 104 } 105 WifiTracker(Context context, WifiListener wifiListener, boolean includeSaved, boolean includeScans, boolean includePasspoints)106 public WifiTracker(Context context, WifiListener wifiListener, 107 boolean includeSaved, boolean includeScans, boolean includePasspoints) { 108 this(context, wifiListener, null, includeSaved, includeScans, includePasspoints); 109 } 110 WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper, boolean includeSaved, boolean includeScans, boolean includePasspoints)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 WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper, boolean includeSaved, boolean includeScans, boolean includePasspoints, WifiManager wifiManager, ConnectivityManager connectivityManager, Looper currentLooper)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 */ forceUpdate()162 public void forceUpdate() { 163 updateAccessPoints(); 164 } 165 166 /** 167 * Force a scan for wifi networks to happen now. 168 */ forceScan()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 */ pauseScanning()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 */ resumeScanning()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 */ startTracking()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 */ stopTracking()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 */ getAccessPoints()236 public List<AccessPoint> getAccessPoints() { 237 synchronized (mAccessPoints) { 238 return new ArrayList<>(mAccessPoints); 239 } 240 } 241 getManager()242 public WifiManager getManager() { 243 return mWifiManager; 244 } 245 isWifiEnabled()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 */ doSavedNetworksExist()254 public boolean doSavedNetworksExist() { 255 return mSavedNetworksExist; 256 } 257 isConnected()258 public boolean isConnected() { 259 return mConnected.get(); 260 } 261 dump(PrintWriter pw)262 public void dump(PrintWriter pw) { 263 pw.println(" - wifi tracker ------"); 264 for (AccessPoint accessPoint : getAccessPoints()) { 265 pw.println(" " + accessPoint); 266 } 267 } 268 handleResume()269 private void handleResume() { 270 mScanResultCache.clear(); 271 mSeenBssids.clear(); 272 mScanId = 0; 273 } 274 fetchScanResults()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 getWifiConfigurationForNetworkId(int networkId)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 updateAccessPoints()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 getCachedOrCreate(ScanResult result, List<AccessPoint> cache)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 getCachedOrCreate(WifiConfiguration config, List<AccessPoint> cache)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 updateNetworkInfo(NetworkInfo networkInfo)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 updateWifiState(int state)506 private void updateWifiState(int state) { 507 mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_WIFI_STATE, state, 0).sendToTarget(); 508 } 509 getCurrentAccessPoints(Context context, boolean includeSaved, boolean includeScans, boolean includePasspoints)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 { onCapabilitiesChanged(Network network, NetworkCapabilities nc)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 MainHandler(Looper looper)562 public MainHandler(Looper looper) { 563 super(looper); 564 } 565 566 @Override handleMessage(Message msg)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 WorkHandler(Looper looper)601 public WorkHandler(Looper looper) { 602 super(looper); 603 } 604 605 @Override handleMessage(Message msg)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 resume()644 void resume() { 645 if (!hasMessages(MSG_SCAN)) { 646 sendEmptyMessage(MSG_SCAN); 647 } 648 } 649 forceScan()650 void forceScan() { 651 removeMessages(MSG_SCAN); 652 sendEmptyMessage(MSG_SCAN); 653 } 654 pause()655 void pause() { 656 mRetry = 0; 657 removeMessages(MSG_SCAN); 658 } 659 660 @VisibleForTesting isScanning()661 boolean isScanning() { 662 return hasMessages(MSG_SCAN); 663 } 664 665 @Override handleMessage(Message message)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 */ getAll(K key)685 List<V> getAll(K key) { 686 List<V> values = store.get(key); 687 return values != null ? values : Collections.<V>emptyList(); 688 } 689 put(K key, V val)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 */ onWifiStateChanged(int state)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 */ onConnectedChanged()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 */ onAccessPointsChanged()726 void onAccessPointsChanged(); 727 } 728 } 729