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 * Sanity 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 // NetworkCallback objects cannot be reused. http://b/20701525 . 347 mNetworkCallback = new WifiTrackerNetworkCallback(); 348 mConnectivityManager.registerNetworkCallback( 349 mNetworkRequest, mNetworkCallback, mWorkHandler); 350 mRegistered = true; 351 } 352 } 353 354 355 /** 356 * Synchronously update the list of access points with the latest information. 357 * 358 * <p>Intended to only be invoked within {@link #onStart()}. 359 */ 360 @MainThread 361 @VisibleForTesting forceUpdate()362 void forceUpdate() { 363 mLastInfo = mWifiManager.getConnectionInfo(); 364 mLastNetworkInfo = mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork()); 365 366 fetchScansAndConfigsAndUpdateAccessPoints(); 367 } 368 registerScoreCache()369 private void registerScoreCache() { 370 mNetworkScoreManager.registerNetworkScoreCache( 371 NetworkKey.TYPE_WIFI, 372 mScoreCache, 373 NetworkScoreManager.SCORE_FILTER_SCAN_RESULTS); 374 } 375 requestScoresForNetworkKeys(Collection<NetworkKey> keys)376 private void requestScoresForNetworkKeys(Collection<NetworkKey> keys) { 377 if (keys.isEmpty()) return; 378 379 if (DBG()) { 380 Log.d(TAG, "Requesting scores for Network Keys: " + keys); 381 } 382 mNetworkScoreManager.requestScores(keys.toArray(new NetworkKey[keys.size()])); 383 synchronized (mLock) { 384 mRequestedScores.addAll(keys); 385 } 386 } 387 388 /** 389 * Stop tracking wifi networks and scores. 390 * 391 * <p>This should always be called when done with a WifiTracker (if onStart was called) to 392 * ensure proper cleanup and prevent any further callbacks from occurring. 393 * 394 * <p>Calling this method will set the {@link #mStaleScanResults} bit, which prevents 395 * {@link WifiListener#onAccessPointsChanged()} callbacks from being invoked (until the bit 396 * is unset on the next SCAN_RESULTS_AVAILABLE_ACTION). 397 */ 398 @Override 399 @MainThread onStop()400 public void onStop() { 401 if (mRegistered) { 402 mContext.unregisterReceiver(mReceiver); 403 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); 404 mRegistered = false; 405 } 406 unregisterScoreCache(); 407 pauseScanning(); // and set mStaleScanResults 408 409 mWorkHandler.removeCallbacksAndMessages(null /* remove all */); 410 } 411 unregisterScoreCache()412 private void unregisterScoreCache() { 413 mNetworkScoreManager.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, mScoreCache); 414 415 // We do not want to clear the existing scores in the cache, as this method is called during 416 // stop tracking on activity pause. Hence, on resumption we want the ability to show the 417 // last known, potentially stale, scores. However, by clearing requested scores, the scores 418 // will be requested again upon resumption of tracking, and if any changes have occurred 419 // the listeners (UI) will be updated accordingly. 420 synchronized (mLock) { 421 mRequestedScores.clear(); 422 } 423 } 424 425 /** 426 * Gets the current list of access points. 427 * 428 * <p>This method is can be called on an abitrary thread by clients, but is normally called on 429 * the UI Thread by the rendering App. 430 */ 431 @AnyThread getAccessPoints()432 public List<AccessPoint> getAccessPoints() { 433 synchronized (mLock) { 434 return new ArrayList<>(mInternalAccessPoints); 435 } 436 } 437 getManager()438 public WifiManager getManager() { 439 return mWifiManager; 440 } 441 isWifiEnabled()442 public boolean isWifiEnabled() { 443 return mWifiManager != null && mWifiManager.isWifiEnabled(); 444 } 445 446 /** 447 * Returns the number of saved networks on the device, regardless of whether the WifiTracker 448 * is tracking saved networks. 449 * TODO(b/62292448): remove this function and update callsites to use WifiSavedConfigUtils 450 * directly. 451 */ getNumSavedNetworks()452 public int getNumSavedNetworks() { 453 return WifiSavedConfigUtils.getAllConfigs(mContext, mWifiManager).size(); 454 } 455 isConnected()456 public boolean isConnected() { 457 return mConnected.get(); 458 } 459 dump(PrintWriter pw)460 public void dump(PrintWriter pw) { 461 pw.println(" - wifi tracker ------"); 462 for (AccessPoint accessPoint : getAccessPoints()) { 463 pw.println(" " + accessPoint); 464 } 465 } 466 updateScanResultCache( final List<ScanResult> newResults)467 private ArrayMap<String, List<ScanResult>> updateScanResultCache( 468 final List<ScanResult> newResults) { 469 // TODO(sghuman): Delete this and replace it with the Map of Ap Keys to ScanResults for 470 // memory efficiency 471 for (ScanResult newResult : newResults) { 472 if (newResult.SSID == null || newResult.SSID.isEmpty()) { 473 continue; 474 } 475 mScanResultCache.put(newResult.BSSID, newResult); 476 } 477 478 // Evict old results in all conditions 479 evictOldScans(); 480 481 ArrayMap<String, List<ScanResult>> scanResultsByApKey = new ArrayMap<>(); 482 for (ScanResult result : mScanResultCache.values()) { 483 // Ignore hidden and ad-hoc networks. 484 if (result.SSID == null || result.SSID.length() == 0 || 485 result.capabilities.contains("[IBSS]")) { 486 continue; 487 } 488 489 String apKey = AccessPoint.getKey(mContext, result); 490 List<ScanResult> resultList; 491 if (scanResultsByApKey.containsKey(apKey)) { 492 resultList = scanResultsByApKey.get(apKey); 493 } else { 494 resultList = new ArrayList<>(); 495 scanResultsByApKey.put(apKey, resultList); 496 } 497 498 resultList.add(result); 499 } 500 501 return scanResultsByApKey; 502 } 503 504 /** 505 * Remove old scan results from the cache. If {@link #mLastScanSucceeded} is false, then 506 * increase the timeout window to avoid completely flushing the AP list before the next 507 * successful scan completes. 508 * 509 * <p>Should only ever be invoked from {@link #updateScanResultCache(List)} when 510 * {@link #mStaleScanResults} is false. 511 */ evictOldScans()512 private void evictOldScans() { 513 long evictionTimeoutMillis = mLastScanSucceeded ? MAX_SCAN_RESULT_AGE_MILLIS 514 : MAX_SCAN_RESULT_AGE_MILLIS * 2; 515 516 long nowMs = SystemClock.elapsedRealtime(); 517 for (Iterator<ScanResult> iter = mScanResultCache.values().iterator(); iter.hasNext(); ) { 518 ScanResult result = iter.next(); 519 // result timestamp is in microseconds 520 if (nowMs - result.timestamp / 1000 > evictionTimeoutMillis) { 521 iter.remove(); 522 } 523 } 524 } 525 getWifiConfigurationForNetworkId( int networkId, final List<WifiConfiguration> configs)526 private WifiConfiguration getWifiConfigurationForNetworkId( 527 int networkId, final List<WifiConfiguration> configs) { 528 if (configs != null) { 529 for (WifiConfiguration config : configs) { 530 if (mLastInfo != null && networkId == config.networkId) { 531 return config; 532 } 533 } 534 } 535 return null; 536 } 537 538 /** 539 * Retrieves latest scan results and wifi configs, then calls 540 * {@link #updateAccessPoints(List, List)}. 541 */ fetchScansAndConfigsAndUpdateAccessPoints()542 private void fetchScansAndConfigsAndUpdateAccessPoints() { 543 List<ScanResult> newScanResults = mWifiManager.getScanResults(); 544 545 // Filter all unsupported networks from the scan result list 546 final List<ScanResult> filteredScanResults = 547 filterScanResultsByCapabilities(newScanResults); 548 549 if (isVerboseLoggingEnabled()) { 550 Log.i(TAG, "Fetched scan results: " + filteredScanResults); 551 } 552 553 List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks(); 554 updateAccessPoints(filteredScanResults, configs); 555 } 556 557 /** Update the internal list of access points. */ updateAccessPoints(final List<ScanResult> newScanResults, List<WifiConfiguration> configs)558 private void updateAccessPoints(final List<ScanResult> newScanResults, 559 List<WifiConfiguration> configs) { 560 561 WifiConfiguration connectionConfig = null; 562 if (mLastInfo != null) { 563 connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId(), configs); 564 } 565 566 // Rather than dropping and reacquiring the lock multiple times in this method, we lock 567 // once for efficiency of lock acquisition time and readability 568 synchronized (mLock) { 569 ArrayMap<String, List<ScanResult>> scanResultsByApKey = 570 updateScanResultCache(newScanResults); 571 572 // Swap the current access points into a cached list for maintaining AP listeners 573 List<AccessPoint> cachedAccessPoints; 574 cachedAccessPoints = new ArrayList<>(mInternalAccessPoints); 575 576 ArrayList<AccessPoint> accessPoints = new ArrayList<>(); 577 578 final List<NetworkKey> scoresToRequest = new ArrayList<>(); 579 580 for (Map.Entry<String, List<ScanResult>> entry : scanResultsByApKey.entrySet()) { 581 for (ScanResult result : entry.getValue()) { 582 NetworkKey key = NetworkKey.createFromScanResult(result); 583 if (key != null && !mRequestedScores.contains(key)) { 584 scoresToRequest.add(key); 585 } 586 } 587 588 AccessPoint accessPoint = 589 getCachedOrCreate(entry.getValue(), cachedAccessPoints); 590 591 // Update the matching config if there is one, to populate saved network info 592 final List<WifiConfiguration> matchedConfigs = configs.stream() 593 .filter(config -> accessPoint.matches(config)) 594 .collect(Collectors.toList()); 595 596 final int matchedConfigCount = matchedConfigs.size(); 597 if (matchedConfigCount == 0) { 598 accessPoint.update(null); 599 } else if (matchedConfigCount == 1) { 600 accessPoint.update(matchedConfigs.get(0)); 601 } else { 602 // We may have 2 matched configured WifiCongiguration if the AccessPoint is 603 // of PSK/SAE transition mode or open/OWE transition mode. 604 Optional<WifiConfiguration> preferredConfig = matchedConfigs.stream() 605 .filter(config -> isSaeOrOwe(config)).findFirst(); 606 if (preferredConfig.isPresent()) { 607 accessPoint.update(preferredConfig.get()); 608 } else { 609 accessPoint.update(matchedConfigs.get(0)); 610 } 611 } 612 613 accessPoints.add(accessPoint); 614 } 615 616 List<ScanResult> cachedScanResults = new ArrayList<>(mScanResultCache.values()); 617 618 // Add a unique Passpoint AccessPoint for each Passpoint profile's unique identifier. 619 accessPoints.addAll(updatePasspointAccessPoints( 620 mWifiManager.getAllMatchingWifiConfigs(cachedScanResults), cachedAccessPoints)); 621 622 // Add OSU Provider AccessPoints 623 accessPoints.addAll(updateOsuAccessPoints( 624 mWifiManager.getMatchingOsuProviders(cachedScanResults), cachedAccessPoints)); 625 626 if (mLastInfo != null && mLastNetworkInfo != null) { 627 for (AccessPoint ap : accessPoints) { 628 ap.update(connectionConfig, mLastInfo, mLastNetworkInfo); 629 } 630 } 631 632 // If there were no scan results, create an AP for the currently connected network (if 633 // it exists). 634 if (accessPoints.isEmpty() && connectionConfig != null) { 635 AccessPoint activeAp = new AccessPoint(mContext, connectionConfig); 636 activeAp.update(connectionConfig, mLastInfo, mLastNetworkInfo); 637 accessPoints.add(activeAp); 638 scoresToRequest.add(NetworkKey.createFromWifiInfo(mLastInfo)); 639 } 640 641 requestScoresForNetworkKeys(scoresToRequest); 642 for (AccessPoint ap : accessPoints) { 643 ap.update(mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge); 644 } 645 646 // Pre-sort accessPoints to speed preference insertion 647 Collections.sort(accessPoints); 648 649 // Log accesspoints that are being removed 650 if (DBG()) { 651 Log.d(TAG, 652 "------ Dumping AccessPoints that were not seen on this scan ------"); 653 for (AccessPoint prevAccessPoint : mInternalAccessPoints) { 654 String prevTitle = prevAccessPoint.getTitle(); 655 boolean found = false; 656 for (AccessPoint newAccessPoint : accessPoints) { 657 if (newAccessPoint.getTitle() != null && newAccessPoint.getTitle() 658 .equals(prevTitle)) { 659 found = true; 660 break; 661 } 662 } 663 if (!found) 664 Log.d(TAG, "Did not find " + prevTitle + " in this scan"); 665 } 666 Log.d(TAG, 667 "---- Done dumping AccessPoints that were not seen on this scan ----"); 668 } 669 670 mInternalAccessPoints.clear(); 671 mInternalAccessPoints.addAll(accessPoints); 672 } 673 674 conditionallyNotifyListeners(); 675 } 676 isSaeOrOwe(WifiConfiguration config)677 private static boolean isSaeOrOwe(WifiConfiguration config) { 678 final int security = AccessPoint.getSecurity(config); 679 return security == AccessPoint.SECURITY_SAE || security == AccessPoint.SECURITY_OWE; 680 } 681 682 @VisibleForTesting updatePasspointAccessPoints( List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> passpointConfigsAndScans, List<AccessPoint> accessPointCache)683 List<AccessPoint> updatePasspointAccessPoints( 684 List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> passpointConfigsAndScans, 685 List<AccessPoint> accessPointCache) { 686 List<AccessPoint> accessPoints = new ArrayList<>(); 687 688 Set<String> seenFQDNs = new ArraySet<>(); 689 for (Pair<WifiConfiguration, 690 Map<Integer, List<ScanResult>>> pairing : passpointConfigsAndScans) { 691 WifiConfiguration config = pairing.first; 692 if (seenFQDNs.add(config.FQDN)) { 693 List<ScanResult> homeScans = 694 pairing.second.get(WifiManager.PASSPOINT_HOME_NETWORK); 695 List<ScanResult> roamingScans = 696 pairing.second.get(WifiManager.PASSPOINT_ROAMING_NETWORK); 697 698 AccessPoint accessPoint = 699 getCachedOrCreatePasspoint(config, homeScans, roamingScans, 700 accessPointCache); 701 accessPoints.add(accessPoint); 702 } 703 } 704 return accessPoints; 705 } 706 707 @VisibleForTesting updateOsuAccessPoints( Map<OsuProvider, List<ScanResult>> providersAndScans, List<AccessPoint> accessPointCache)708 List<AccessPoint> updateOsuAccessPoints( 709 Map<OsuProvider, List<ScanResult>> providersAndScans, 710 List<AccessPoint> accessPointCache) { 711 List<AccessPoint> accessPoints = new ArrayList<>(); 712 713 Set<OsuProvider> alreadyProvisioned = mWifiManager 714 .getMatchingPasspointConfigsForOsuProviders( 715 providersAndScans.keySet()).keySet(); 716 for (OsuProvider provider : providersAndScans.keySet()) { 717 if (!alreadyProvisioned.contains(provider)) { 718 AccessPoint accessPointOsu = 719 getCachedOrCreateOsu(provider, providersAndScans.get(provider), 720 accessPointCache); 721 accessPoints.add(accessPointOsu); 722 } 723 } 724 return accessPoints; 725 } 726 getCachedOrCreate( List<ScanResult> scanResults, List<AccessPoint> cache)727 private AccessPoint getCachedOrCreate( 728 List<ScanResult> scanResults, 729 List<AccessPoint> cache) { 730 AccessPoint accessPoint = getCachedByKey(cache, 731 AccessPoint.getKey(mContext, scanResults.get(0))); 732 if (accessPoint == null) { 733 accessPoint = new AccessPoint(mContext, scanResults); 734 } else { 735 accessPoint.setScanResults(scanResults); 736 } 737 return accessPoint; 738 } 739 getCachedOrCreatePasspoint( WifiConfiguration config, List<ScanResult> homeScans, List<ScanResult> roamingScans, List<AccessPoint> cache)740 private AccessPoint getCachedOrCreatePasspoint( 741 WifiConfiguration config, 742 List<ScanResult> homeScans, 743 List<ScanResult> roamingScans, 744 List<AccessPoint> cache) { 745 AccessPoint accessPoint = getCachedByKey(cache, AccessPoint.getKey(config)); 746 if (accessPoint == null) { 747 accessPoint = new AccessPoint(mContext, config, homeScans, roamingScans); 748 } else { 749 accessPoint.update(config); 750 accessPoint.setScanResultsPasspoint(homeScans, roamingScans); 751 } 752 return accessPoint; 753 } 754 getCachedOrCreateOsu( OsuProvider provider, List<ScanResult> scanResults, List<AccessPoint> cache)755 private AccessPoint getCachedOrCreateOsu( 756 OsuProvider provider, 757 List<ScanResult> scanResults, 758 List<AccessPoint> cache) { 759 AccessPoint accessPoint = getCachedByKey(cache, AccessPoint.getKey(provider)); 760 if (accessPoint == null) { 761 accessPoint = new AccessPoint(mContext, provider, scanResults); 762 } else { 763 accessPoint.setScanResults(scanResults); 764 } 765 return accessPoint; 766 } 767 getCachedByKey(List<AccessPoint> cache, String key)768 private AccessPoint getCachedByKey(List<AccessPoint> cache, String key) { 769 ListIterator<AccessPoint> lit = cache.listIterator(); 770 while (lit.hasNext()) { 771 AccessPoint currentAccessPoint = lit.next(); 772 if (currentAccessPoint.getKey().equals(key)) { 773 lit.remove(); 774 return currentAccessPoint; 775 } 776 } 777 return null; 778 } 779 updateNetworkInfo(NetworkInfo networkInfo)780 private void updateNetworkInfo(NetworkInfo networkInfo) { 781 /* Sticky broadcasts can call this when wifi is disabled */ 782 if (!isWifiEnabled()) { 783 clearAccessPointsAndConditionallyUpdate(); 784 return; 785 } 786 787 if (networkInfo != null) { 788 mLastNetworkInfo = networkInfo; 789 if (DBG()) { 790 Log.d(TAG, "mLastNetworkInfo set: " + mLastNetworkInfo); 791 } 792 793 if(networkInfo.isConnected() != mConnected.getAndSet(networkInfo.isConnected())) { 794 mListener.onConnectedChanged(); 795 } 796 } 797 798 WifiConfiguration connectionConfig = null; 799 800 mLastInfo = mWifiManager.getConnectionInfo(); 801 if (DBG()) { 802 Log.d(TAG, "mLastInfo set as: " + mLastInfo); 803 } 804 if (mLastInfo != null) { 805 connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId(), 806 mWifiManager.getConfiguredNetworks()); 807 } 808 809 boolean updated = false; 810 boolean reorder = false; // Only reorder if connected AP was changed 811 812 synchronized (mLock) { 813 for (int i = mInternalAccessPoints.size() - 1; i >= 0; --i) { 814 AccessPoint ap = mInternalAccessPoints.get(i); 815 boolean previouslyConnected = ap.isActive(); 816 if (ap.update(connectionConfig, mLastInfo, mLastNetworkInfo)) { 817 updated = true; 818 if (previouslyConnected != ap.isActive()) reorder = true; 819 } 820 if (ap.update(mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge)) { 821 reorder = true; 822 updated = true; 823 } 824 } 825 826 if (reorder) { 827 Collections.sort(mInternalAccessPoints); 828 } 829 if (updated) { 830 conditionallyNotifyListeners(); 831 } 832 } 833 } 834 835 /** 836 * Clears the access point list and conditionally invokes 837 * {@link WifiListener#onAccessPointsChanged()} if required (i.e. the list was not already 838 * empty). 839 */ clearAccessPointsAndConditionallyUpdate()840 private void clearAccessPointsAndConditionallyUpdate() { 841 synchronized (mLock) { 842 if (!mInternalAccessPoints.isEmpty()) { 843 mInternalAccessPoints.clear(); 844 conditionallyNotifyListeners(); 845 } 846 } 847 } 848 849 /** 850 * Update all the internal access points rankingScores, badge and metering. 851 * 852 * <p>Will trigger a resort and notify listeners of changes if applicable. 853 * 854 * <p>Synchronized on {@link #mLock}. 855 */ updateNetworkScores()856 private void updateNetworkScores() { 857 synchronized (mLock) { 858 boolean updated = false; 859 for (int i = 0; i < mInternalAccessPoints.size(); i++) { 860 if (mInternalAccessPoints.get(i).update( 861 mScoreCache, mNetworkScoringUiEnabled, mMaxSpeedLabelScoreCacheAge)) { 862 updated = true; 863 } 864 } 865 if (updated) { 866 Collections.sort(mInternalAccessPoints); 867 conditionallyNotifyListeners(); 868 } 869 } 870 } 871 872 /** 873 * Receiver for handling broadcasts. 874 * 875 * This receiver is registered on the WorkHandler. 876 */ 877 @VisibleForTesting 878 final BroadcastReceiver mReceiver = new BroadcastReceiver() { 879 @Override 880 public void onReceive(Context context, Intent intent) { 881 String action = intent.getAction(); 882 883 if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { 884 updateWifiState( 885 intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 886 WifiManager.WIFI_STATE_UNKNOWN)); 887 } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) { 888 mStaleScanResults = false; 889 mLastScanSucceeded = 890 intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, true); 891 892 fetchScansAndConfigsAndUpdateAccessPoints(); 893 } else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) 894 || WifiManager.ACTION_LINK_CONFIGURATION_CHANGED.equals(action)) { 895 fetchScansAndConfigsAndUpdateAccessPoints(); 896 } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) { 897 // TODO(sghuman): Refactor these methods so they cannot result in duplicate 898 // onAccessPointsChanged updates being called from this intent. 899 NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); 900 updateNetworkInfo(info); 901 fetchScansAndConfigsAndUpdateAccessPoints(); 902 } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) { 903 updateNetworkInfo(/* networkInfo= */ null); 904 } 905 } 906 }; 907 908 /** 909 * Handles updates to WifiState. 910 * 911 * <p>If Wifi is not enabled in the enabled state, {@link #mStaleScanResults} will be set to 912 * true. 913 */ updateWifiState(int state)914 private void updateWifiState(int state) { 915 if (isVerboseLoggingEnabled()) { 916 Log.d(TAG, "updateWifiState: " + state); 917 } 918 if (state == WifiManager.WIFI_STATE_ENABLED) { 919 synchronized (mLock) { 920 if (mScanner != null) { 921 // We only need to resume if mScanner isn't null because 922 // that means we want to be scanning. 923 mScanner.resume(); 924 } 925 } 926 } else { 927 clearAccessPointsAndConditionallyUpdate(); 928 mLastInfo = null; 929 mLastNetworkInfo = null; 930 synchronized (mLock) { 931 if (mScanner != null) { 932 mScanner.pause(); 933 } 934 } 935 mStaleScanResults = true; 936 } 937 mListener.onWifiStateChanged(state); 938 } 939 940 private final class WifiTrackerNetworkCallback extends ConnectivityManager.NetworkCallback { onCapabilitiesChanged(Network network, NetworkCapabilities nc)941 public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) { 942 if (network.equals(mWifiManager.getCurrentNetwork())) { 943 // TODO(sghuman): Investigate whether this comment still holds true and if it makes 944 // more sense fetch the latest network info here: 945 946 // We don't send a NetworkInfo object along with this message, because even if we 947 // fetch one from ConnectivityManager, it might be older than the most recent 948 // NetworkInfo message we got via a WIFI_STATE_CHANGED broadcast. 949 updateNetworkInfo(/* networkInfo= */ null); 950 } 951 } 952 } 953 954 @VisibleForTesting 955 class Scanner extends Handler { 956 static final int MSG_SCAN = 0; 957 958 private int mRetry = 0; 959 resume()960 void resume() { 961 if (isVerboseLoggingEnabled()) { 962 Log.d(TAG, "Scanner resume"); 963 } 964 if (!hasMessages(MSG_SCAN)) { 965 sendEmptyMessage(MSG_SCAN); 966 } 967 } 968 pause()969 void pause() { 970 if (isVerboseLoggingEnabled()) { 971 Log.d(TAG, "Scanner pause"); 972 } 973 mRetry = 0; 974 removeMessages(MSG_SCAN); 975 } 976 977 @VisibleForTesting isScanning()978 boolean isScanning() { 979 return hasMessages(MSG_SCAN); 980 } 981 982 @Override handleMessage(Message message)983 public void handleMessage(Message message) { 984 if (message.what != MSG_SCAN) return; 985 if (mWifiManager.startScan()) { 986 mRetry = 0; 987 } else if (++mRetry >= 3) { 988 mRetry = 0; 989 if (mContext != null) { 990 Toast.makeText(mContext, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show(); 991 } 992 return; 993 } 994 sendEmptyMessageDelayed(MSG_SCAN, WIFI_RESCAN_INTERVAL_MS); 995 } 996 } 997 998 /** A restricted multimap for use in constructAccessPoints */ 999 private static class Multimap<K,V> { 1000 private final HashMap<K,List<V>> store = new HashMap<K,List<V>>(); 1001 /** retrieve a non-null list of values with key K */ getAll(K key)1002 List<V> getAll(K key) { 1003 List<V> values = store.get(key); 1004 return values != null ? values : Collections.<V>emptyList(); 1005 } 1006 put(K key, V val)1007 void put(K key, V val) { 1008 List<V> curVals = store.get(key); 1009 if (curVals == null) { 1010 curVals = new ArrayList<V>(3); 1011 store.put(key, curVals); 1012 } 1013 curVals.add(val); 1014 } 1015 } 1016 1017 /** 1018 * Wraps the given {@link WifiListener} instance and executes its methods on the Main Thread. 1019 * 1020 * <p>Also logs all callbacks invocations when verbose logging is enabled. 1021 */ 1022 @VisibleForTesting class WifiListenerExecutor implements WifiListener { 1023 1024 private final WifiListener mDelegatee; 1025 WifiListenerExecutor(WifiListener listener)1026 public WifiListenerExecutor(WifiListener listener) { 1027 mDelegatee = listener; 1028 } 1029 1030 @Override onWifiStateChanged(int state)1031 public void onWifiStateChanged(int state) { 1032 runAndLog(() -> mDelegatee.onWifiStateChanged(state), 1033 String.format("Invoking onWifiStateChanged callback with state %d", state)); 1034 } 1035 1036 @Override onConnectedChanged()1037 public void onConnectedChanged() { 1038 runAndLog(mDelegatee::onConnectedChanged, "Invoking onConnectedChanged callback"); 1039 } 1040 1041 @Override onAccessPointsChanged()1042 public void onAccessPointsChanged() { 1043 runAndLog(mDelegatee::onAccessPointsChanged, "Invoking onAccessPointsChanged callback"); 1044 } 1045 runAndLog(Runnable r, String verboseLog)1046 private void runAndLog(Runnable r, String verboseLog) { 1047 ThreadUtils.postOnMainThread(() -> { 1048 if (mRegistered) { 1049 if (isVerboseLoggingEnabled()) { 1050 Log.i(TAG, verboseLog); 1051 } 1052 r.run(); 1053 } 1054 }); 1055 } 1056 } 1057 1058 /** 1059 * WifiListener interface that defines callbacks indicating state changes in WifiTracker. 1060 * 1061 * <p>All callbacks are invoked on the MainThread. 1062 */ 1063 public interface WifiListener { 1064 /** 1065 * Called when the state of Wifi has changed, the state will be one of 1066 * the following. 1067 * 1068 * <li>{@link WifiManager#WIFI_STATE_DISABLED}</li> 1069 * <li>{@link WifiManager#WIFI_STATE_ENABLED}</li> 1070 * <li>{@link WifiManager#WIFI_STATE_DISABLING}</li> 1071 * <li>{@link WifiManager#WIFI_STATE_ENABLING}</li> 1072 * <li>{@link WifiManager#WIFI_STATE_UNKNOWN}</li> 1073 * <p> 1074 * 1075 * @param state The new state of wifi. 1076 */ onWifiStateChanged(int state)1077 void onWifiStateChanged(int state); 1078 1079 /** 1080 * Called when the connection state of wifi has changed and 1081 * {@link WifiTracker#isConnected()} should be called to get the updated state. 1082 */ onConnectedChanged()1083 void onConnectedChanged(); 1084 1085 /** 1086 * Called to indicate the list of AccessPoints has been updated and 1087 * {@link WifiTracker#getAccessPoints()} should be called to get the updated list. 1088 */ onAccessPointsChanged()1089 void onAccessPointsChanged(); 1090 } 1091 1092 /** 1093 * Invokes {@link WifiListenerExecutor#onAccessPointsChanged()} iif {@link #mStaleScanResults} 1094 * is false. 1095 */ conditionallyNotifyListeners()1096 private void conditionallyNotifyListeners() { 1097 if (mStaleScanResults) { 1098 return; 1099 } 1100 1101 mListener.onAccessPointsChanged(); 1102 } 1103 1104 /** 1105 * Filters unsupported networks from scan results. New WPA3 networks and OWE networks 1106 * may not be compatible with the device HW/SW. 1107 * @param scanResults List of scan results 1108 * @return List of filtered scan results based on local device capabilities 1109 */ filterScanResultsByCapabilities(List<ScanResult> scanResults)1110 private List<ScanResult> filterScanResultsByCapabilities(List<ScanResult> scanResults) { 1111 if (scanResults == null) { 1112 return null; 1113 } 1114 1115 // Get and cache advanced capabilities 1116 final boolean isOweSupported = mWifiManager.isEnhancedOpenSupported(); 1117 final boolean isSaeSupported = mWifiManager.isWpa3SaeSupported(); 1118 final boolean isSuiteBSupported = mWifiManager.isWpa3SuiteBSupported(); 1119 1120 List<ScanResult> filteredScanResultList = new ArrayList<>(); 1121 1122 // Iterate through the list of scan results and filter out APs which are not 1123 // compatible with our device. 1124 for (ScanResult scanResult : scanResults) { 1125 if (scanResult.capabilities.contains(WIFI_SECURITY_PSK)) { 1126 // All devices (today) support RSN-PSK or WPA-PSK 1127 // Add this here because some APs may support both PSK and SAE and the check 1128 // below will filter it out. 1129 filteredScanResultList.add(scanResult); 1130 continue; 1131 } 1132 1133 if ((scanResult.capabilities.contains(WIFI_SECURITY_SUITE_B_192) && !isSuiteBSupported) 1134 || (scanResult.capabilities.contains(WIFI_SECURITY_SAE) && !isSaeSupported) 1135 || (scanResult.capabilities.contains(WIFI_SECURITY_OWE) && !isOweSupported)) { 1136 if (isVerboseLoggingEnabled()) { 1137 Log.v(TAG, "filterScanResultsByCapabilities: Filtering SSID " 1138 + scanResult.SSID + " with capabilities: " + scanResult.capabilities); 1139 } 1140 } else { 1141 // Safe to add 1142 filteredScanResultList.add(scanResult); 1143 } 1144 } 1145 1146 return filteredScanResultList; 1147 } 1148 } 1149