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.annotation.MainThread; 19 import android.content.BroadcastReceiver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.IntentFilter; 23 import android.database.ContentObserver; 24 import android.net.ConnectivityManager; 25 import android.net.Network; 26 import android.net.NetworkCapabilities; 27 import android.net.NetworkInfo; 28 import android.net.NetworkInfo.DetailedState; 29 import android.net.NetworkKey; 30 import android.net.NetworkRequest; 31 import android.net.NetworkScoreManager; 32 import android.net.ScoredNetwork; 33 import android.net.wifi.ScanResult; 34 import android.net.wifi.WifiConfiguration; 35 import android.net.wifi.WifiInfo; 36 import android.net.wifi.WifiManager; 37 import android.net.wifi.WifiNetworkScoreCache; 38 import android.net.wifi.WifiNetworkScoreCache.CacheListener; 39 import android.os.ConditionVariable; 40 import android.os.Handler; 41 import android.os.Looper; 42 import android.os.Message; 43 import android.provider.Settings; 44 import android.support.annotation.GuardedBy; 45 import android.util.ArraySet; 46 import android.util.Log; 47 import android.util.SparseArray; 48 import android.util.SparseIntArray; 49 import android.widget.Toast; 50 51 import com.android.internal.annotations.VisibleForTesting; 52 import com.android.settingslib.R; 53 54 import java.io.PrintWriter; 55 import java.util.ArrayList; 56 import java.util.Collection; 57 import java.util.Collections; 58 import java.util.HashMap; 59 import java.util.Iterator; 60 import java.util.List; 61 import java.util.Map; 62 import java.util.Set; 63 import java.util.concurrent.atomic.AtomicBoolean; 64 65 /** 66 * Tracks saved or available wifi networks and their state. 67 */ 68 public class WifiTracker { 69 // TODO(b/36733768): Remove flag includeSaved and includePasspoints. 70 71 private static final String TAG = "WifiTracker"; 72 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 73 74 /** verbose logging flag. this flag is set thru developer debugging options 75 * and used so as to assist with in-the-field WiFi connectivity debugging */ 76 public static int sVerboseLogging = 0; 77 78 // TODO: Allow control of this? 79 // Combo scans can take 5-6s to complete - set to 10s. 80 private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000; 81 private static final int NUM_SCANS_TO_CONFIRM_AP_LOSS = 3; 82 83 private final Context mContext; 84 private final WifiManager mWifiManager; 85 private final IntentFilter mFilter; 86 private final ConnectivityManager mConnectivityManager; 87 private final NetworkRequest mNetworkRequest; 88 private final AtomicBoolean mConnected = new AtomicBoolean(false); 89 private final WifiListener mListener; 90 private final boolean mIncludeSaved; 91 private final boolean mIncludeScans; 92 private final boolean mIncludePasspoints; 93 @VisibleForTesting final MainHandler mMainHandler; 94 private final WorkHandler mWorkHandler; 95 96 private WifiTrackerNetworkCallback mNetworkCallback; 97 98 @GuardedBy("mLock") 99 private boolean mRegistered; 100 101 /** 102 * The externally visible access point list. 103 * 104 * Updated using main handler. Clone of this collection is returned from 105 * {@link #getAccessPoints()} 106 */ 107 private final List<AccessPoint> mAccessPoints = new ArrayList<>(); 108 109 /** 110 * The internal list of access points, synchronized on itself. 111 * 112 * Never exposed outside this class. 113 */ 114 @GuardedBy("mLock") 115 private final List<AccessPoint> mInternalAccessPoints = new ArrayList<>(); 116 117 /** 118 * Synchronization lock for managing concurrency between main and worker threads. 119 * 120 * <p>This lock should be held for all background work. 121 * TODO(b/37674366): Remove the worker thread so synchronization is no longer necessary. 122 */ 123 private final Object mLock = new Object(); 124 125 //visible to both worker and main thread. 126 @GuardedBy("mLock") 127 private final AccessPointListenerAdapter mAccessPointListenerAdapter 128 = new AccessPointListenerAdapter(); 129 130 private final HashMap<String, Integer> mSeenBssids = new HashMap<>(); 131 private final HashMap<String, ScanResult> mScanResultCache = new HashMap<>(); 132 private Integer mScanId = 0; 133 134 private NetworkInfo mLastNetworkInfo; 135 private WifiInfo mLastInfo; 136 137 private final NetworkScoreManager mNetworkScoreManager; 138 private final WifiNetworkScoreCache mScoreCache; 139 140 @GuardedBy("mLock") 141 private final Set<NetworkKey> mRequestedScores = new ArraySet<>(); 142 143 @VisibleForTesting 144 Scanner mScanner; 145 private boolean mStaleScanResults = false; 146 147 public WifiTracker(Context context, WifiListener wifiListener, 148 boolean includeSaved, boolean includeScans) { 149 this(context, wifiListener, null, includeSaved, includeScans); 150 } 151 152 public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper, 153 boolean includeSaved, boolean includeScans) { 154 this(context, wifiListener, workerLooper, includeSaved, includeScans, false); 155 } 156 157 public WifiTracker(Context context, WifiListener wifiListener, 158 boolean includeSaved, boolean includeScans, boolean includePasspoints) { 159 this(context, wifiListener, null, includeSaved, includeScans, includePasspoints); 160 } 161 162 public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper, 163 boolean includeSaved, boolean includeScans, boolean includePasspoints) { 164 this(context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints, 165 context.getSystemService(WifiManager.class), 166 context.getSystemService(ConnectivityManager.class), 167 context.getSystemService(NetworkScoreManager.class), Looper.myLooper() 168 ); 169 } 170 171 @VisibleForTesting 172 WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper, 173 boolean includeSaved, boolean includeScans, boolean includePasspoints, 174 WifiManager wifiManager, ConnectivityManager connectivityManager, 175 NetworkScoreManager networkScoreManager, Looper currentLooper) { 176 if (!includeSaved && !includeScans) { 177 throw new IllegalArgumentException("Must include either saved or scans"); 178 } 179 mContext = context; 180 if (currentLooper == null) { 181 // When we aren't on a looper thread, default to the main. 182 currentLooper = Looper.getMainLooper(); 183 } 184 mMainHandler = new MainHandler(currentLooper); 185 mWorkHandler = new WorkHandler( 186 workerLooper != null ? workerLooper : currentLooper); 187 mWifiManager = wifiManager; 188 mIncludeSaved = includeSaved; 189 mIncludeScans = includeScans; 190 mIncludePasspoints = includePasspoints; 191 mListener = wifiListener; 192 mConnectivityManager = connectivityManager; 193 194 // check if verbose logging has been turned on or off 195 sVerboseLogging = mWifiManager.getVerboseLoggingLevel(); 196 197 mFilter = new IntentFilter(); 198 mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); 199 mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); 200 mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION); 201 mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); 202 mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION); 203 mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION); 204 mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); 205 mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION); 206 207 mNetworkRequest = new NetworkRequest.Builder() 208 .clearCapabilities() 209 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) 210 .build(); 211 212 mNetworkScoreManager = networkScoreManager; 213 214 mScoreCache = new WifiNetworkScoreCache(context, new CacheListener(mWorkHandler) { 215 @Override 216 public void networkCacheUpdated(List<ScoredNetwork> networks) { 217 synchronized (mLock) { 218 if (!mRegistered) return; 219 } 220 221 if (Log.isLoggable(TAG, Log.VERBOSE)) { 222 Log.v(TAG, "Score cache was updated with networks: " + networks); 223 } 224 updateNetworkScores(); 225 } 226 }); 227 } 228 229 /** 230 * Synchronously update the list of access points with the latest information. 231 */ 232 @MainThread 233 public void forceUpdate() { 234 synchronized (mLock) { 235 mWorkHandler.removeMessages(WorkHandler.MSG_UPDATE_ACCESS_POINTS); 236 mLastInfo = mWifiManager.getConnectionInfo(); 237 mLastNetworkInfo = mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork()); 238 updateAccessPointsLocked(); 239 240 if (DBG) { 241 Log.d(TAG, "force update - internal access point list:\n" + mInternalAccessPoints); 242 } 243 244 // Synchronously copy access points 245 mMainHandler.removeMessages(MainHandler.MSG_ACCESS_POINT_CHANGED); 246 mMainHandler.handleMessage( 247 Message.obtain(mMainHandler, MainHandler.MSG_ACCESS_POINT_CHANGED)); 248 if (DBG) { 249 Log.d(TAG, "force update - external access point list:\n" + mAccessPoints); 250 } 251 } 252 } 253 254 /** 255 * Force a scan for wifi networks to happen now. 256 */ 257 public void forceScan() { 258 if (mWifiManager.isWifiEnabled() && mScanner != null) { 259 mScanner.forceScan(); 260 } 261 } 262 263 /** 264 * Temporarily stop scanning for wifi networks. 265 */ 266 public void pauseScanning() { 267 if (mScanner != null) { 268 mScanner.pause(); 269 mScanner = null; 270 } 271 } 272 273 /** 274 * Resume scanning for wifi networks after it has been paused. 275 * 276 * <p>The score cache should be registered before this method is invoked. 277 */ 278 public void resumeScanning() { 279 if (mScanner == null) { 280 mScanner = new Scanner(); 281 } 282 283 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_RESUME); 284 if (mWifiManager.isWifiEnabled()) { 285 mScanner.resume(); 286 } 287 } 288 289 /** 290 * Start tracking wifi networks and scores. 291 * 292 * <p>Registers listeners and starts scanning for wifi networks. If this is not called 293 * then forceUpdate() must be called to populate getAccessPoints(). 294 */ 295 @MainThread 296 public void startTracking() { 297 synchronized (mLock) { 298 registerScoreCache(); 299 300 resumeScanning(); 301 if (!mRegistered) { 302 mContext.registerReceiver(mReceiver, mFilter); 303 // NetworkCallback objects cannot be reused. http://b/20701525 . 304 mNetworkCallback = new WifiTrackerNetworkCallback(); 305 mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback); 306 mRegistered = true; 307 } 308 } 309 } 310 311 private void registerScoreCache() { 312 mNetworkScoreManager.registerNetworkScoreCache( 313 NetworkKey.TYPE_WIFI, 314 mScoreCache, 315 NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS); 316 } 317 318 private void requestScoresForNetworkKeys(Collection<NetworkKey> keys) { 319 if (keys.isEmpty()) return; 320 321 if (DBG) { 322 Log.d(TAG, "Requesting scores for Network Keys: " + keys); 323 } 324 mNetworkScoreManager.requestScores(keys.toArray(new NetworkKey[keys.size()])); 325 synchronized (mLock) { 326 mRequestedScores.addAll(keys); 327 } 328 } 329 330 /** 331 * Stop tracking wifi networks and scores. 332 * 333 * <p>This should always be called when done with a WifiTracker (if startTracking was called) to 334 * ensure proper cleanup and prevent any further callbacks from occurring. 335 * 336 * <p>Calling this method will set the {@link #mStaleScanResults} bit, which prevents 337 * {@link WifiListener#onAccessPointsChanged()} callbacks from being invoked (until the bit 338 * is unset on the next SCAN_RESULTS_AVAILABLE_ACTION). 339 */ 340 @MainThread 341 public void stopTracking() { 342 synchronized (mLock) { 343 if (mRegistered) { 344 mContext.unregisterReceiver(mReceiver); 345 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); 346 mRegistered = false; 347 } 348 unregisterAndClearScoreCache(); 349 pauseScanning(); 350 351 mWorkHandler.removePendingMessages(); 352 mMainHandler.removePendingMessages(); 353 } 354 mStaleScanResults = true; 355 } 356 357 private void unregisterAndClearScoreCache() { 358 mNetworkScoreManager.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, mScoreCache); 359 mScoreCache.clearScores(); 360 361 // Synchronize on mLock to avoid concurrent modification during updateAccessPointsLocked 362 synchronized (mLock) { 363 mRequestedScores.clear(); 364 } 365 } 366 367 /** 368 * Gets the current list of access points. Should be called from main thread, otherwise 369 * expect inconsistencies 370 */ 371 @MainThread 372 public List<AccessPoint> getAccessPoints() { 373 return new ArrayList<>(mAccessPoints); 374 } 375 376 public WifiManager getManager() { 377 return mWifiManager; 378 } 379 380 public boolean isWifiEnabled() { 381 return mWifiManager.isWifiEnabled(); 382 } 383 384 /** 385 * Returns the number of saved networks on the device, regardless of whether the WifiTracker 386 * is tracking saved networks. 387 * TODO(b/62292448): remove this function and update callsites to use WifiSavedConfigUtils 388 * directly. 389 */ 390 public int getNumSavedNetworks() { 391 return WifiSavedConfigUtils.getAllConfigs(mContext, mWifiManager).size(); 392 } 393 394 public boolean isConnected() { 395 return mConnected.get(); 396 } 397 398 public void dump(PrintWriter pw) { 399 pw.println(" - wifi tracker ------"); 400 for (AccessPoint accessPoint : getAccessPoints()) { 401 pw.println(" " + accessPoint); 402 } 403 } 404 405 private void handleResume() { 406 mScanResultCache.clear(); 407 mSeenBssids.clear(); 408 mScanId = 0; 409 } 410 411 private Collection<ScanResult> fetchScanResults() { 412 mScanId++; 413 final List<ScanResult> newResults = mWifiManager.getScanResults(); 414 for (ScanResult newResult : newResults) { 415 if (newResult.SSID == null || newResult.SSID.isEmpty()) { 416 continue; 417 } 418 mScanResultCache.put(newResult.BSSID, newResult); 419 mSeenBssids.put(newResult.BSSID, mScanId); 420 } 421 422 if (mScanId > NUM_SCANS_TO_CONFIRM_AP_LOSS) { 423 if (DBG) Log.d(TAG, "------ Dumping SSIDs that were expired on this scan ------"); 424 Integer threshold = mScanId - NUM_SCANS_TO_CONFIRM_AP_LOSS; 425 for (Iterator<Map.Entry<String, Integer>> it = mSeenBssids.entrySet().iterator(); 426 it.hasNext(); /* nothing */) { 427 Map.Entry<String, Integer> e = it.next(); 428 if (e.getValue() < threshold) { 429 ScanResult result = mScanResultCache.get(e.getKey()); 430 if (DBG) Log.d(TAG, "Removing " + e.getKey() + ":(" + result.SSID + ")"); 431 mScanResultCache.remove(e.getKey()); 432 it.remove(); 433 } 434 } 435 if (DBG) Log.d(TAG, "---- Done Dumping SSIDs that were expired on this scan ----"); 436 } 437 438 return mScanResultCache.values(); 439 } 440 441 private WifiConfiguration getWifiConfigurationForNetworkId(int networkId) { 442 final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks(); 443 if (configs != null) { 444 for (WifiConfiguration config : configs) { 445 if (mLastInfo != null && networkId == config.networkId && 446 !(config.selfAdded && config.numAssociation == 0)) { 447 return config; 448 } 449 } 450 } 451 return null; 452 } 453 454 /** Safely modify {@link #mInternalAccessPoints} by acquiring {@link #mLock} first. */ 455 private void updateAccessPointsLocked() { 456 synchronized (mLock) { 457 updateAccessPoints(); 458 } 459 } 460 461 /** 462 * Update the internal list of access points. 463 * 464 * <p>Should never be called directly, use {@link #updateAccessPointsLocked()} instead. 465 */ 466 @GuardedBy("mLock") 467 private void updateAccessPoints() { 468 // Swap the current access points into a cached list. 469 List<AccessPoint> cachedAccessPoints = new ArrayList<>(mInternalAccessPoints); 470 ArrayList<AccessPoint> accessPoints = new ArrayList<>(); 471 472 // Clear out the configs so we don't think something is saved when it isn't. 473 for (AccessPoint accessPoint : cachedAccessPoints) { 474 accessPoint.clearConfig(); 475 } 476 477 /* Lookup table to more quickly update AccessPoints by only considering objects with the 478 * correct SSID. Maps SSID -> List of AccessPoints with the given SSID. */ 479 Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>(); 480 WifiConfiguration connectionConfig = null; 481 if (mLastInfo != null) { 482 connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId()); 483 } 484 485 final Collection<ScanResult> results = fetchScanResults(); 486 487 final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks(); 488 if (configs != null) { 489 for (WifiConfiguration config : configs) { 490 if (config.selfAdded && config.numAssociation == 0) { 491 continue; 492 } 493 AccessPoint accessPoint = getCachedOrCreate(config, cachedAccessPoints); 494 if (mLastInfo != null && mLastNetworkInfo != null) { 495 accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo); 496 } 497 if (mIncludeSaved) { 498 // If saved network not present in scan result then set its Rssi to 499 // UNREACHABLE_RSSI 500 boolean apFound = false; 501 for (ScanResult result : results) { 502 if (result.SSID.equals(accessPoint.getSsidStr())) { 503 apFound = true; 504 break; 505 } 506 } 507 if (!apFound) { 508 accessPoint.setUnreachable(); 509 } 510 accessPoints.add(accessPoint); 511 apMap.put(accessPoint.getSsidStr(), accessPoint); 512 } else { 513 // If we aren't using saved networks, drop them into the cache so that 514 // we have access to their saved info. 515 cachedAccessPoints.add(accessPoint); 516 } 517 } 518 } 519 520 final List<NetworkKey> scoresToRequest = new ArrayList<>(); 521 if (results != null) { 522 for (ScanResult result : results) { 523 // Ignore hidden and ad-hoc networks. 524 if (result.SSID == null || result.SSID.length() == 0 || 525 result.capabilities.contains("[IBSS]")) { 526 continue; 527 } 528 529 NetworkKey key = NetworkKey.createFromScanResult(result); 530 if (key != null && !mRequestedScores.contains(key)) { 531 scoresToRequest.add(key); 532 } 533 534 boolean found = false; 535 for (AccessPoint accessPoint : apMap.getAll(result.SSID)) { 536 if (accessPoint.update(result)) { 537 found = true; 538 break; 539 } 540 } 541 if (!found && mIncludeScans) { 542 AccessPoint accessPoint = getCachedOrCreate(result, cachedAccessPoints); 543 if (mLastInfo != null && mLastNetworkInfo != null) { 544 accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo); 545 } 546 547 if (result.isPasspointNetwork()) { 548 // Retrieve a WifiConfiguration for a Passpoint provider that matches 549 // the given ScanResult. This is used for showing that a given AP 550 // (ScanResult) is available via a Passpoint provider (provider friendly 551 // name). 552 try { 553 WifiConfiguration config = mWifiManager.getMatchingWifiConfig(result); 554 if (config != null) { 555 accessPoint.update(config); 556 } 557 } catch (UnsupportedOperationException e) { 558 // Passpoint not supported on the device. 559 } 560 } 561 562 accessPoints.add(accessPoint); 563 apMap.put(accessPoint.getSsidStr(), accessPoint); 564 } 565 } 566 } 567 568 requestScoresForNetworkKeys(scoresToRequest); 569 for (AccessPoint ap : accessPoints) { 570 ap.update(mScoreCache, false /* mNetworkScoringUiEnabled */); 571 } 572 573 // Pre-sort accessPoints to speed preference insertion 574 Collections.sort(accessPoints); 575 576 // Log accesspoints that were deleted 577 if (DBG) { 578 Log.d(TAG, "------ Dumping SSIDs that were not seen on this scan ------"); 579 for (AccessPoint prevAccessPoint : mInternalAccessPoints) { 580 if (prevAccessPoint.getSsid() == null) 581 continue; 582 String prevSsid = prevAccessPoint.getSsidStr(); 583 boolean found = false; 584 for (AccessPoint newAccessPoint : accessPoints) { 585 if (newAccessPoint.getSsid() != null && newAccessPoint.getSsid() 586 .equals(prevSsid)) { 587 found = true; 588 break; 589 } 590 } 591 if (!found) 592 Log.d(TAG, "Did not find " + prevSsid + " in this scan"); 593 } 594 Log.d(TAG, "---- Done dumping SSIDs that were not seen on this scan ----"); 595 } 596 597 mInternalAccessPoints.clear(); 598 mInternalAccessPoints.addAll(accessPoints); 599 600 mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED); 601 } 602 603 @VisibleForTesting 604 AccessPoint getCachedOrCreate(ScanResult result, List<AccessPoint> cache) { 605 final int N = cache.size(); 606 for (int i = 0; i < N; i++) { 607 if (cache.get(i).matches(result)) { 608 AccessPoint ret = cache.remove(i); 609 ret.update(result); 610 return ret; 611 } 612 } 613 final AccessPoint accessPoint = new AccessPoint(mContext, result); 614 accessPoint.setListener(mAccessPointListenerAdapter); 615 return accessPoint; 616 } 617 618 @VisibleForTesting 619 AccessPoint getCachedOrCreate(WifiConfiguration config, List<AccessPoint> cache) { 620 final int N = cache.size(); 621 for (int i = 0; i < N; i++) { 622 if (cache.get(i).matches(config)) { 623 AccessPoint ret = cache.remove(i); 624 ret.loadConfig(config); 625 return ret; 626 } 627 } 628 final AccessPoint accessPoint = new AccessPoint(mContext, config); 629 accessPoint.setListener(mAccessPointListenerAdapter); 630 return accessPoint; 631 } 632 633 private void updateNetworkInfo(NetworkInfo networkInfo) { 634 /* sticky broadcasts can call this when wifi is disabled */ 635 if (!mWifiManager.isWifiEnabled()) { 636 mMainHandler.sendEmptyMessage(MainHandler.MSG_PAUSE_SCANNING); 637 return; 638 } 639 640 if (networkInfo != null && 641 networkInfo.getDetailedState() == DetailedState.OBTAINING_IPADDR) { 642 mMainHandler.sendEmptyMessage(MainHandler.MSG_PAUSE_SCANNING); 643 } else { 644 mMainHandler.sendEmptyMessage(MainHandler.MSG_RESUME_SCANNING); 645 } 646 647 if (networkInfo != null) { 648 mLastNetworkInfo = networkInfo; 649 } 650 651 WifiConfiguration connectionConfig = null; 652 mLastInfo = mWifiManager.getConnectionInfo(); 653 if (mLastInfo != null) { 654 connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId()); 655 } 656 657 boolean updated = false; 658 boolean reorder = false; // Only reorder if connected AP was changed 659 660 synchronized (mLock) { 661 for (int i = mInternalAccessPoints.size() - 1; i >= 0; --i) { 662 AccessPoint ap = mInternalAccessPoints.get(i); 663 boolean previouslyConnected = ap.isActive(); 664 if (ap.update(connectionConfig, mLastInfo, mLastNetworkInfo)) { 665 updated = true; 666 if (previouslyConnected != ap.isActive()) reorder = true; 667 } 668 if (ap.update(mScoreCache, false /* mNetworkScoringUiEnabled */)) { 669 reorder = true; 670 updated = true; 671 } 672 } 673 674 if (reorder) Collections.sort(mInternalAccessPoints); 675 676 if (updated) mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED); 677 } 678 } 679 680 /** 681 * Update all the internal access points rankingScores, badge and metering. 682 * 683 * <p>Will trigger a resort and notify listeners of changes if applicable. 684 * 685 * <p>Synchronized on {@link #mLock}. 686 */ 687 private void updateNetworkScores() { 688 synchronized (mLock) { 689 boolean updated = false; 690 for (int i = 0; i < mInternalAccessPoints.size(); i++) { 691 if (mInternalAccessPoints.get(i).update( 692 mScoreCache, false /* mNetworkScoringUiEnabled */)) { 693 updated = true; 694 } 695 } 696 if (updated) { 697 Collections.sort(mInternalAccessPoints); 698 mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED); 699 } 700 } 701 } 702 703 private void updateWifiState(int state) { 704 mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_WIFI_STATE, state, 0).sendToTarget(); 705 } 706 707 public static List<AccessPoint> getCurrentAccessPoints(Context context, boolean includeSaved, 708 boolean includeScans, boolean includePasspoints) { 709 WifiTracker tracker = new WifiTracker(context, 710 null, null, includeSaved, includeScans, includePasspoints); 711 tracker.forceUpdate(); 712 tracker.copyAndNotifyListeners(false /*notifyListeners*/); 713 return tracker.getAccessPoints(); 714 } 715 716 @VisibleForTesting 717 final BroadcastReceiver mReceiver = new BroadcastReceiver() { 718 @Override 719 public void onReceive(Context context, Intent intent) { 720 String action = intent.getAction(); 721 722 if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) { 723 mStaleScanResults = false; 724 } 725 726 if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { 727 updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 728 WifiManager.WIFI_STATE_UNKNOWN)); 729 } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) || 730 WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) || 731 WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) { 732 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS); 733 } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) { 734 NetworkInfo info = (NetworkInfo) intent.getParcelableExtra( 735 WifiManager.EXTRA_NETWORK_INFO); 736 mConnected.set(info.isConnected()); 737 738 mMainHandler.sendEmptyMessage(MainHandler.MSG_CONNECTED_CHANGED); 739 740 mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO, info) 741 .sendToTarget(); 742 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS); 743 } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) { 744 NetworkInfo info = 745 mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork()); 746 mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO, info) 747 .sendToTarget(); 748 } 749 } 750 }; 751 752 private final class WifiTrackerNetworkCallback extends ConnectivityManager.NetworkCallback { 753 public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) { 754 if (network.equals(mWifiManager.getCurrentNetwork())) { 755 // We don't send a NetworkInfo object along with this message, because even if we 756 // fetch one from ConnectivityManager, it might be older than the most recent 757 // NetworkInfo message we got via a WIFI_STATE_CHANGED broadcast. 758 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO); 759 } 760 } 761 } 762 763 @VisibleForTesting 764 final class MainHandler extends Handler { 765 @VisibleForTesting static final int MSG_CONNECTED_CHANGED = 0; 766 @VisibleForTesting static final int MSG_WIFI_STATE_CHANGED = 1; 767 @VisibleForTesting static final int MSG_ACCESS_POINT_CHANGED = 2; 768 private static final int MSG_RESUME_SCANNING = 3; 769 private static final int MSG_PAUSE_SCANNING = 4; 770 771 public MainHandler(Looper looper) { 772 super(looper); 773 } 774 775 @Override 776 public void handleMessage(Message msg) { 777 if (mListener == null) { 778 return; 779 } 780 switch (msg.what) { 781 case MSG_CONNECTED_CHANGED: 782 mListener.onConnectedChanged(); 783 break; 784 case MSG_WIFI_STATE_CHANGED: 785 mListener.onWifiStateChanged(msg.arg1); 786 break; 787 case MSG_ACCESS_POINT_CHANGED: 788 copyAndNotifyListeners(true /*notifyListeners*/); 789 mListener.onAccessPointsChanged(); 790 break; 791 case MSG_RESUME_SCANNING: 792 if (mScanner != null) { 793 mScanner.resume(); 794 } 795 break; 796 case MSG_PAUSE_SCANNING: 797 if (mScanner != null) { 798 mScanner.pause(); 799 } 800 break; 801 } 802 } 803 804 void removePendingMessages() { 805 removeMessages(MSG_ACCESS_POINT_CHANGED); 806 removeMessages(MSG_CONNECTED_CHANGED); 807 removeMessages(MSG_WIFI_STATE_CHANGED); 808 removeMessages(MSG_PAUSE_SCANNING); 809 removeMessages(MSG_RESUME_SCANNING); 810 } 811 } 812 813 private final class WorkHandler extends Handler { 814 private static final int MSG_UPDATE_ACCESS_POINTS = 0; 815 private static final int MSG_UPDATE_NETWORK_INFO = 1; 816 private static final int MSG_RESUME = 2; 817 private static final int MSG_UPDATE_WIFI_STATE = 3; 818 819 public WorkHandler(Looper looper) { 820 super(looper); 821 } 822 823 @Override 824 public void handleMessage(Message msg) { 825 synchronized (mLock) { 826 processMessage(msg); 827 } 828 } 829 830 @GuardedBy("mLock") 831 private void processMessage(Message msg) { 832 if (!mRegistered) return; 833 834 switch (msg.what) { 835 case MSG_UPDATE_ACCESS_POINTS: 836 if (!mStaleScanResults) { 837 updateAccessPointsLocked(); 838 } 839 break; 840 case MSG_UPDATE_NETWORK_INFO: 841 updateNetworkInfo((NetworkInfo) msg.obj); 842 break; 843 case MSG_RESUME: 844 handleResume(); 845 break; 846 case MSG_UPDATE_WIFI_STATE: 847 if (msg.arg1 == WifiManager.WIFI_STATE_ENABLED) { 848 if (mScanner != null) { 849 // We only need to resume if mScanner isn't null because 850 // that means we want to be scanning. 851 mScanner.resume(); 852 } 853 } else { 854 mLastInfo = null; 855 mLastNetworkInfo = null; 856 if (mScanner != null) { 857 mScanner.pause(); 858 } 859 } 860 mMainHandler.obtainMessage(MainHandler.MSG_WIFI_STATE_CHANGED, msg.arg1, 0) 861 .sendToTarget(); 862 break; 863 } 864 } 865 866 private void removePendingMessages() { 867 removeMessages(MSG_UPDATE_ACCESS_POINTS); 868 removeMessages(MSG_UPDATE_NETWORK_INFO); 869 removeMessages(MSG_RESUME); 870 removeMessages(MSG_UPDATE_WIFI_STATE); 871 } 872 } 873 874 @VisibleForTesting 875 class Scanner extends Handler { 876 static final int MSG_SCAN = 0; 877 878 private int mRetry = 0; 879 880 void resume() { 881 if (!hasMessages(MSG_SCAN)) { 882 sendEmptyMessage(MSG_SCAN); 883 } 884 } 885 886 void forceScan() { 887 removeMessages(MSG_SCAN); 888 sendEmptyMessage(MSG_SCAN); 889 } 890 891 void pause() { 892 mRetry = 0; 893 removeMessages(MSG_SCAN); 894 } 895 896 @VisibleForTesting 897 boolean isScanning() { 898 return hasMessages(MSG_SCAN); 899 } 900 901 @Override 902 public void handleMessage(Message message) { 903 if (message.what != MSG_SCAN) return; 904 if (mWifiManager.startScan()) { 905 mRetry = 0; 906 } else if (++mRetry >= 3) { 907 mRetry = 0; 908 if (mContext != null) { 909 Toast.makeText(mContext, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show(); 910 } 911 return; 912 } 913 sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS); 914 } 915 } 916 917 /** A restricted multimap for use in constructAccessPoints */ 918 private static class Multimap<K,V> { 919 private final HashMap<K,List<V>> store = new HashMap<K,List<V>>(); 920 /** retrieve a non-null list of values with key K */ 921 List<V> getAll(K key) { 922 List<V> values = store.get(key); 923 return values != null ? values : Collections.<V>emptyList(); 924 } 925 926 void put(K key, V val) { 927 List<V> curVals = store.get(key); 928 if (curVals == null) { 929 curVals = new ArrayList<V>(3); 930 store.put(key, curVals); 931 } 932 curVals.add(val); 933 } 934 } 935 936 public interface WifiListener { 937 /** 938 * Called when the state of Wifi has changed, the state will be one of 939 * the following. 940 * 941 * <li>{@link WifiManager#WIFI_STATE_DISABLED}</li> 942 * <li>{@link WifiManager#WIFI_STATE_ENABLED}</li> 943 * <li>{@link WifiManager#WIFI_STATE_DISABLING}</li> 944 * <li>{@link WifiManager#WIFI_STATE_ENABLING}</li> 945 * <li>{@link WifiManager#WIFI_STATE_UNKNOWN}</li> 946 * <p> 947 * 948 * @param state The new state of wifi. 949 */ 950 void onWifiStateChanged(int state); 951 952 /** 953 * Called when the connection state of wifi has changed and isConnected 954 * should be called to get the updated state. 955 */ 956 void onConnectedChanged(); 957 958 /** 959 * Called to indicate the list of AccessPoints has been updated and 960 * getAccessPoints should be called to get the latest information. 961 */ 962 void onAccessPointsChanged(); 963 } 964 965 /** 966 * Helps capture notifications that were generated during AccessPoint modification. Used later 967 * on by {@link #copyAndNotifyListeners(boolean)} to send notifications. 968 */ 969 private static class AccessPointListenerAdapter implements AccessPoint.AccessPointListener { 970 static final int AP_CHANGED = 1; 971 static final int LEVEL_CHANGED = 2; 972 973 final SparseIntArray mPendingNotifications = new SparseIntArray(); 974 975 @Override 976 public void onAccessPointChanged(AccessPoint accessPoint) { 977 int type = mPendingNotifications.get(accessPoint.mId); 978 mPendingNotifications.put(accessPoint.mId, type | AP_CHANGED); 979 } 980 981 @Override 982 public void onLevelChanged(AccessPoint accessPoint) { 983 int type = mPendingNotifications.get(accessPoint.mId); 984 mPendingNotifications.put(accessPoint.mId, type | LEVEL_CHANGED); 985 } 986 } 987 988 /** 989 * Responsible for copying access points from {@link #mInternalAccessPoints} and notifying 990 * accesspoint listeners. 991 * 992 * @param notifyListeners if true, accesspoint listeners are notified, otherwise notifications 993 * dropped. 994 */ 995 @MainThread 996 private void copyAndNotifyListeners(boolean notifyListeners) { 997 // Need to watch out for memory allocations on main thread. 998 SparseArray<AccessPoint> oldAccessPoints = new SparseArray<>(); 999 SparseIntArray notificationMap = null; 1000 List<AccessPoint> updatedAccessPoints = new ArrayList<>(); 1001 1002 for (AccessPoint accessPoint : mAccessPoints) { 1003 oldAccessPoints.put(accessPoint.mId, accessPoint); 1004 } 1005 1006 if (DBG) { 1007 Log.d(TAG, "Starting to copy AP items on the MainHandler"); 1008 } 1009 synchronized (mLock) { 1010 if (notifyListeners) { 1011 notificationMap = mAccessPointListenerAdapter.mPendingNotifications.clone(); 1012 } 1013 1014 mAccessPointListenerAdapter.mPendingNotifications.clear(); 1015 1016 for (AccessPoint internalAccessPoint : mInternalAccessPoints) { 1017 AccessPoint accessPoint = oldAccessPoints.get(internalAccessPoint.mId); 1018 if (accessPoint == null) { 1019 accessPoint = new AccessPoint(mContext, internalAccessPoint); 1020 } else { 1021 accessPoint.copyFrom(internalAccessPoint); 1022 } 1023 updatedAccessPoints.add(accessPoint); 1024 } 1025 } 1026 1027 mAccessPoints.clear(); 1028 mAccessPoints.addAll(updatedAccessPoints); 1029 1030 if (notificationMap != null && notificationMap.size() > 0) { 1031 for (AccessPoint accessPoint : updatedAccessPoints) { 1032 int notificationType = notificationMap.get(accessPoint.mId); 1033 AccessPoint.AccessPointListener listener = accessPoint.mAccessPointListener; 1034 if (notificationType == 0 || listener == null) { 1035 continue; 1036 } 1037 1038 if ((notificationType & AccessPointListenerAdapter.AP_CHANGED) != 0) { 1039 listener.onAccessPointChanged(accessPoint); 1040 } 1041 1042 if ((notificationType & AccessPointListenerAdapter.LEVEL_CHANGED) != 0) { 1043 listener.onLevelChanged(accessPoint); 1044 } 1045 } 1046 } 1047 } 1048 } 1049