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 */ 16package com.android.settingslib.wifi; 17 18import android.content.BroadcastReceiver; 19import android.content.Context; 20import android.content.Intent; 21import android.content.IntentFilter; 22import android.net.NetworkInfo; 23import android.net.NetworkInfo.DetailedState; 24import android.net.wifi.ScanResult; 25import android.net.wifi.WifiConfiguration; 26import android.net.wifi.WifiInfo; 27import android.net.wifi.WifiManager; 28import android.os.Handler; 29import android.os.Looper; 30import android.os.Message; 31import android.util.Log; 32import android.widget.Toast; 33 34import com.android.internal.annotations.VisibleForTesting; 35import com.android.settingslib.R; 36 37import java.io.PrintWriter; 38import java.util.ArrayList; 39import java.util.Collection; 40import java.util.Collections; 41import java.util.HashMap; 42import java.util.Iterator; 43import java.util.List; 44import java.util.Map; 45import java.util.concurrent.atomic.AtomicBoolean; 46 47/** 48 * Tracks saved or available wifi networks and their state. 49 */ 50public 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 89 public WifiTracker(Context context, WifiListener wifiListener, 90 boolean includeSaved, boolean includeScans) { 91 this(context, wifiListener, null, includeSaved, includeScans); 92 } 93 94 public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper, 95 boolean includeSaved, boolean includeScans) { 96 this(context, wifiListener, workerLooper, includeSaved, includeScans, false); 97 } 98 99 public WifiTracker(Context context, WifiListener wifiListener, 100 boolean includeSaved, boolean includeScans, boolean includePasspoints) { 101 this(context, wifiListener, null, includeSaved, includeScans, includePasspoints); 102 } 103 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 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 */ 148 public void forceUpdate() { 149 updateAccessPoints(); 150 } 151 152 /** 153 * Force a scan for wifi networks to happen now. 154 */ 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 */ 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 */ 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 */ 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 */ 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 */ 218 public List<AccessPoint> getAccessPoints() { 219 synchronized (mAccessPoints) { 220 return new ArrayList<>(mAccessPoints); 221 } 222 } 223 224 public WifiManager getManager() { 225 return mWifiManager; 226 } 227 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 */ 236 public boolean doSavedNetworksExist() { 237 return mSavedNetworksExist; 238 } 239 240 public boolean isConnected() { 241 return mConnected.get(); 242 } 243 244 public void dump(PrintWriter pw) { 245 pw.println(" - wifi tracker ------"); 246 for (AccessPoint accessPoint : getAccessPoints()) { 247 pw.println(" " + accessPoint); 248 } 249 } 250 251 private void handleResume() { 252 mScanResultCache.clear(); 253 mSeenBssids.clear(); 254 mScanId = 0; 255 } 256 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 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 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 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 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 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 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 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 532 public MainHandler(Looper looper) { 533 super(looper); 534 } 535 536 @Override 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 570 public WorkHandler(Looper looper) { 571 super(looper); 572 } 573 574 @Override 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 596 void resume() { 597 if (!hasMessages(MSG_SCAN)) { 598 sendEmptyMessage(MSG_SCAN); 599 } 600 } 601 602 void forceScan() { 603 removeMessages(MSG_SCAN); 604 sendEmptyMessage(MSG_SCAN); 605 } 606 607 void pause() { 608 mRetry = 0; 609 removeMessages(MSG_SCAN); 610 } 611 612 @VisibleForTesting 613 boolean isScanning() { 614 return hasMessages(MSG_SCAN); 615 } 616 617 @Override 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 */ 637 List<V> getAll(K key) { 638 List<V> values = store.get(key); 639 return values != null ? values : Collections.<V>emptyList(); 640 } 641 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 */ 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 */ 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 */ 678 void onAccessPointsChanged(); 679 } 680} 681