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