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.AnyThread; 19 import android.annotation.MainThread; 20 import android.content.BroadcastReceiver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.net.ConnectivityManager; 25 import android.net.Network; 26 import android.net.NetworkCapabilities; 27 import android.net.NetworkInfo; 28 import android.net.NetworkKey; 29 import android.net.NetworkRequest; 30 import android.net.NetworkScoreManager; 31 import android.net.ScoredNetwork; 32 import android.net.wifi.ScanResult; 33 import android.net.wifi.WifiConfiguration; 34 import android.net.wifi.WifiInfo; 35 import android.net.wifi.WifiManager; 36 import android.net.wifi.WifiNetworkScoreCache; 37 import android.net.wifi.WifiNetworkScoreCache.CacheListener; 38 import android.net.wifi.hotspot2.OsuProvider; 39 import android.os.Handler; 40 import android.os.HandlerThread; 41 import android.os.Message; 42 import android.os.Process; 43 import android.os.SystemClock; 44 import android.provider.Settings; 45 import android.text.format.DateUtils; 46 import android.util.ArrayMap; 47 import android.util.ArraySet; 48 import android.util.Log; 49 import android.util.Pair; 50 import android.widget.Toast; 51 52 import androidx.annotation.GuardedBy; 53 import androidx.annotation.NonNull; 54 import androidx.annotation.VisibleForTesting; 55 56 import com.android.settingslib.R; 57 import com.android.settingslib.core.lifecycle.Lifecycle; 58 import com.android.settingslib.core.lifecycle.LifecycleObserver; 59 import com.android.settingslib.core.lifecycle.events.OnDestroy; 60 import com.android.settingslib.core.lifecycle.events.OnStart; 61 import com.android.settingslib.core.lifecycle.events.OnStop; 62 import com.android.settingslib.utils.ThreadUtils; 63 64 import java.io.PrintWriter; 65 import java.util.ArrayList; 66 import java.util.Collection; 67 import java.util.Collections; 68 import java.util.HashMap; 69 import java.util.Iterator; 70 import java.util.List; 71 import java.util.ListIterator; 72 import java.util.Map; 73 import java.util.Optional; 74 import java.util.Set; 75 import java.util.concurrent.atomic.AtomicBoolean; 76 import java.util.stream.Collectors; 77 78 /** 79 * Tracks saved or available wifi networks and their state. 80 */ 81 public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestroy { 82 /** 83 * Default maximum age in millis of cached scored networks in 84 * {@link AccessPoint#mScoredNetworkCache} to be used for speed label generation. 85 */ 86 private static final long DEFAULT_MAX_CACHED_SCORE_AGE_MILLIS = 20 * DateUtils.MINUTE_IN_MILLIS; 87 88 /** Maximum age of scan results to hold onto while actively scanning. **/ 89 @VisibleForTesting static final long MAX_SCAN_RESULT_AGE_MILLIS = 15000; 90 91 private static final String TAG = "WifiTracker"; DBG()92 private static final boolean DBG() { 93 return Log.isLoggable(TAG, Log.DEBUG); 94 } 95 isVerboseLoggingEnabled()96 private static boolean isVerboseLoggingEnabled() { 97 return WifiTracker.sVerboseLogging || Log.isLoggable(TAG, Log.VERBOSE); 98 } 99 100 /** 101 * Verbose logging flag set thru developer debugging options and used so as to assist with 102 * in-the-field WiFi connectivity debugging. 103 * 104 * <p>{@link #isVerboseLoggingEnabled()} should be read rather than referencing this value 105 * directly, to ensure adb TAG level verbose settings are respected. 106 */ 107 public static boolean sVerboseLogging; 108 109 // TODO: Allow control of this? 110 // Combo scans can take 5-6s to complete - set to 10s. 111 private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000; 112 113 private final Context mContext; 114 private final WifiManager mWifiManager; 115 private final IntentFilter mFilter; 116 private final ConnectivityManager mConnectivityManager; 117 private final NetworkRequest mNetworkRequest; 118 private final AtomicBoolean mConnected = new AtomicBoolean(false); 119 private final WifiListenerExecutor mListener; 120 @VisibleForTesting Handler mWorkHandler; 121 private HandlerThread mWorkThread; 122 123 private WifiTrackerNetworkCallback mNetworkCallback; 124 125 /** 126 * Synchronization lock for managing concurrency between main and worker threads. 127 * 128 * <p>This lock should be held for all modifications to {@link #mInternalAccessPoints} and 129 * {@link #mScanner}. 130 */ 131 private final Object mLock = new Object(); 132 133 /** The list of AccessPoints, aggregated visible ScanResults with metadata. */ 134 @GuardedBy("mLock") 135 private final List<AccessPoint> mInternalAccessPoints = new ArrayList<>(); 136 137 @GuardedBy("mLock") 138 private final Set<NetworkKey> mRequestedScores = new ArraySet<>(); 139 140 /** 141 * Tracks whether fresh scan results have been received since scanning start. 142 * 143 * <p>If this variable is false, we will not invoke callbacks so that we do not 144 * update the UI with stale data / clear out existing UI elements prematurely. 145 */ 146 private boolean mStaleScanResults = true; 147 148 /** 149 * Tracks whether the latest SCAN_RESULTS_AVAILABLE_ACTION contained new scans. If not, then 150 * we treat the last scan as an aborted scan and increase the eviction timeout window to avoid 151 * completely flushing the AP list before the next successful scan completes. 152 */ 153 private boolean mLastScanSucceeded = true; 154 155 // Does not need to be locked as it only updated on the worker thread, with the exception of 156 // during onStart, which occurs before the receiver is registered on the work handler. 157 private final HashMap<String, ScanResult> mScanResultCache = new HashMap<>(); 158 private boolean mRegistered; 159 160 private NetworkInfo mLastNetworkInfo; 161 private WifiInfo mLastInfo; 162 163 private final NetworkScoreManager mNetworkScoreManager; 164 private WifiNetworkScoreCache mScoreCache; 165 private boolean mNetworkScoringUiEnabled; 166 private long mMaxSpeedLabelScoreCacheAge; 167 168 private static final String WIFI_SECURITY_PSK = "PSK"; 169 private static final String WIFI_SECURITY_EAP = "EAP"; 170 private static final String WIFI_SECURITY_SAE = "SAE"; 171 private static final String WIFI_SECURITY_OWE = "OWE"; 172 private static final String WIFI_SECURITY_SUITE_B_192 = "SUITE_B_192"; 173 174 @GuardedBy("mLock") 175 @VisibleForTesting 176 Scanner mScanner; 177 newIntentFilter()178 private static IntentFilter newIntentFilter() { 179 IntentFilter filter = new IntentFilter(); 180 filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); 181 filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); 182 filter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION); 183 filter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); 184 filter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION); 185 filter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION); 186 filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); 187 filter.addAction(WifiManager.RSSI_CHANGED_ACTION); 188 189 return filter; 190 } 191 192 /** 193 * Use the lifecycle constructor below whenever possible 194 */ 195 @Deprecated WifiTracker(Context context, WifiListener wifiListener, boolean includeSaved, boolean includeScans)196 public WifiTracker(Context context, WifiListener wifiListener, 197 boolean includeSaved, boolean includeScans) { 198 this(context, wifiListener, 199 context.getSystemService(WifiManager.class), 200 context.getSystemService(ConnectivityManager.class), 201 context.getSystemService(NetworkScoreManager.class), 202 newIntentFilter()); 203 } 204 205 // TODO(sghuman): Clean up includeSaved and includeScans from all constructors and linked 206 // calling apps once IC window is complete WifiTracker(Context context, WifiListener wifiListener, @NonNull Lifecycle lifecycle, boolean includeSaved, boolean includeScans)207 public WifiTracker(Context context, WifiListener wifiListener, 208 @NonNull Lifecycle lifecycle, boolean includeSaved, boolean includeScans) { 209 this(context, wifiListener, 210 context.getSystemService(WifiManager.class), 211 context.getSystemService(ConnectivityManager.class), 212 context.getSystemService(NetworkScoreManager.class), 213 newIntentFilter()); 214 215 lifecycle.addObserver(this); 216 } 217 218 @VisibleForTesting WifiTracker(Context context, WifiListener wifiListener, WifiManager wifiManager, ConnectivityManager connectivityManager, NetworkScoreManager networkScoreManager, IntentFilter filter)219 WifiTracker(Context context, WifiListener wifiListener, 220 WifiManager wifiManager, ConnectivityManager connectivityManager, 221 NetworkScoreManager networkScoreManager, 222 IntentFilter filter) { 223 mContext = context; 224 mWifiManager = wifiManager; 225 mListener = new WifiListenerExecutor(wifiListener); 226 mConnectivityManager = connectivityManager; 227 228 // check if verbose logging developer option has been turned on or off 229 sVerboseLogging = mWifiManager != null && (mWifiManager.getVerboseLoggingLevel() > 0); 230 231 mFilter = filter; 232 233 mNetworkRequest = new NetworkRequest.Builder() 234 .clearCapabilities() 235 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) 236 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) 237 .build(); 238 239 mNetworkScoreManager = networkScoreManager; 240 241 // TODO(sghuman): Remove this and create less hacky solution for testing 242 final HandlerThread workThread = new HandlerThread(TAG 243 + "{" + Integer.toHexString(System.identityHashCode(this)) + "}", 244 Process.THREAD_PRIORITY_BACKGROUND); 245 workThread.start(); 246 setWorkThread(workThread); 247 } 248 249 /** 250 * Sanity warning: this wipes out mScoreCache, so use with extreme caution 251 * @param workThread substitute Handler thread, for testing purposes only 252 */ 253 @VisibleForTesting 254 // TODO(sghuman): Remove this method, this needs to happen in a factory method and be passed in 255 // during construction setWorkThread(HandlerThread workThread)256 void setWorkThread(HandlerThread workThread) { 257 mWorkThread = workThread; 258 mWorkHandler = new Handler(workThread.getLooper()); 259 mScoreCache = new WifiNetworkScoreCache(mContext, new CacheListener(mWorkHandler) { 260 @Override 261 public void networkCacheUpdated(List<ScoredNetwork> networks) { 262 if (!mRegistered) return; 263 264 if (Log.isLoggable(TAG, Log.VERBOSE)) { 265 Log.v(TAG, "Score cache was updated with networks: " + networks); 266 } 267 updateNetworkScores(); 268 } 269 }); 270 } 271 272 @Override onDestroy()273 public void onDestroy() { 274 mWorkThread.quit(); 275 } 276 277 /** 278 * Temporarily stop scanning for wifi networks. 279 * 280 * <p>Sets {@link #mStaleScanResults} to true. 281 */ pauseScanning()282 private void pauseScanning() { 283 synchronized (mLock) { 284 if (mScanner != null) { 285 mScanner.pause(); 286 mScanner = null; 287 } 288 } 289 mStaleScanResults = true; 290 } 291 292 /** 293 * Resume scanning for wifi networks after it has been paused. 294 * 295 * <p>The score cache should be registered before this method is invoked. 296 */ resumeScanning()297 public void resumeScanning() { 298 synchronized (mLock) { 299 if (mScanner == null) { 300 mScanner = new Scanner(); 301 } 302 303 if (isWifiEnabled()) { 304 mScanner.resume(); 305 } 306 } 307 } 308 309 /** 310 * Start tracking wifi networks and scores. 311 * 312 * <p>Registers listeners and starts scanning for wifi networks. If this is not called 313 * then forceUpdate() must be called to populate getAccessPoints(). 314 */ 315 @Override 316 @MainThread onStart()317 public void onStart() { 318 // fetch current ScanResults instead of waiting for broadcast of fresh results 319 forceUpdate(); 320 321 registerScoreCache(); 322 323 mNetworkScoringUiEnabled = 324 Settings.Global.getInt( 325 mContext.getContentResolver(), 326 Settings.Global.NETWORK_SCORING_UI_ENABLED, 0) == 1; 327 328 mMaxSpeedLabelScoreCacheAge = 329 Settings.Global.getLong( 330 mContext.getContentResolver(), 331 Settings.Global.SPEED_LABEL_CACHE_EVICTION_AGE_MILLIS, 332 DEFAULT_MAX_CACHED_SCORE_AGE_MILLIS); 333 334 resumeScanning(); 335 if (!mRegistered) { 336 mContext.registerReceiver(mReceiver, mFilter, null /* permission */, mWorkHandler); 337 // NetworkCallback objects cannot be reused. http://b/20701525 . 338 mNetworkCallback = new WifiTrackerNetworkCallback(); 339 mConnectivityManager.registerNetworkCallback( 340 mNetworkRequest, mNetworkCallback, mWorkHandler); 341 mRegistered = true; 342 } 343 } 344 345 346 /** 347 * Synchronously update the list of access points with the latest information. 348 * 349 * <p>Intended to only be invoked within {@link #onStart()}. 350 */ 351 @MainThread 352 @VisibleForTesting forceUpdate()353 void forceUpdate() { 354 mLastInfo = mWifiManager.getConnectionInfo(); 355 mLastNetworkInfo = mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork()); 356 357 fetchScansAndConfigsAndUpdateAccessPoints(); 358 } 359 registerScoreCache()360 private void registerScoreCache() { 361 mNetworkScoreManager.registerNetworkScoreCache( 362 NetworkKey.TYPE_WIFI, 363 mScoreCache, 364 NetworkScoreManager.SCORE_FILTER_SCAN_RESULTS); 365 } 366 requestScoresForNetworkKeys(Collection<NetworkKey> keys)367 private void requestScoresForNetworkKeys(Collection<NetworkKey> keys) { 368 if (keys.isEmpty()) return; 369 370 if (DBG()) { 371 Log.d(TAG, "Requesting scores for Network Keys: " + keys); 372 } 373 mNetworkScoreManager.requestScores(keys.toArray(new NetworkKey[keys.size()])); 374 synchronized (mLock) { 375 mRequestedScores.addAll(keys); 376 } 377 } 378 379 /** 380 * Stop tracking wifi networks and scores. 381 * 382 * <p>This should always be called when done with a WifiTracker (if onStart was called) to 383 * ensure proper cleanup and prevent any further callbacks from occurring. 384 * 385 * <p>Calling this method will set the {@link #mStaleScanResults} bit, which prevents 386 * {@link WifiListener#onAccessPointsChanged()} callbacks from being invoked (until the bit 387 * is unset on the next SCAN_RESULTS_AVAILABLE_ACTION). 388 */ 389 @Override 390 @MainThread onStop()391 public void onStop() { 392 if (mRegistered) { 393 mContext.unregisterReceiver(mReceiver); 394 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); 395 mRegistered = false; 396 } 397 unregisterScoreCache(); 398 pauseScanning(); // and set mStaleScanResults 399 400 mWorkHandler.removeCallbacksAndMessages(null /* remove all */); 401 } 402 unregisterScoreCache()403 private void unregisterScoreCache() { 404 mNetworkScoreManager.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, mScoreCache); 405 406 // We do not want to clear the existing scores in the cache, as this method is called during 407 // stop tracking on activity pause. Hence, on resumption we want the ability to show the 408 // last known, potentially stale, scores. However, by clearing requested scores, the scores 409 // will be requested again upon resumption of tracking, and if any changes have occurred 410 // the listeners (UI) will be updated accordingly. 411 synchronized (mLock) { 412 mRequestedScores.clear(); 413 } 414 } 415 416 /** 417 * Gets the current list of access points. 418 * 419 * <p>This method is can be called on an abitrary thread by clients, but is normally called on 420 * the UI Thread by the rendering App. 421 */ 422 @AnyThread getAccessPoints()423 public List<AccessPoint> getAccessPoints() { 424 synchronized (mLock) { 425 return new ArrayList<>(mInternalAccessPoints); 426 } 427 } 428 getManager()429 public WifiManager getManager() { 430 return mWifiManager; 431 } 432 isWifiEnabled()433 public boolean isWifiEnabled() { 434 return mWifiManager != null && mWifiManager.isWifiEnabled(); 435 } 436 437 /** 438 * Returns the number of saved networks on the device, regardless of whether the WifiTracker 439 * is tracking saved networks. 440 * TODO(b/62292448): remove this function and update callsites to use WifiSavedConfigUtils 441 * directly. 442 */ getNumSavedNetworks()443 public int getNumSavedNetworks() { 444 return WifiSavedConfigUtils.getAllConfigs(mContext, mWifiManager).size(); 445 } 446 isConnected()447 public boolean isConnected() { 448 return mConnected.get(); 449 } 450 dump(PrintWriter pw)451 public void dump(PrintWriter pw) { 452 pw.println(" - wifi tracker ------"); 453 for (AccessPoint accessPoint : getAccessPoints()) { 454 pw.println(" " + accessPoint); 455 } 456 } 457 updateScanResultCache( final List<ScanResult> newResults)458 private ArrayMap<String, List<ScanResult>> updateScanResultCache( 459 final List<ScanResult> newResults) { 460 // TODO(sghuman): Delete this and replace it with the Map of Ap Keys to ScanResults for 461 // memory efficiency 462 for (ScanResult newResult : newResults) { 463 if (newResult.SSID == null || newResult.SSID.isEmpty()) { 464 continue; 465 } 466 mScanResultCache.put(newResult.BSSID, newResult); 467 } 468 469 // Evict old results in all conditions 470 evictOldScans(); 471 472 ArrayMap<String, List<ScanResult>> scanResultsByApKey = new ArrayMap<>(); 473 for (ScanResult result : mScanResultCache.values()) { 474 // Ignore hidden and ad-hoc networks. 475 if (result.SSID == null || result.SSID.length() == 0 || 476 result.capabilities.contains("[IBSS]")) { 477 continue; 478 } 479 480 String apKey = AccessPoint.getKey(mContext, result); 481 List<ScanResult> resultList; 482 if (scanResultsByApKey.containsKey(apKey)) { 483 resultList = scanResultsByApKey.get(apKey); 484 } else { 485 resultList = new ArrayList<>(); 486 scanResultsByApKey.put(apKey, resultList); 487 } 488 489 resultList.add(result); 490 } 491 492 return scanResultsByApKey; 493 } 494 495 /** 496 * Remove old scan results from the cache. If {@link #mLastScanSucceeded} is false, then 497 * increase the timeout window to avoid completely flushing the AP list before the next 498 * successful scan completes. 499 * 500 * <p>Should only ever be invoked from {@link #updateScanResultCache(List)} when 501 * {@link #mStaleScanResults} is false. 502 */ evictOldScans()503 private void evictOldScans() { 504 long evictionTimeoutMillis = mLastScanSucceeded ? MAX_SCAN_RESULT_AGE_MILLIS 505 : MAX_SCAN_RESULT_AGE_MILLIS * 2; 506 507 long nowMs = SystemClock.elapsedRealtime(); 508 for (Iterator<ScanResult> iter = mScanResultCache.values().iterator(); iter.hasNext(); ) { 509 ScanResult result = iter.next(); 510 // result timestamp is in microseconds 511 if (nowMs - result.timestamp / 1000 > evictionTimeoutMillis) { 512 iter.remove(); 513 } 514 } 515 } 516 getWifiConfigurationForNetworkId( int networkId, final List<WifiConfiguration> configs)517 private WifiConfiguration getWifiConfigurationForNetworkId( 518 int networkId, final List<WifiConfiguration> configs) { 519 if (configs != null) { 520 for (WifiConfiguration config : configs) { 521 if (mLastInfo != null && networkId == config.networkId && 522 !(config.selfAdded && config.numAssociation == 0)) { 523 return config; 524 } 525 } 526 } 527 return null; 528 } 529 530 /** 531 * Retrieves latest scan results and wifi configs, then calls 532 * {@link #updateAccessPoints(List, List)}. 533 */ fetchScansAndConfigsAndUpdateAccessPoints()534 private void fetchScansAndConfigsAndUpdateAccessPoints() { 535 List<ScanResult> newScanResults = mWifiManager.getScanResults(); 536 537 // Filter all unsupported networks from the scan result list 538 final List<ScanResult> filteredScanResults = 539 filterScanResultsByCapabilities(newScanResults); 540 541 if (isVerboseLoggingEnabled()) { 542 Log.i(TAG, "Fetched scan results: " + filteredScanResults); 543 } 544 545 List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks(); 546 updateAccessPoints(filteredScanResults, configs); 547 } 548 549 /** Update the internal list of access points. */ updateAccessPoints(final List<ScanResult> newScanResults, List<WifiConfiguration> configs)550 private void updateAccessPoints(final List<ScanResult> newScanResults, 551 List<WifiConfiguration> configs) { 552 553 WifiConfiguration connectionConfig = null; 554 if (mLastInfo != null) { 555 connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId(), configs); 556 } 557 558 // Rather than dropping and reacquiring the lock multiple times in this method, we lock 559 // once for efficiency of lock acquisition time and readability 560 synchronized (mLock) { 561 ArrayMap<String, List<ScanResult>> scanResultsByApKey = 562 updateScanResultCache(newScanResults); 563 564 // Swap the current access points into a cached list for maintaining AP listeners 565 List<AccessPoint> cachedAccessPoints; 566 cachedAccessPoints = new ArrayList<>(mInternalAccessPoints); 567 568 ArrayList<AccessPoint> accessPoints = new ArrayList<>(); 569 570 final List<NetworkKey> scoresToRequest = new ArrayList<>(); 571 572 for (Map.Entry<String, List<ScanResult>> entry : scanResultsByApKey.entrySet()) { 573 for (ScanResult result : entry.getValue()) { 574 NetworkKey key = NetworkKey.createFromScanResult(result); 575 if (key != null && !mRequestedScores.contains(key)) { 576 scoresToRequest.add(key); 577 } 578 } 579 580 AccessPoint accessPoint = 581 getCachedOrCreate(entry.getValue(), cachedAccessPoints); 582 583 // Update the matching config if there is one, to populate saved network info 584 final List<WifiConfiguration> matchedConfigs = configs.stream() 585 .filter(config -> accessPoint.matches(config)) 586 .collect(Collectors.toList()); 587 588 final int matchedConfigCount = matchedConfigs.size(); 589 if (matchedConfigCount == 0) { 590 accessPoint.update(null); 591 } else if (matchedConfigCount == 1) { 592 accessPoint.update(matchedConfigs.get(0)); 593 } else { 594 // We may have 2 matched configured WifiCongiguration if the AccessPoint is 595 // of PSK/SAE transition mode or open/OWE transition mode. 596 Optional<WifiConfiguration> preferredConfig = matchedConfigs.stream() 597 .filter(config -> isSaeOrOwe(config)).findFirst(); 598 if (preferredConfig.isPresent()) { 599 accessPoint.update(preferredConfig.get()); 600 } else { 601 accessPoint.update(matchedConfigs.get(0)); 602 } 603 } 604 605 accessPoints.add(accessPoint); 606 } 607 608 List<ScanResult> cachedScanResults = new ArrayList<>(mScanResultCache.values()); 609 610 // Add a unique Passpoint AccessPoint for each Passpoint profile's FQDN. 611 accessPoints.addAll(updatePasspointAccessPoints( 612 mWifiManager.getAllMatchingWifiConfigs(cachedScanResults), cachedAccessPoints)); 613 614 // Add OSU Provider AccessPoints 615 accessPoints.addAll(updateOsuAccessPoints( 616 mWifiManager.getMatchingOsuProviders(cachedScanResults), cachedAccessPoints)); 617 618 if (mLastInfo != null && mLastNetworkInfo != null) { 619 for (AccessPoint ap : accessPoints) { 620 ap.update(connectionConfig, mLastInfo, mLastNetworkInfo); 621 } 622 } 623 624 // If there were no scan results, create an AP for the currently connected network (if 625 // it exists). 626 if (accessPoints.isEmpty() && connectionConfig != null) { 627 AccessPoint activeAp = new AccessPoint(mContext, connectionConfig); 628 activeAp.update(connectionConfig, mLastInfo, mLastNetworkInfo); 629 accessPoints.add(activeAp); 630 scoresToRequest.add(NetworkKey.createFromWifiInfo(mLastInfo)); 631 } 632 633 requestScoresForNetworkKeys(scoresToRequest); 634 for (AccessPoint ap : accessPoints) { 635 ap.update(mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge); 636 } 637 638 // Pre-sort accessPoints to speed preference insertion 639 Collections.sort(accessPoints); 640 641 // Log accesspoints that are being removed 642 if (DBG()) { 643 Log.d(TAG, 644 "------ Dumping AccessPoints that were not seen on this scan ------"); 645 for (AccessPoint prevAccessPoint : mInternalAccessPoints) { 646 String prevTitle = prevAccessPoint.getTitle(); 647 boolean found = false; 648 for (AccessPoint newAccessPoint : accessPoints) { 649 if (newAccessPoint.getTitle() != null && newAccessPoint.getTitle() 650 .equals(prevTitle)) { 651 found = true; 652 break; 653 } 654 } 655 if (!found) 656 Log.d(TAG, "Did not find " + prevTitle + " in this scan"); 657 } 658 Log.d(TAG, 659 "---- Done dumping AccessPoints that were not seen on this scan ----"); 660 } 661 662 mInternalAccessPoints.clear(); 663 mInternalAccessPoints.addAll(accessPoints); 664 } 665 666 conditionallyNotifyListeners(); 667 } 668 isSaeOrOwe(WifiConfiguration config)669 private static boolean isSaeOrOwe(WifiConfiguration config) { 670 final int security = AccessPoint.getSecurity(config); 671 return security == AccessPoint.SECURITY_SAE || security == AccessPoint.SECURITY_OWE; 672 } 673 674 @VisibleForTesting updatePasspointAccessPoints( List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> passpointConfigsAndScans, List<AccessPoint> accessPointCache)675 List<AccessPoint> updatePasspointAccessPoints( 676 List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> passpointConfigsAndScans, 677 List<AccessPoint> accessPointCache) { 678 List<AccessPoint> accessPoints = new ArrayList<>(); 679 680 Set<String> seenFQDNs = new ArraySet<>(); 681 for (Pair<WifiConfiguration, 682 Map<Integer, List<ScanResult>>> pairing : passpointConfigsAndScans) { 683 WifiConfiguration config = pairing.first; 684 if (seenFQDNs.add(config.FQDN)) { 685 List<ScanResult> homeScans = 686 pairing.second.get(WifiManager.PASSPOINT_HOME_NETWORK); 687 List<ScanResult> roamingScans = 688 pairing.second.get(WifiManager.PASSPOINT_ROAMING_NETWORK); 689 690 AccessPoint accessPoint = 691 getCachedOrCreatePasspoint(config, homeScans, roamingScans, 692 accessPointCache); 693 accessPoints.add(accessPoint); 694 } 695 } 696 return accessPoints; 697 } 698 699 @VisibleForTesting updateOsuAccessPoints( Map<OsuProvider, List<ScanResult>> providersAndScans, List<AccessPoint> accessPointCache)700 List<AccessPoint> updateOsuAccessPoints( 701 Map<OsuProvider, List<ScanResult>> providersAndScans, 702 List<AccessPoint> accessPointCache) { 703 List<AccessPoint> accessPoints = new ArrayList<>(); 704 705 Set<OsuProvider> alreadyProvisioned = mWifiManager 706 .getMatchingPasspointConfigsForOsuProviders( 707 providersAndScans.keySet()).keySet(); 708 for (OsuProvider provider : providersAndScans.keySet()) { 709 if (!alreadyProvisioned.contains(provider)) { 710 AccessPoint accessPointOsu = 711 getCachedOrCreateOsu(provider, providersAndScans.get(provider), 712 accessPointCache); 713 accessPoints.add(accessPointOsu); 714 } 715 } 716 return accessPoints; 717 } 718 getCachedOrCreate( List<ScanResult> scanResults, List<AccessPoint> cache)719 private AccessPoint getCachedOrCreate( 720 List<ScanResult> scanResults, 721 List<AccessPoint> cache) { 722 AccessPoint accessPoint = getCachedByKey(cache, 723 AccessPoint.getKey(mContext, scanResults.get(0))); 724 if (accessPoint == null) { 725 accessPoint = new AccessPoint(mContext, scanResults); 726 } else { 727 accessPoint.setScanResults(scanResults); 728 } 729 return accessPoint; 730 } 731 getCachedOrCreatePasspoint( WifiConfiguration config, List<ScanResult> homeScans, List<ScanResult> roamingScans, List<AccessPoint> cache)732 private AccessPoint getCachedOrCreatePasspoint( 733 WifiConfiguration config, 734 List<ScanResult> homeScans, 735 List<ScanResult> roamingScans, 736 List<AccessPoint> cache) { 737 AccessPoint accessPoint = getCachedByKey(cache, AccessPoint.getKey(config)); 738 if (accessPoint == null) { 739 accessPoint = new AccessPoint(mContext, config, homeScans, roamingScans); 740 } else { 741 accessPoint.update(config); 742 accessPoint.setScanResultsPasspoint(homeScans, roamingScans); 743 } 744 return accessPoint; 745 } 746 getCachedOrCreateOsu( OsuProvider provider, List<ScanResult> scanResults, List<AccessPoint> cache)747 private AccessPoint getCachedOrCreateOsu( 748 OsuProvider provider, 749 List<ScanResult> scanResults, 750 List<AccessPoint> cache) { 751 AccessPoint accessPoint = getCachedByKey(cache, AccessPoint.getKey(provider)); 752 if (accessPoint == null) { 753 accessPoint = new AccessPoint(mContext, provider, scanResults); 754 } else { 755 accessPoint.setScanResults(scanResults); 756 } 757 return accessPoint; 758 } 759 getCachedByKey(List<AccessPoint> cache, String key)760 private AccessPoint getCachedByKey(List<AccessPoint> cache, String key) { 761 ListIterator<AccessPoint> lit = cache.listIterator(); 762 while (lit.hasNext()) { 763 AccessPoint currentAccessPoint = lit.next(); 764 if (currentAccessPoint.getKey().equals(key)) { 765 lit.remove(); 766 return currentAccessPoint; 767 } 768 } 769 return null; 770 } 771 updateNetworkInfo(NetworkInfo networkInfo)772 private void updateNetworkInfo(NetworkInfo networkInfo) { 773 /* Sticky broadcasts can call this when wifi is disabled */ 774 if (!isWifiEnabled()) { 775 clearAccessPointsAndConditionallyUpdate(); 776 return; 777 } 778 779 if (networkInfo != null) { 780 mLastNetworkInfo = networkInfo; 781 if (DBG()) { 782 Log.d(TAG, "mLastNetworkInfo set: " + mLastNetworkInfo); 783 } 784 785 if(networkInfo.isConnected() != mConnected.getAndSet(networkInfo.isConnected())) { 786 mListener.onConnectedChanged(); 787 } 788 } 789 790 WifiConfiguration connectionConfig = null; 791 792 mLastInfo = mWifiManager.getConnectionInfo(); 793 if (DBG()) { 794 Log.d(TAG, "mLastInfo set as: " + mLastInfo); 795 } 796 if (mLastInfo != null) { 797 connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId(), 798 mWifiManager.getConfiguredNetworks()); 799 } 800 801 boolean updated = false; 802 boolean reorder = false; // Only reorder if connected AP was changed 803 804 synchronized (mLock) { 805 for (int i = mInternalAccessPoints.size() - 1; i >= 0; --i) { 806 AccessPoint ap = mInternalAccessPoints.get(i); 807 boolean previouslyConnected = ap.isActive(); 808 if (ap.update(connectionConfig, mLastInfo, mLastNetworkInfo)) { 809 updated = true; 810 if (previouslyConnected != ap.isActive()) reorder = true; 811 } 812 if (ap.update(mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge)) { 813 reorder = true; 814 updated = true; 815 } 816 } 817 818 if (reorder) { 819 Collections.sort(mInternalAccessPoints); 820 } 821 if (updated) { 822 conditionallyNotifyListeners(); 823 } 824 } 825 } 826 827 /** 828 * Clears the access point list and conditionally invokes 829 * {@link WifiListener#onAccessPointsChanged()} if required (i.e. the list was not already 830 * empty). 831 */ clearAccessPointsAndConditionallyUpdate()832 private void clearAccessPointsAndConditionallyUpdate() { 833 synchronized (mLock) { 834 if (!mInternalAccessPoints.isEmpty()) { 835 mInternalAccessPoints.clear(); 836 conditionallyNotifyListeners(); 837 } 838 } 839 } 840 841 /** 842 * Update all the internal access points rankingScores, badge and metering. 843 * 844 * <p>Will trigger a resort and notify listeners of changes if applicable. 845 * 846 * <p>Synchronized on {@link #mLock}. 847 */ updateNetworkScores()848 private void updateNetworkScores() { 849 synchronized (mLock) { 850 boolean updated = false; 851 for (int i = 0; i < mInternalAccessPoints.size(); i++) { 852 if (mInternalAccessPoints.get(i).update( 853 mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge)) { 854 updated = true; 855 } 856 } 857 if (updated) { 858 Collections.sort(mInternalAccessPoints); 859 conditionallyNotifyListeners(); 860 } 861 } 862 } 863 864 /** 865 * Receiver for handling broadcasts. 866 * 867 * This receiver is registered on the WorkHandler. 868 */ 869 @VisibleForTesting 870 final BroadcastReceiver mReceiver = new BroadcastReceiver() { 871 @Override 872 public void onReceive(Context context, Intent intent) { 873 String action = intent.getAction(); 874 875 if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { 876 updateWifiState( 877 intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 878 WifiManager.WIFI_STATE_UNKNOWN)); 879 } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) { 880 mStaleScanResults = false; 881 mLastScanSucceeded = 882 intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, true); 883 884 fetchScansAndConfigsAndUpdateAccessPoints(); 885 } else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) 886 || WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) { 887 fetchScansAndConfigsAndUpdateAccessPoints(); 888 } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) { 889 // TODO(sghuman): Refactor these methods so they cannot result in duplicate 890 // onAccessPointsChanged updates being called from this intent. 891 NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); 892 updateNetworkInfo(info); 893 fetchScansAndConfigsAndUpdateAccessPoints(); 894 } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) { 895 NetworkInfo info = 896 mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork()); 897 updateNetworkInfo(info); 898 } 899 } 900 }; 901 902 /** 903 * Handles updates to WifiState. 904 * 905 * <p>If Wifi is not enabled in the enabled state, {@link #mStaleScanResults} will be set to 906 * true. 907 */ updateWifiState(int state)908 private void updateWifiState(int state) { 909 if (isVerboseLoggingEnabled()) { 910 Log.d(TAG, "updateWifiState: " + state); 911 } 912 if (state == WifiManager.WIFI_STATE_ENABLED) { 913 synchronized (mLock) { 914 if (mScanner != null) { 915 // We only need to resume if mScanner isn't null because 916 // that means we want to be scanning. 917 mScanner.resume(); 918 } 919 } 920 } else { 921 clearAccessPointsAndConditionallyUpdate(); 922 mLastInfo = null; 923 mLastNetworkInfo = null; 924 synchronized (mLock) { 925 if (mScanner != null) { 926 mScanner.pause(); 927 } 928 } 929 mStaleScanResults = true; 930 } 931 mListener.onWifiStateChanged(state); 932 } 933 934 private final class WifiTrackerNetworkCallback extends ConnectivityManager.NetworkCallback { onCapabilitiesChanged(Network network, NetworkCapabilities nc)935 public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) { 936 if (network.equals(mWifiManager.getCurrentNetwork())) { 937 // TODO(sghuman): Investigate whether this comment still holds true and if it makes 938 // more sense fetch the latest network info here: 939 940 // We don't send a NetworkInfo object along with this message, because even if we 941 // fetch one from ConnectivityManager, it might be older than the most recent 942 // NetworkInfo message we got via a WIFI_STATE_CHANGED broadcast. 943 updateNetworkInfo(null); 944 } 945 } 946 } 947 948 @VisibleForTesting 949 class Scanner extends Handler { 950 static final int MSG_SCAN = 0; 951 952 private int mRetry = 0; 953 resume()954 void resume() { 955 if (isVerboseLoggingEnabled()) { 956 Log.d(TAG, "Scanner resume"); 957 } 958 if (!hasMessages(MSG_SCAN)) { 959 sendEmptyMessage(MSG_SCAN); 960 } 961 } 962 pause()963 void pause() { 964 if (isVerboseLoggingEnabled()) { 965 Log.d(TAG, "Scanner pause"); 966 } 967 mRetry = 0; 968 removeMessages(MSG_SCAN); 969 } 970 971 @VisibleForTesting isScanning()972 boolean isScanning() { 973 return hasMessages(MSG_SCAN); 974 } 975 976 @Override handleMessage(Message message)977 public void handleMessage(Message message) { 978 if (message.what != MSG_SCAN) return; 979 if (mWifiManager.startScan()) { 980 mRetry = 0; 981 } else if (++mRetry >= 3) { 982 mRetry = 0; 983 if (mContext != null) { 984 Toast.makeText(mContext, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show(); 985 } 986 return; 987 } 988 sendEmptyMessageDelayed(MSG_SCAN, WIFI_RESCAN_INTERVAL_MS); 989 } 990 } 991 992 /** A restricted multimap for use in constructAccessPoints */ 993 private static class Multimap<K,V> { 994 private final HashMap<K,List<V>> store = new HashMap<K,List<V>>(); 995 /** retrieve a non-null list of values with key K */ getAll(K key)996 List<V> getAll(K key) { 997 List<V> values = store.get(key); 998 return values != null ? values : Collections.<V>emptyList(); 999 } 1000 put(K key, V val)1001 void put(K key, V val) { 1002 List<V> curVals = store.get(key); 1003 if (curVals == null) { 1004 curVals = new ArrayList<V>(3); 1005 store.put(key, curVals); 1006 } 1007 curVals.add(val); 1008 } 1009 } 1010 1011 /** 1012 * Wraps the given {@link WifiListener} instance and executes its methods on the Main Thread. 1013 * 1014 * <p>Also logs all callbacks invocations when verbose logging is enabled. 1015 */ 1016 @VisibleForTesting class WifiListenerExecutor implements WifiListener { 1017 1018 private final WifiListener mDelegatee; 1019 WifiListenerExecutor(WifiListener listener)1020 public WifiListenerExecutor(WifiListener listener) { 1021 mDelegatee = listener; 1022 } 1023 1024 @Override onWifiStateChanged(int state)1025 public void onWifiStateChanged(int state) { 1026 runAndLog(() -> mDelegatee.onWifiStateChanged(state), 1027 String.format("Invoking onWifiStateChanged callback with state %d", state)); 1028 } 1029 1030 @Override onConnectedChanged()1031 public void onConnectedChanged() { 1032 runAndLog(mDelegatee::onConnectedChanged, "Invoking onConnectedChanged callback"); 1033 } 1034 1035 @Override onAccessPointsChanged()1036 public void onAccessPointsChanged() { 1037 runAndLog(mDelegatee::onAccessPointsChanged, "Invoking onAccessPointsChanged callback"); 1038 } 1039 runAndLog(Runnable r, String verboseLog)1040 private void runAndLog(Runnable r, String verboseLog) { 1041 ThreadUtils.postOnMainThread(() -> { 1042 if (mRegistered) { 1043 if (isVerboseLoggingEnabled()) { 1044 Log.i(TAG, verboseLog); 1045 } 1046 r.run(); 1047 } 1048 }); 1049 } 1050 } 1051 1052 /** 1053 * WifiListener interface that defines callbacks indicating state changes in WifiTracker. 1054 * 1055 * <p>All callbacks are invoked on the MainThread. 1056 */ 1057 public interface WifiListener { 1058 /** 1059 * Called when the state of Wifi has changed, the state will be one of 1060 * the following. 1061 * 1062 * <li>{@link WifiManager#WIFI_STATE_DISABLED}</li> 1063 * <li>{@link WifiManager#WIFI_STATE_ENABLED}</li> 1064 * <li>{@link WifiManager#WIFI_STATE_DISABLING}</li> 1065 * <li>{@link WifiManager#WIFI_STATE_ENABLING}</li> 1066 * <li>{@link WifiManager#WIFI_STATE_UNKNOWN}</li> 1067 * <p> 1068 * 1069 * @param state The new state of wifi. 1070 */ onWifiStateChanged(int state)1071 void onWifiStateChanged(int state); 1072 1073 /** 1074 * Called when the connection state of wifi has changed and 1075 * {@link WifiTracker#isConnected()} should be called to get the updated state. 1076 */ onConnectedChanged()1077 void onConnectedChanged(); 1078 1079 /** 1080 * Called to indicate the list of AccessPoints has been updated and 1081 * {@link WifiTracker#getAccessPoints()} should be called to get the updated list. 1082 */ onAccessPointsChanged()1083 void onAccessPointsChanged(); 1084 } 1085 1086 /** 1087 * Invokes {@link WifiListenerExecutor#onAccessPointsChanged()} iif {@link #mStaleScanResults} 1088 * is false. 1089 */ conditionallyNotifyListeners()1090 private void conditionallyNotifyListeners() { 1091 if (mStaleScanResults) { 1092 return; 1093 } 1094 1095 mListener.onAccessPointsChanged(); 1096 } 1097 1098 /** 1099 * Filters unsupported networks from scan results. New WPA3 networks and OWE networks 1100 * may not be compatible with the device HW/SW. 1101 * @param scanResults List of scan results 1102 * @return List of filtered scan results based on local device capabilities 1103 */ filterScanResultsByCapabilities(List<ScanResult> scanResults)1104 private List<ScanResult> filterScanResultsByCapabilities(List<ScanResult> scanResults) { 1105 if (scanResults == null) { 1106 return null; 1107 } 1108 1109 // Get and cache advanced capabilities 1110 final boolean isOweSupported = mWifiManager.isEnhancedOpenSupported(); 1111 final boolean isSaeSupported = mWifiManager.isWpa3SaeSupported(); 1112 final boolean isSuiteBSupported = mWifiManager.isWpa3SuiteBSupported(); 1113 1114 List<ScanResult> filteredScanResultList = new ArrayList<>(); 1115 1116 // Iterate through the list of scan results and filter out APs which are not 1117 // compatible with our device. 1118 for (ScanResult scanResult : scanResults) { 1119 if (scanResult.capabilities.contains(WIFI_SECURITY_PSK)) { 1120 // All devices (today) support RSN-PSK or WPA-PSK 1121 // Add this here because some APs may support both PSK and SAE and the check 1122 // below will filter it out. 1123 filteredScanResultList.add(scanResult); 1124 continue; 1125 } 1126 1127 if ((scanResult.capabilities.contains(WIFI_SECURITY_SUITE_B_192) && !isSuiteBSupported) 1128 || (scanResult.capabilities.contains(WIFI_SECURITY_SAE) && !isSaeSupported) 1129 || (scanResult.capabilities.contains(WIFI_SECURITY_OWE) && !isOweSupported)) { 1130 if (isVerboseLoggingEnabled()) { 1131 Log.v(TAG, "filterScanResultsByCapabilities: Filtering SSID " 1132 + scanResult.SSID + " with capabilities: " + scanResult.capabilities); 1133 } 1134 } else { 1135 // Safe to add 1136 filteredScanResultList.add(scanResult); 1137 } 1138 } 1139 1140 return filteredScanResultList; 1141 } 1142 } 1143