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 WifiTracker(Context context, WifiListener wifiListener, boolean includeSaved, boolean includeScans)147 public WifiTracker(Context context, WifiListener wifiListener, 148 boolean includeSaved, boolean includeScans) { 149 this(context, wifiListener, null, includeSaved, includeScans); 150 } 151 WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper, boolean includeSaved, boolean includeScans)152 public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper, 153 boolean includeSaved, boolean includeScans) { 154 this(context, wifiListener, workerLooper, includeSaved, includeScans, false); 155 } 156 WifiTracker(Context context, WifiListener wifiListener, boolean includeSaved, boolean includeScans, boolean includePasspoints)157 public WifiTracker(Context context, WifiListener wifiListener, 158 boolean includeSaved, boolean includeScans, boolean includePasspoints) { 159 this(context, wifiListener, null, includeSaved, includeScans, includePasspoints); 160 } 161 WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper, boolean includeSaved, boolean includeScans, boolean includePasspoints)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 WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper, boolean includeSaved, boolean includeScans, boolean includePasspoints, WifiManager wifiManager, ConnectivityManager connectivityManager, NetworkScoreManager networkScoreManager, Looper currentLooper)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 forceUpdate()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 */ forceScan()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 */ pauseScanning()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 */ resumeScanning()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 startTracking()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 registerScoreCache()311 private void registerScoreCache() { 312 mNetworkScoreManager.registerNetworkScoreCache( 313 NetworkKey.TYPE_WIFI, 314 mScoreCache, 315 NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS); 316 } 317 requestScoresForNetworkKeys(Collection<NetworkKey> keys)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 stopTracking()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 unregisterAndClearScoreCache()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 getAccessPoints()372 public List<AccessPoint> getAccessPoints() { 373 return new ArrayList<>(mAccessPoints); 374 } 375 getManager()376 public WifiManager getManager() { 377 return mWifiManager; 378 } 379 isWifiEnabled()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 */ getNumSavedNetworks()390 public int getNumSavedNetworks() { 391 return WifiSavedConfigUtils.getAllConfigs(mContext, mWifiManager).size(); 392 } 393 isConnected()394 public boolean isConnected() { 395 return mConnected.get(); 396 } 397 dump(PrintWriter pw)398 public void dump(PrintWriter pw) { 399 pw.println(" - wifi tracker ------"); 400 for (AccessPoint accessPoint : getAccessPoints()) { 401 pw.println(" " + accessPoint); 402 } 403 } 404 handleResume()405 private void handleResume() { 406 mScanResultCache.clear(); 407 mSeenBssids.clear(); 408 mScanId = 0; 409 } 410 fetchScanResults()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 getWifiConfigurationForNetworkId(int networkId)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. */ updateAccessPointsLocked()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") updateAccessPoints()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 getCachedOrCreate(ScanResult result, List<AccessPoint> cache)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 getCachedOrCreate(WifiConfiguration config, List<AccessPoint> cache)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 updateNetworkInfo(NetworkInfo networkInfo)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 */ updateNetworkScores()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 updateWifiState(int state)703 private void updateWifiState(int state) { 704 mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_WIFI_STATE, state, 0).sendToTarget(); 705 } 706 getCurrentAccessPoints(Context context, boolean includeSaved, boolean includeScans, boolean includePasspoints)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 { onCapabilitiesChanged(Network network, NetworkCapabilities nc)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 MainHandler(Looper looper)771 public MainHandler(Looper looper) { 772 super(looper); 773 } 774 775 @Override handleMessage(Message msg)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 removePendingMessages()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 WorkHandler(Looper looper)819 public WorkHandler(Looper looper) { 820 super(looper); 821 } 822 823 @Override handleMessage(Message msg)824 public void handleMessage(Message msg) { 825 synchronized (mLock) { 826 processMessage(msg); 827 } 828 } 829 830 @GuardedBy("mLock") processMessage(Message msg)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 removePendingMessages()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 resume()880 void resume() { 881 if (!hasMessages(MSG_SCAN)) { 882 sendEmptyMessage(MSG_SCAN); 883 } 884 } 885 forceScan()886 void forceScan() { 887 removeMessages(MSG_SCAN); 888 sendEmptyMessage(MSG_SCAN); 889 } 890 pause()891 void pause() { 892 mRetry = 0; 893 removeMessages(MSG_SCAN); 894 } 895 896 @VisibleForTesting isScanning()897 boolean isScanning() { 898 return hasMessages(MSG_SCAN); 899 } 900 901 @Override handleMessage(Message message)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 */ getAll(K key)921 List<V> getAll(K key) { 922 List<V> values = store.get(key); 923 return values != null ? values : Collections.<V>emptyList(); 924 } 925 put(K key, V val)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 */ onWifiStateChanged(int state)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 */ onConnectedChanged()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 */ onAccessPointsChanged()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 onAccessPointChanged(AccessPoint accessPoint)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 onLevelChanged(AccessPoint accessPoint)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 copyAndNotifyListeners(boolean notifyListeners)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