1 /* 2 * Copyright (C) 2019 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 17 package com.android.wifitrackerlib; 18 19 import static android.os.Build.VERSION_CODES; 20 21 import static androidx.core.util.Preconditions.checkNotNull; 22 23 import static com.android.wifitrackerlib.OsuWifiEntry.osuProviderToOsuWifiEntryKey; 24 import static com.android.wifitrackerlib.PasspointWifiEntry.uniqueIdToPasspointWifiEntryKey; 25 import static com.android.wifitrackerlib.StandardWifiEntry.ScanResultKey; 26 import static com.android.wifitrackerlib.StandardWifiEntry.StandardWifiEntryKey; 27 import static com.android.wifitrackerlib.WifiEntry.CONNECTED_STATE_DISCONNECTED; 28 import static com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE; 29 30 import static java.util.stream.Collectors.toList; 31 import static java.util.stream.Collectors.toMap; 32 33 import android.annotation.TargetApi; 34 import android.content.Context; 35 import android.content.Intent; 36 import android.net.ConnectivityDiagnosticsManager; 37 import android.net.ConnectivityManager; 38 import android.net.LinkProperties; 39 import android.net.Network; 40 import android.net.NetworkCapabilities; 41 import android.net.NetworkInfo; 42 import android.net.wifi.ScanResult; 43 import android.net.wifi.WifiConfiguration; 44 import android.net.wifi.WifiInfo; 45 import android.net.wifi.WifiManager; 46 import android.net.wifi.hotspot2.OsuProvider; 47 import android.net.wifi.hotspot2.PasspointConfiguration; 48 import android.net.wifi.sharedconnectivity.app.HotspotNetwork; 49 import android.net.wifi.sharedconnectivity.app.HotspotNetworkConnectionStatus; 50 import android.net.wifi.sharedconnectivity.app.KnownNetwork; 51 import android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus; 52 import android.os.Handler; 53 import android.telephony.SubscriptionManager; 54 import android.text.TextUtils; 55 import android.util.ArrayMap; 56 import android.util.ArraySet; 57 import android.util.Log; 58 import android.util.Pair; 59 import android.util.SparseArray; 60 61 import androidx.annotation.AnyThread; 62 import androidx.annotation.GuardedBy; 63 import androidx.annotation.MainThread; 64 import androidx.annotation.NonNull; 65 import androidx.annotation.Nullable; 66 import androidx.annotation.VisibleForTesting; 67 import androidx.annotation.WorkerThread; 68 import androidx.core.os.BuildCompat; 69 import androidx.lifecycle.Lifecycle; 70 71 import java.time.Clock; 72 import java.util.ArrayList; 73 import java.util.Collections; 74 import java.util.List; 75 import java.util.Map; 76 import java.util.Set; 77 import java.util.StringJoiner; 78 import java.util.TreeSet; 79 import java.util.function.Function; 80 import java.util.stream.Collectors; 81 82 /** 83 * Wi-Fi tracker that provides all Wi-Fi related data to the Wi-Fi picker page. 84 * 85 * These include 86 * - The connected WifiEntry 87 * - List of all visible WifiEntries 88 * - Number of saved networks 89 * - Number of saved subscriptions 90 */ 91 public class WifiPickerTracker extends BaseWifiTracker { 92 93 private static final String TAG = "WifiPickerTracker"; 94 95 private final WifiPickerTrackerCallback mListener; 96 97 // Lock object for data returned by the public API 98 private final Object mLock = new Object(); 99 // List representing the return value of the getActiveWifiEntries() API 100 @GuardedBy("mLock") 101 @NonNull private final List<WifiEntry> mActiveWifiEntries = new ArrayList<>(); 102 // List representing the return value of the getWifiEntries() API 103 @GuardedBy("mLock") 104 @NonNull private final List<WifiEntry> mWifiEntries = new ArrayList<>(); 105 // NetworkRequestEntry representing a network that was connected through the NetworkRequest API 106 private NetworkRequestEntry mNetworkRequestEntry; 107 108 // Cache containing saved WifiConfigurations mapped by StandardWifiEntry key 109 private final Map<StandardWifiEntryKey, List<WifiConfiguration>> mStandardWifiConfigCache = 110 new ArrayMap<>(); 111 // Cache containing suggested WifiConfigurations mapped by StandardWifiEntry key 112 private final Map<StandardWifiEntryKey, List<WifiConfiguration>> mSuggestedConfigCache = 113 new ArrayMap<>(); 114 // Cache containing network request WifiConfigurations mapped by StandardWifiEntry key. 115 private final ArrayMap<StandardWifiEntryKey, List<WifiConfiguration>> 116 mNetworkRequestConfigCache = new ArrayMap<>(); 117 // Cache containing visible StandardWifiEntries. Must be accessed only by the worker thread. 118 private final List<StandardWifiEntry> mStandardWifiEntryCache = new ArrayList<>(); 119 // Cache containing available suggested StandardWifiEntries. These entries may be already 120 // represented in mStandardWifiEntryCache, so filtering must be done before they are returned in 121 // getWifiEntry() and getConnectedWifiEntry(). 122 private final List<StandardWifiEntry> mSuggestedWifiEntryCache = new ArrayList<>(); 123 // Cache containing saved PasspointConfigurations mapped by PasspointWifiEntry key. 124 private final Map<String, PasspointConfiguration> mPasspointConfigCache = new ArrayMap<>(); 125 // Cache containing Passpoint WifiConfigurations mapped by network id. 126 private final SparseArray<WifiConfiguration> mPasspointWifiConfigCache = new SparseArray<>(); 127 // Cache containing visible PasspointWifiEntries. Must be accessed only by the worker thread. 128 private final Map<String, PasspointWifiEntry> mPasspointWifiEntryCache = new ArrayMap<>(); 129 // Cache containing visible OsuWifiEntries. Must be accessed only by the worker thread. 130 private final Map<String, OsuWifiEntry> mOsuWifiEntryCache = new ArrayMap<>(); 131 132 private MergedCarrierEntry mMergedCarrierEntry; 133 134 private int mNumSavedNetworks; 135 136 private final List<KnownNetwork> mKnownNetworkDataCache = new ArrayList<>(); 137 private final List<KnownNetworkEntry> mKnownNetworkEntryCache = new ArrayList<>(); 138 private final List<HotspotNetwork> mHotspotNetworkDataCache = new ArrayList<>(); 139 private final List<HotspotNetworkEntry> mHotspotNetworkEntryCache = new ArrayList<>(); 140 141 /** 142 * Constructor for WifiPickerTracker. 143 * @param lifecycle Lifecycle this is tied to for lifecycle callbacks. 144 * @param context Context for registering broadcast receiver and for resource strings. 145 * @param wifiManager Provides all Wi-Fi info. 146 * @param connectivityManager Provides network info. 147 * @param mainHandler Handler for processing listener callbacks. 148 * @param workerHandler Handler for processing all broadcasts and running the Scanner. 149 * @param clock Clock used for evaluating the age of scans 150 * @param maxScanAgeMillis Max age for tracked WifiEntries. 151 * @param scanIntervalMillis Interval between initiating scans. 152 * @param listener WifiTrackerCallback listening on changes to WifiPickerTracker data. 153 */ WifiPickerTracker(@onNull Lifecycle lifecycle, @NonNull Context context, @NonNull WifiManager wifiManager, @NonNull ConnectivityManager connectivityManager, @NonNull Handler mainHandler, @NonNull Handler workerHandler, @NonNull Clock clock, long maxScanAgeMillis, long scanIntervalMillis, @Nullable WifiPickerTrackerCallback listener)154 public WifiPickerTracker(@NonNull Lifecycle lifecycle, @NonNull Context context, 155 @NonNull WifiManager wifiManager, 156 @NonNull ConnectivityManager connectivityManager, 157 @NonNull Handler mainHandler, 158 @NonNull Handler workerHandler, 159 @NonNull Clock clock, 160 long maxScanAgeMillis, 161 long scanIntervalMillis, 162 @Nullable WifiPickerTrackerCallback listener) { 163 this(new WifiTrackerInjector(context), lifecycle, context, wifiManager, connectivityManager, 164 mainHandler, workerHandler, clock, maxScanAgeMillis, scanIntervalMillis, listener); 165 } 166 167 @VisibleForTesting WifiPickerTracker( @onNull WifiTrackerInjector injector, @NonNull Lifecycle lifecycle, @NonNull Context context, @NonNull WifiManager wifiManager, @NonNull ConnectivityManager connectivityManager, @NonNull Handler mainHandler, @NonNull Handler workerHandler, @NonNull Clock clock, long maxScanAgeMillis, long scanIntervalMillis, @Nullable WifiPickerTrackerCallback listener)168 WifiPickerTracker( 169 @NonNull WifiTrackerInjector injector, 170 @NonNull Lifecycle lifecycle, 171 @NonNull Context context, 172 @NonNull WifiManager wifiManager, 173 @NonNull ConnectivityManager connectivityManager, 174 @NonNull Handler mainHandler, 175 @NonNull Handler workerHandler, 176 @NonNull Clock clock, 177 long maxScanAgeMillis, 178 long scanIntervalMillis, 179 @Nullable WifiPickerTrackerCallback listener) { 180 super(injector, lifecycle, context, wifiManager, connectivityManager, 181 mainHandler, workerHandler, clock, maxScanAgeMillis, scanIntervalMillis, listener, 182 TAG); 183 mListener = listener; 184 } 185 186 /** 187 * Returns the WifiEntry representing the current primary connection. 188 */ 189 @AnyThread getConnectedWifiEntry()190 public @Nullable WifiEntry getConnectedWifiEntry() { 191 synchronized (mLock) { 192 if (mActiveWifiEntries.isEmpty()) { 193 return null; 194 } 195 // Primary entry is sorted to be first. 196 WifiEntry primaryWifiEntry = mActiveWifiEntries.get(0); 197 if (!primaryWifiEntry.isPrimaryNetwork()) { 198 return null; 199 } 200 return primaryWifiEntry; 201 } 202 } 203 204 /** 205 * Returns a list of all connected/connecting Wi-Fi entries, including the primary and any 206 * secondary connections. 207 */ 208 @AnyThread getActiveWifiEntries()209 public @NonNull List<WifiEntry> getActiveWifiEntries() { 210 synchronized (mLock) { 211 return new ArrayList<>(mActiveWifiEntries); 212 } 213 } 214 215 /** 216 * Returns a list of disconnected, in-range WifiEntries. 217 * 218 * The currently connected entry is omitted and may be accessed through 219 * {@link #getConnectedWifiEntry()} 220 */ 221 @AnyThread getWifiEntries()222 public @NonNull List<WifiEntry> getWifiEntries() { 223 synchronized (mLock) { 224 return new ArrayList<>(mWifiEntries); 225 } 226 } 227 228 /** 229 * Returns the MergedCarrierEntry representing the active carrier subscription. 230 */ 231 @AnyThread getMergedCarrierEntry()232 public @Nullable MergedCarrierEntry getMergedCarrierEntry() { 233 if (!isInitialized() && mMergedCarrierEntry == null) { 234 // Settings currently relies on the MergedCarrierEntry being available before 235 // handleOnStart() is called in order to display the W+ toggle. Populate it here if 236 // we aren't initialized yet. 237 int subId = SubscriptionManager.getDefaultDataSubscriptionId(); 238 if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 239 mMergedCarrierEntry = new MergedCarrierEntry(mInjector, mWorkerHandler, 240 mWifiManager, /* forSavedNetworksPage */ false, subId); 241 } 242 } 243 return mMergedCarrierEntry; 244 } 245 246 /** 247 * Returns the number of saved networks. 248 */ 249 @AnyThread getNumSavedNetworks()250 public int getNumSavedNetworks() { 251 return mNumSavedNetworks; 252 } 253 254 /** 255 * Returns the number of saved subscriptions. 256 */ 257 @AnyThread getNumSavedSubscriptions()258 public int getNumSavedSubscriptions() { 259 return mPasspointConfigCache.size(); 260 } 261 getAllWifiEntries()262 private List<WifiEntry> getAllWifiEntries() { 263 List<WifiEntry> allEntries = new ArrayList<>(); 264 allEntries.addAll(mStandardWifiEntryCache); 265 allEntries.addAll(mSuggestedWifiEntryCache); 266 allEntries.addAll(mPasspointWifiEntryCache.values()); 267 allEntries.addAll(mOsuWifiEntryCache.values()); 268 if (mInjector.isSharedConnectivityFeatureEnabled()) { 269 allEntries.addAll(mKnownNetworkEntryCache); 270 allEntries.addAll(mHotspotNetworkEntryCache); 271 } 272 if (mNetworkRequestEntry != null) { 273 allEntries.add(mNetworkRequestEntry); 274 } 275 if (mMergedCarrierEntry != null) { 276 allEntries.add(mMergedCarrierEntry); 277 } 278 return allEntries; 279 } 280 clearAllWifiEntries()281 private void clearAllWifiEntries() { 282 mStandardWifiEntryCache.clear(); 283 mSuggestedWifiEntryCache.clear(); 284 mPasspointWifiEntryCache.clear(); 285 mOsuWifiEntryCache.clear(); 286 if (mInjector.isSharedConnectivityFeatureEnabled()) { 287 mKnownNetworkEntryCache.clear(); 288 mHotspotNetworkEntryCache.clear(); 289 } 290 mNetworkRequestEntry = null; 291 } 292 293 @WorkerThread 294 @Override handleOnStart()295 protected void handleOnStart() { 296 // Remove stale WifiEntries remaining from the last onStop(). 297 clearAllWifiEntries(); 298 299 // Update configs and scans 300 updateWifiConfigurations(mWifiManager.getPrivilegedConfiguredNetworks()); 301 updatePasspointConfigurations(mWifiManager.getPasspointConfigurations()); 302 mScanResultUpdater.update(mWifiManager.getScanResults()); 303 conditionallyUpdateScanResults(true /* lastScanSucceeded */); 304 305 // Trigger callbacks manually now to avoid waiting until the first calls to update state. 306 handleDefaultSubscriptionChanged(SubscriptionManager.getDefaultDataSubscriptionId()); 307 Network currentNetwork = mWifiManager.getCurrentNetwork(); 308 if (currentNetwork != null) { 309 NetworkCapabilities networkCapabilities = 310 mConnectivityManager.getNetworkCapabilities(currentNetwork); 311 if (networkCapabilities != null) { 312 // getNetworkCapabilities(Network) obfuscates location info such as SSID and 313 // networkId, so we need to set the WifiInfo directly from WifiManager. 314 handleNetworkCapabilitiesChanged(currentNetwork, 315 new NetworkCapabilities.Builder(networkCapabilities) 316 .setTransportInfo(mWifiManager.getConnectionInfo()) 317 .build()); 318 } 319 LinkProperties linkProperties = mConnectivityManager.getLinkProperties(currentNetwork); 320 if (linkProperties != null) { 321 handleLinkPropertiesChanged(currentNetwork, linkProperties); 322 } 323 } 324 notifyOnNumSavedNetworksChanged(); 325 notifyOnNumSavedSubscriptionsChanged(); 326 updateWifiEntries(); 327 } 328 329 @WorkerThread 330 @Override handleWifiStateChangedAction()331 protected void handleWifiStateChangedAction() { 332 if (getWifiState() == WifiManager.WIFI_STATE_DISABLED) { 333 clearAllWifiEntries(); 334 } 335 updateWifiEntries(); 336 } 337 338 @WorkerThread 339 @Override handleScanResultsAvailableAction(@onNull Intent intent)340 protected void handleScanResultsAvailableAction(@NonNull Intent intent) { 341 checkNotNull(intent, "Intent cannot be null!"); 342 conditionallyUpdateScanResults( 343 intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, true)); 344 updateWifiEntries(); 345 } 346 347 @WorkerThread 348 @Override handleConfiguredNetworksChangedAction(@onNull Intent intent)349 protected void handleConfiguredNetworksChangedAction(@NonNull Intent intent) { 350 checkNotNull(intent, "Intent cannot be null!"); 351 352 processConfiguredNetworksChanged(); 353 } 354 355 @WorkerThread 356 /** All wifi entries and saved entries needs to be updated. */ processConfiguredNetworksChanged()357 protected void processConfiguredNetworksChanged() { 358 updateWifiConfigurations(mWifiManager.getPrivilegedConfiguredNetworks()); 359 updatePasspointConfigurations(mWifiManager.getPasspointConfigurations()); 360 // Update scans since config changes may result in different entries being shown. 361 final List<ScanResult> scanResults = mScanResultUpdater.getScanResults(); 362 updateStandardWifiEntryScans(scanResults); 363 updateNetworkRequestEntryScans(scanResults); 364 updatePasspointWifiEntryScans(scanResults); 365 updateOsuWifiEntryScans(scanResults); 366 if (mInjector.isSharedConnectivityFeatureEnabled() && BuildCompat.isAtLeastU()) { 367 updateKnownNetworkEntryScans(scanResults); 368 // Updating the hotspot entries here makes the UI more reliable when switching pages or 369 // when toggling settings while the internet picker is shown. 370 updateHotspotNetworkEntries(); 371 } 372 notifyOnNumSavedNetworksChanged(); 373 notifyOnNumSavedSubscriptionsChanged(); 374 updateWifiEntries(); 375 } 376 377 @WorkerThread 378 @Override handleNetworkStateChangedAction(@onNull Intent intent)379 protected void handleNetworkStateChangedAction(@NonNull Intent intent) { 380 WifiInfo primaryWifiInfo = mWifiManager.getConnectionInfo(); 381 NetworkInfo networkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); 382 if (primaryWifiInfo != null) { 383 conditionallyCreateConnectedWifiEntry(primaryWifiInfo); 384 } 385 for (WifiEntry entry : getAllWifiEntries()) { 386 entry.onPrimaryWifiInfoChanged(primaryWifiInfo, networkInfo); 387 } 388 updateWifiEntries(); 389 } 390 391 @WorkerThread 392 @Override handleRssiChangedAction(@onNull Intent intent)393 protected void handleRssiChangedAction(@NonNull Intent intent) { 394 // RSSI is available via the new WifiInfo object, which is used to populate the RSSI in the 395 // verbose summary. 396 WifiInfo primaryWifiInfo = mWifiManager.getConnectionInfo(); 397 for (WifiEntry entry : getAllWifiEntries()) { 398 entry.onPrimaryWifiInfoChanged(primaryWifiInfo, null); 399 } 400 } 401 402 @WorkerThread 403 @Override handleLinkPropertiesChanged( @onNull Network network, @Nullable LinkProperties linkProperties)404 protected void handleLinkPropertiesChanged( 405 @NonNull Network network, @Nullable LinkProperties linkProperties) { 406 for (WifiEntry entry : getAllWifiEntries()) { 407 entry.updateLinkProperties(network, linkProperties); 408 } 409 } 410 411 @WorkerThread 412 @Override handleNetworkCapabilitiesChanged( @onNull Network network, @NonNull NetworkCapabilities capabilities)413 protected void handleNetworkCapabilitiesChanged( 414 @NonNull Network network, @NonNull NetworkCapabilities capabilities) { 415 updateNetworkCapabilities(network, capabilities); 416 updateWifiEntries(); 417 } 418 419 @WorkerThread 420 @Override handleNetworkLost(@onNull Network network)421 protected void handleNetworkLost(@NonNull Network network) { 422 for (WifiEntry entry : getAllWifiEntries()) { 423 entry.onNetworkLost(network); 424 } 425 if (mNetworkRequestEntry != null 426 && mNetworkRequestEntry.getConnectedState() == CONNECTED_STATE_DISCONNECTED) { 427 mNetworkRequestEntry = null; 428 } 429 updateWifiEntries(); 430 } 431 432 @WorkerThread 433 @Override handleConnectivityReportAvailable( @onNull ConnectivityDiagnosticsManager.ConnectivityReport connectivityReport)434 protected void handleConnectivityReportAvailable( 435 @NonNull ConnectivityDiagnosticsManager.ConnectivityReport connectivityReport) { 436 for (WifiEntry entry : getAllWifiEntries()) { 437 entry.updateConnectivityReport(connectivityReport); 438 } 439 } 440 441 @WorkerThread handleDefaultNetworkCapabilitiesChanged(@onNull Network network, @NonNull NetworkCapabilities networkCapabilities)442 protected void handleDefaultNetworkCapabilitiesChanged(@NonNull Network network, 443 @NonNull NetworkCapabilities networkCapabilities) { 444 for (WifiEntry entry : getAllWifiEntries()) { 445 entry.onDefaultNetworkCapabilitiesChanged(network, networkCapabilities); 446 } 447 } 448 449 @WorkerThread 450 @Override handleDefaultNetworkLost()451 protected void handleDefaultNetworkLost() { 452 for (WifiEntry entry : getAllWifiEntries()) { 453 entry.onDefaultNetworkLost(); 454 } 455 } 456 457 @WorkerThread 458 @Override handleDefaultSubscriptionChanged(int defaultSubId)459 protected void handleDefaultSubscriptionChanged(int defaultSubId) { 460 updateMergedCarrierEntry(defaultSubId); 461 } 462 463 @TargetApi(VERSION_CODES.UPSIDE_DOWN_CAKE) 464 @WorkerThread 465 @Override handleKnownNetworksUpdated(List<KnownNetwork> networks)466 protected void handleKnownNetworksUpdated(List<KnownNetwork> networks) { 467 if (mInjector.isSharedConnectivityFeatureEnabled()) { 468 mKnownNetworkDataCache.clear(); 469 mKnownNetworkDataCache.addAll(networks); 470 updateKnownNetworkEntryScans(mScanResultUpdater.getScanResults()); 471 updateWifiEntries(); 472 } 473 } 474 475 @TargetApi(VERSION_CODES.UPSIDE_DOWN_CAKE) 476 @WorkerThread 477 @Override handleHotspotNetworksUpdated(List<HotspotNetwork> networks)478 protected void handleHotspotNetworksUpdated(List<HotspotNetwork> networks) { 479 if (mInjector.isSharedConnectivityFeatureEnabled()) { 480 mHotspotNetworkDataCache.clear(); 481 mHotspotNetworkDataCache.addAll(networks); 482 updateHotspotNetworkEntries(); 483 updateWifiEntries(); 484 } 485 } 486 @TargetApi(VERSION_CODES.UPSIDE_DOWN_CAKE) 487 @WorkerThread handleHotspotNetworkConnectionStatusChanged( @onNull HotspotNetworkConnectionStatus status)488 protected void handleHotspotNetworkConnectionStatusChanged( 489 @NonNull HotspotNetworkConnectionStatus status) { 490 mHotspotNetworkEntryCache.stream().filter( 491 entry -> entry.getHotspotNetworkEntryKey().getDeviceId() 492 == status.getHotspotNetwork().getDeviceId()).forEach( 493 entry -> entry.onConnectionStatusChanged(status.getStatus())); 494 } 495 496 @TargetApi(VERSION_CODES.UPSIDE_DOWN_CAKE) 497 @WorkerThread 498 @Override handleKnownNetworkConnectionStatusChanged( @onNull KnownNetworkConnectionStatus status)499 protected void handleKnownNetworkConnectionStatusChanged( 500 @NonNull KnownNetworkConnectionStatus status) { 501 final ScanResultKey key = new ScanResultKey(status.getKnownNetwork().getSsid(), 502 status.getKnownNetwork().getSecurityTypes().stream().toList()); 503 mKnownNetworkEntryCache.stream().filter( 504 entry -> entry.getStandardWifiEntryKey().getScanResultKey().equals(key)).forEach( 505 entry -> entry.onConnectionStatusChanged(status.getStatus())); 506 } 507 508 @TargetApi(VERSION_CODES.UPSIDE_DOWN_CAKE) 509 @WorkerThread 510 @Override handleServiceConnected()511 protected void handleServiceConnected() { 512 if (mInjector.isSharedConnectivityFeatureEnabled()) { 513 mKnownNetworkDataCache.clear(); 514 mKnownNetworkDataCache.addAll(mSharedConnectivityManager.getKnownNetworks()); 515 mHotspotNetworkDataCache.clear(); 516 mHotspotNetworkDataCache.addAll(mSharedConnectivityManager.getHotspotNetworks()); 517 updateKnownNetworkEntryScans(mScanResultUpdater.getScanResults()); 518 updateHotspotNetworkEntries(); 519 updateWifiEntries(); 520 } 521 } 522 523 @TargetApi(VERSION_CODES.UPSIDE_DOWN_CAKE) 524 @WorkerThread 525 @Override handleServiceDisconnected()526 protected void handleServiceDisconnected() { 527 if (mInjector.isSharedConnectivityFeatureEnabled()) { 528 mKnownNetworkDataCache.clear(); 529 mHotspotNetworkDataCache.clear(); 530 mKnownNetworkEntryCache.clear(); 531 mHotspotNetworkEntryCache.clear(); 532 updateWifiEntries(); 533 } 534 } 535 536 /** 537 * Update the list returned by getWifiEntries() with the current states of the entry caches. 538 */ 539 @WorkerThread updateWifiEntries()540 protected void updateWifiEntries() { 541 synchronized (mLock) { 542 mActiveWifiEntries.clear(); 543 mActiveWifiEntries.addAll(mStandardWifiEntryCache); 544 mActiveWifiEntries.addAll(mSuggestedWifiEntryCache); 545 mActiveWifiEntries.addAll(mPasspointWifiEntryCache.values()); 546 if (mInjector.isSharedConnectivityFeatureEnabled()) { 547 mActiveWifiEntries.addAll(mHotspotNetworkEntryCache); 548 } 549 if (mNetworkRequestEntry != null) { 550 mActiveWifiEntries.add(mNetworkRequestEntry); 551 } 552 mActiveWifiEntries.removeIf(entry -> 553 entry.getConnectedState() == CONNECTED_STATE_DISCONNECTED); 554 Set<ScanResultKey> activeHotspotNetworkKeys = new ArraySet<>(); 555 for (WifiEntry entry : mActiveWifiEntries) { 556 if (entry instanceof HotspotNetworkEntry) { 557 activeHotspotNetworkKeys.add(((HotspotNetworkEntry) entry) 558 .getHotspotNetworkEntryKey().getScanResultKey()); 559 } 560 } 561 mActiveWifiEntries.removeIf(entry -> entry instanceof StandardWifiEntry 562 && activeHotspotNetworkKeys.contains( 563 ((StandardWifiEntry) entry).getStandardWifiEntryKey().getScanResultKey())); 564 mActiveWifiEntries.sort(WifiEntry.WIFI_PICKER_COMPARATOR); 565 mWifiEntries.clear(); 566 final Set<ScanResultKey> scanResultKeysWithVisibleSuggestions = 567 mSuggestedWifiEntryCache.stream() 568 .filter(entry -> { 569 if (entry.isUserShareable()) return true; 570 synchronized (mLock) { 571 return mActiveWifiEntries.contains(entry); 572 } 573 }) 574 .map(entry -> entry.getStandardWifiEntryKey().getScanResultKey()) 575 .collect(Collectors.toSet()); 576 Set<String> passpointUtf8Ssids = new ArraySet<>(); 577 for (PasspointWifiEntry passpointWifiEntry : mPasspointWifiEntryCache.values()) { 578 passpointUtf8Ssids.addAll(passpointWifiEntry.getAllUtf8Ssids()); 579 } 580 Set<ScanResultKey> knownNetworkKeys = new ArraySet<>(); 581 for (KnownNetworkEntry knownNetworkEntry : mKnownNetworkEntryCache) { 582 knownNetworkKeys.add( 583 knownNetworkEntry.getStandardWifiEntryKey().getScanResultKey()); 584 } 585 Set<ScanResultKey> hotspotNetworkKeys = new ArraySet<>(); 586 for (HotspotNetworkEntry hotspotNetworkEntry : mHotspotNetworkEntryCache) { 587 if (!hotspotNetworkEntry.getHotspotNetworkEntryKey().isVirtualEntry()) { 588 hotspotNetworkKeys.add( 589 hotspotNetworkEntry.getHotspotNetworkEntryKey().getScanResultKey()); 590 } 591 } 592 Set<ScanResultKey> savedEntryKeys = new ArraySet<>(); 593 for (StandardWifiEntry entry : mStandardWifiEntryCache) { 594 entry.updateAdminRestrictions(); 595 if (mActiveWifiEntries.contains(entry)) { 596 continue; 597 } 598 if (!entry.isSaved()) { 599 if (scanResultKeysWithVisibleSuggestions 600 .contains(entry.getStandardWifiEntryKey().getScanResultKey())) { 601 continue; 602 } 603 // Filter out any unsaved entries that are already provisioned with Passpoint 604 if (passpointUtf8Ssids.contains(entry.getSsid())) { 605 continue; 606 } 607 if (mInjector.isSharedConnectivityFeatureEnabled()) { 608 // Filter out any unsaved entries that are matched with a KnownNetworkEntry 609 if (knownNetworkKeys 610 .contains(entry.getStandardWifiEntryKey().getScanResultKey())) { 611 continue; 612 } 613 } 614 } else { 615 // Create a set of saved entry keys 616 savedEntryKeys.add(entry.getStandardWifiEntryKey().getScanResultKey()); 617 } 618 if (mInjector.isSharedConnectivityFeatureEnabled()) { 619 // Filter out any entries that are matched with a HotspotNetworkEntry 620 if (hotspotNetworkKeys 621 .contains(entry.getStandardWifiEntryKey().getScanResultKey())) { 622 continue; 623 } 624 } 625 mWifiEntries.add(entry); 626 } 627 mWifiEntries.addAll(mSuggestedWifiEntryCache.stream().filter(entry -> 628 entry.getConnectedState() == CONNECTED_STATE_DISCONNECTED 629 && entry.isUserShareable()).collect(toList())); 630 mWifiEntries.addAll(mPasspointWifiEntryCache.values().stream().filter(entry -> 631 entry.getConnectedState() == CONNECTED_STATE_DISCONNECTED).collect(toList())); 632 mWifiEntries.addAll(mOsuWifiEntryCache.values().stream().filter(entry -> 633 entry.getConnectedState() == CONNECTED_STATE_DISCONNECTED 634 && !entry.isAlreadyProvisioned()).collect(toList())); 635 mWifiEntries.addAll(getContextualWifiEntries().stream().filter(entry -> 636 entry.getConnectedState() == CONNECTED_STATE_DISCONNECTED).collect(toList())); 637 if (mInjector.isSharedConnectivityFeatureEnabled()) { 638 mWifiEntries.addAll(mKnownNetworkEntryCache.stream().filter(entry -> 639 (entry.getConnectedState() == CONNECTED_STATE_DISCONNECTED) 640 && !(savedEntryKeys.contains( 641 entry.getStandardWifiEntryKey().getScanResultKey()))).collect( 642 toList())); 643 mWifiEntries.addAll(mHotspotNetworkEntryCache.stream().filter(entry -> 644 entry.getConnectedState() == CONNECTED_STATE_DISCONNECTED).collect( 645 toList())); 646 } 647 Collections.sort(mWifiEntries, WifiEntry.WIFI_PICKER_COMPARATOR); 648 if (isVerboseLoggingEnabled()) { 649 StringJoiner entryLog = new StringJoiner("\n"); 650 int numEntries = mActiveWifiEntries.size() + mWifiEntries.size(); 651 int index = 1; 652 for (WifiEntry entry : mActiveWifiEntries) { 653 entryLog.add("Entry " + index + "/" + numEntries + ": " + entry); 654 index++; 655 } 656 for (WifiEntry entry : mWifiEntries) { 657 entryLog.add("Entry " + index + "/" + numEntries + ": " + entry); 658 index++; 659 } 660 Log.v(TAG, entryLog.toString()); 661 Log.v(TAG, "MergedCarrierEntry: " + mMergedCarrierEntry); 662 } 663 } 664 notifyOnWifiEntriesChanged(); 665 } 666 667 /** 668 * Updates the MergedCarrierEntry returned by {@link #getMergedCarrierEntry()) with the current 669 * default data subscription ID, or sets it to null if not available. 670 */ 671 @WorkerThread updateMergedCarrierEntry(int subId)672 private void updateMergedCarrierEntry(int subId) { 673 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 674 if (mMergedCarrierEntry == null) { 675 return; 676 } 677 mMergedCarrierEntry = null; 678 } else { 679 if (mMergedCarrierEntry != null && subId == mMergedCarrierEntry.getSubscriptionId()) { 680 return; 681 } 682 mMergedCarrierEntry = new MergedCarrierEntry(mInjector, mWorkerHandler, mWifiManager, 683 /* forSavedNetworksPage */ false, subId); 684 Network currentNetwork = mWifiManager.getCurrentNetwork(); 685 if (currentNetwork != null) { 686 NetworkCapabilities networkCapabilities = 687 mConnectivityManager.getNetworkCapabilities(currentNetwork); 688 if (networkCapabilities != null) { 689 // getNetworkCapabilities(Network) obfuscates location info such as SSID and 690 // networkId, so we need to set the WifiInfo directly from WifiManager. 691 mMergedCarrierEntry.onNetworkCapabilitiesChanged(currentNetwork, 692 new NetworkCapabilities.Builder(networkCapabilities) 693 .setTransportInfo(mWifiManager.getConnectionInfo()) 694 .build()); 695 } 696 LinkProperties linkProperties = 697 mConnectivityManager.getLinkProperties(currentNetwork); 698 if (linkProperties != null) { 699 mMergedCarrierEntry.updateLinkProperties(currentNetwork, linkProperties); 700 } 701 } 702 } 703 notifyOnWifiEntriesChanged(); 704 } 705 706 /** 707 * Get the contextual WifiEntries added according to customized conditions. 708 */ getContextualWifiEntries()709 protected List<WifiEntry> getContextualWifiEntries() { 710 return Collections.emptyList(); 711 } 712 713 /** 714 * Update the contextual wifi entry according to customized conditions. 715 */ updateContextualWifiEntryScans(@onNull List<ScanResult> scanResults)716 protected void updateContextualWifiEntryScans(@NonNull List<ScanResult> scanResults) { 717 // do nothing 718 } 719 720 /** 721 * Updates or removes scan results for the corresponding StandardWifiEntries. 722 * New entries will be created for scan results without an existing entry. 723 * Unreachable entries will be removed. 724 * 725 * @param scanResults List of valid scan results to convey as StandardWifiEntries 726 */ 727 @WorkerThread updateStandardWifiEntryScans(@onNull List<ScanResult> scanResults)728 private void updateStandardWifiEntryScans(@NonNull List<ScanResult> scanResults) { 729 checkNotNull(scanResults, "Scan Result list should not be null!"); 730 731 // Group scans by ScanResultKey key 732 final Map<ScanResultKey, List<ScanResult>> scanResultsByKey = scanResults.stream() 733 .filter(scan -> !TextUtils.isEmpty(scan.SSID)) 734 .collect(Collectors.groupingBy(ScanResultKey::new)); 735 final Set<ScanResultKey> newScanKeys = new ArraySet<>(scanResultsByKey.keySet()); 736 737 // Iterate through current entries and update each entry's scan results 738 mStandardWifiEntryCache.forEach(entry -> { 739 final ScanResultKey scanKey = entry.getStandardWifiEntryKey().getScanResultKey(); 740 newScanKeys.remove(scanKey); 741 // Update scan results if available, or set to null. 742 entry.updateScanResultInfo(scanResultsByKey.get(scanKey)); 743 }); 744 // Create new StandardWifiEntry objects for each leftover group of scan results. 745 for (ScanResultKey scanKey: newScanKeys) { 746 final StandardWifiEntryKey entryKey = 747 new StandardWifiEntryKey(scanKey, true /* isTargetingNewNetworks */); 748 final StandardWifiEntry newEntry = new StandardWifiEntry(mInjector, 749 mMainHandler, entryKey, mStandardWifiConfigCache.get(entryKey), 750 scanResultsByKey.get(scanKey), mWifiManager, 751 false /* forSavedNetworksPage */); 752 mStandardWifiEntryCache.add(newEntry); 753 } 754 755 // Remove any entry that is now unreachable due to no scans or unsupported 756 // security types. 757 mStandardWifiEntryCache.removeIf( 758 entry -> entry.getLevel() == WIFI_LEVEL_UNREACHABLE); 759 } 760 761 /** 762 * Updates or removes scan results for the corresponding StandardWifiEntries. 763 * New entries will be created for scan results without an existing entry. 764 * Unreachable entries will be removed. 765 * 766 * @param scanResults List of valid scan results to convey as StandardWifiEntries 767 */ 768 @WorkerThread updateSuggestedWifiEntryScans(@onNull List<ScanResult> scanResults)769 private void updateSuggestedWifiEntryScans(@NonNull List<ScanResult> scanResults) { 770 checkNotNull(scanResults, "Scan Result list should not be null!"); 771 772 // Get every ScanResultKey that is user shareable 773 final Set<StandardWifiEntryKey> userSharedEntryKeys = 774 mWifiManager.getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(scanResults) 775 .stream() 776 .map(StandardWifiEntryKey::new) 777 .collect(Collectors.toSet()); 778 779 // Group scans by ScanResultKey key 780 final Map<ScanResultKey, List<ScanResult>> scanResultsByKey = scanResults.stream() 781 .filter(scan -> !TextUtils.isEmpty(scan.SSID)) 782 .collect(Collectors.groupingBy(ScanResultKey::new)); 783 784 // Iterate through current entries and update each entry's scan results and shareability. 785 final Set<StandardWifiEntryKey> seenEntryKeys = new ArraySet<>(); 786 mSuggestedWifiEntryCache.forEach(entry -> { 787 final StandardWifiEntryKey entryKey = entry.getStandardWifiEntryKey(); 788 seenEntryKeys.add(entryKey); 789 // Update scan results if available, or set to null. 790 entry.updateScanResultInfo(scanResultsByKey.get(entryKey.getScanResultKey())); 791 entry.setUserShareable(userSharedEntryKeys.contains(entryKey)); 792 }); 793 // Create new StandardWifiEntry objects for each leftover config with scan results. 794 for (StandardWifiEntryKey entryKey : mSuggestedConfigCache.keySet()) { 795 final ScanResultKey scanKey = entryKey.getScanResultKey(); 796 if (seenEntryKeys.contains(entryKey) 797 || !scanResultsByKey.containsKey(scanKey)) { 798 continue; 799 } 800 final StandardWifiEntry newEntry = new StandardWifiEntry(mInjector, 801 mMainHandler, entryKey, mSuggestedConfigCache.get(entryKey), 802 scanResultsByKey.get(scanKey), mWifiManager, 803 false /* forSavedNetworksPage */); 804 newEntry.setUserShareable(userSharedEntryKeys.contains(entryKey)); 805 mSuggestedWifiEntryCache.add(newEntry); 806 } 807 808 // Remove any entry that is now unreachable due to no scans or unsupported 809 // security types. 810 mSuggestedWifiEntryCache.removeIf(entry -> entry.getLevel() == WIFI_LEVEL_UNREACHABLE); 811 } 812 813 @WorkerThread updatePasspointWifiEntryScans(@onNull List<ScanResult> scanResults)814 private void updatePasspointWifiEntryScans(@NonNull List<ScanResult> scanResults) { 815 checkNotNull(scanResults, "Scan Result list should not be null!"); 816 817 Set<String> seenKeys = new TreeSet<>(); 818 List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> matchingWifiConfigs = 819 mWifiManager.getAllMatchingWifiConfigs(scanResults); 820 821 for (Pair<WifiConfiguration, Map<Integer, List<ScanResult>>> pair : matchingWifiConfigs) { 822 final WifiConfiguration wifiConfig = pair.first; 823 final List<ScanResult> homeScans = 824 pair.second.get(WifiManager.PASSPOINT_HOME_NETWORK); 825 final List<ScanResult> roamingScans = 826 pair.second.get(WifiManager.PASSPOINT_ROAMING_NETWORK); 827 final String key = uniqueIdToPasspointWifiEntryKey(wifiConfig.getKey()); 828 seenKeys.add(key); 829 830 // Create PasspointWifiEntry if one doesn't exist for the seen key yet. 831 if (!mPasspointWifiEntryCache.containsKey(key)) { 832 if (wifiConfig.fromWifiNetworkSuggestion) { 833 mPasspointWifiEntryCache.put(key, new PasspointWifiEntry(mInjector, mContext, 834 mMainHandler, wifiConfig, mWifiManager, 835 false /* forSavedNetworksPage */)); 836 } else if (mPasspointConfigCache.containsKey(key)) { 837 mPasspointWifiEntryCache.put(key, new PasspointWifiEntry(mInjector, 838 mMainHandler, mPasspointConfigCache.get(key), mWifiManager, 839 false /* forSavedNetworksPage */)); 840 } else { 841 // Failed to find PasspointConfig for a provisioned Passpoint network 842 continue; 843 } 844 } 845 mPasspointWifiEntryCache.get(key).updateScanResultInfo(wifiConfig, 846 homeScans, roamingScans); 847 } 848 849 // Remove entries that are now unreachable 850 mPasspointWifiEntryCache.entrySet() 851 .removeIf(entry -> entry.getValue().getLevel() == WIFI_LEVEL_UNREACHABLE 852 || (!seenKeys.contains(entry.getKey())) 853 && entry.getValue().getConnectedState() == CONNECTED_STATE_DISCONNECTED); 854 } 855 856 @WorkerThread updateOsuWifiEntryScans(@onNull List<ScanResult> scanResults)857 private void updateOsuWifiEntryScans(@NonNull List<ScanResult> scanResults) { 858 checkNotNull(scanResults, "Scan Result list should not be null!"); 859 860 Map<OsuProvider, List<ScanResult>> osuProviderToScans = 861 mWifiManager.getMatchingOsuProviders(scanResults); 862 Map<OsuProvider, PasspointConfiguration> osuProviderToPasspointConfig = 863 mWifiManager.getMatchingPasspointConfigsForOsuProviders( 864 osuProviderToScans.keySet()); 865 // Update each OsuWifiEntry with new scans (or empty scans). 866 for (OsuWifiEntry entry : mOsuWifiEntryCache.values()) { 867 entry.updateScanResultInfo(osuProviderToScans.remove(entry.getOsuProvider())); 868 } 869 870 // Create a new entry for each OsuProvider not already matched to an OsuWifiEntry 871 for (OsuProvider provider : osuProviderToScans.keySet()) { 872 OsuWifiEntry newEntry = new OsuWifiEntry(mInjector, mMainHandler, provider, 873 mWifiManager, false /* forSavedNetworksPage */); 874 newEntry.updateScanResultInfo(osuProviderToScans.get(provider)); 875 mOsuWifiEntryCache.put(osuProviderToOsuWifiEntryKey(provider), newEntry); 876 } 877 878 // Pass a reference of each OsuWifiEntry to any matching provisioned PasspointWifiEntries 879 // for expiration handling. 880 mOsuWifiEntryCache.values().forEach(osuEntry -> { 881 PasspointConfiguration provisionedConfig = 882 osuProviderToPasspointConfig.get(osuEntry.getOsuProvider()); 883 if (provisionedConfig == null) { 884 osuEntry.setAlreadyProvisioned(false); 885 return; 886 } 887 osuEntry.setAlreadyProvisioned(true); 888 PasspointWifiEntry provisionedEntry = mPasspointWifiEntryCache.get( 889 uniqueIdToPasspointWifiEntryKey(provisionedConfig.getUniqueId())); 890 if (provisionedEntry == null) { 891 return; 892 } 893 provisionedEntry.setOsuWifiEntry(osuEntry); 894 }); 895 896 // Remove entries that are now unreachable 897 mOsuWifiEntryCache.entrySet() 898 .removeIf(entry -> entry.getValue().getLevel() == WIFI_LEVEL_UNREACHABLE); 899 } 900 901 @TargetApi(VERSION_CODES.UPSIDE_DOWN_CAKE) 902 @WorkerThread updateKnownNetworkEntryScans(@onNull List<ScanResult> scanResults)903 private void updateKnownNetworkEntryScans(@NonNull List<ScanResult> scanResults) { 904 checkNotNull(scanResults, "Scan Result list should not be null!"); 905 906 // Group scans by ScanResultKey key 907 final Map<ScanResultKey, List<ScanResult>> scanResultsByKey = scanResults.stream() 908 .filter(scan -> !TextUtils.isEmpty(scan.SSID)) 909 .collect(Collectors.groupingBy(ScanResultKey::new)); 910 911 // Create a map of KnownNetwork data by ScanResultKey 912 final Map<ScanResultKey, KnownNetwork> knownNetworkDataByKey = 913 mKnownNetworkDataCache.stream().collect(Collectors.toMap( 914 data -> new ScanResultKey(data.getSsid(), 915 new ArrayList<>(data.getSecurityTypes())), 916 data -> data, 917 (data1, data2) -> { 918 Log.e(TAG, 919 "Encountered duplicate key data in " 920 + "updateKnownNetworkEntryScans"); 921 return data1; // When duplicate data is encountered, use first one. 922 })); 923 924 // Remove entries not in latest data set from service 925 mKnownNetworkEntryCache.removeIf(entry -> !knownNetworkDataByKey.keySet().contains( 926 entry.getStandardWifiEntryKey().getScanResultKey())); 927 928 // Create set of ScanResultKeys for known networks from service that are included in scan 929 final Set<ScanResultKey> newScanKeys = knownNetworkDataByKey.keySet().stream().filter( 930 scanResultsByKey::containsKey).collect(Collectors.toSet()); 931 932 // Iterate through current entries and update each entry's scan results 933 mKnownNetworkEntryCache.forEach(entry -> { 934 final ScanResultKey scanKey = entry.getStandardWifiEntryKey().getScanResultKey(); 935 newScanKeys.remove(scanKey); 936 // Update scan results if available, or set to null. 937 entry.updateScanResultInfo(scanResultsByKey.get(scanKey)); 938 }); 939 940 // Get network and capabilities if new network entries are being created 941 Network network = null; 942 NetworkCapabilities capabilities = null; 943 if (!newScanKeys.isEmpty()) { 944 network = mWifiManager.getCurrentNetwork(); 945 if (network != null) { 946 capabilities = mConnectivityManager.getNetworkCapabilities(network); 947 if (capabilities != null) { 948 // getNetworkCapabilities(Network) obfuscates location info such as SSID and 949 // networkId, so we need to set the WifiInfo directly from WifiManager. 950 capabilities = new NetworkCapabilities.Builder(capabilities).setTransportInfo( 951 mWifiManager.getConnectionInfo()).build(); 952 } 953 } 954 } 955 956 // Create new KnownNetworkEntry objects for each leftover group of scan results. 957 for (ScanResultKey scanKey : newScanKeys) { 958 final StandardWifiEntryKey entryKey = 959 new StandardWifiEntryKey(scanKey, true /* isTargetingNewNetworks */); 960 final KnownNetworkEntry newEntry = new KnownNetworkEntry(mInjector, 961 mMainHandler, entryKey, null /* configs */, 962 scanResultsByKey.get(scanKey), mWifiManager, 963 mSharedConnectivityManager, knownNetworkDataByKey.get(scanKey)); 964 if (network != null && capabilities != null) { 965 newEntry.onNetworkCapabilitiesChanged(network, capabilities); 966 } 967 mKnownNetworkEntryCache.add(newEntry); 968 } 969 970 // Remove any entry that is now unreachable due to no scans or unsupported 971 // security types. 972 mKnownNetworkEntryCache.removeIf( 973 entry -> entry.getLevel() == WIFI_LEVEL_UNREACHABLE); 974 } 975 976 @TargetApi(VERSION_CODES.UPSIDE_DOWN_CAKE) 977 @WorkerThread updateHotspotNetworkEntries()978 private void updateHotspotNetworkEntries() { 979 // Map HotspotNetwork data by deviceID 980 final Map<Long, HotspotNetwork> hotspotNetworkDataById = 981 mHotspotNetworkDataCache.stream().collect(Collectors.toMap( 982 HotspotNetwork::getDeviceId, 983 data -> data, 984 (data1, data2) -> { 985 Log.e(TAG, 986 "Encountered duplicate key data in " 987 + "updateHotspotNetworkEntries"); 988 return data1; // When duplicate data is encountered, use first one. 989 })); 990 final Set<Long> newDeviceIds = new ArraySet<>(hotspotNetworkDataById.keySet()); 991 992 // Remove entries not in latest data set from service 993 mHotspotNetworkEntryCache.removeIf( 994 entry -> !newDeviceIds.contains(entry.getHotspotNetworkEntryKey().getDeviceId())); 995 996 // Iterate through entries and update HotspotNetwork data 997 mHotspotNetworkEntryCache.forEach(entry -> { 998 final Long deviceId = entry.getHotspotNetworkEntryKey().getDeviceId(); 999 newDeviceIds.remove(deviceId); 1000 entry.updateHotspotNetworkData(hotspotNetworkDataById.get(deviceId)); 1001 }); 1002 1003 // Get network and capabilities if new network entries are being created 1004 Network network = null; 1005 NetworkCapabilities capabilities = null; 1006 if (!newDeviceIds.isEmpty()) { 1007 network = mWifiManager.getCurrentNetwork(); 1008 if (network != null) { 1009 capabilities = mConnectivityManager.getNetworkCapabilities(network); 1010 if (capabilities != null) { 1011 // getNetworkCapabilities(Network) obfuscates location info such as SSID and 1012 // networkId, so we need to set the WifiInfo directly from WifiManager. 1013 capabilities = new NetworkCapabilities.Builder(capabilities).setTransportInfo( 1014 mWifiManager.getConnectionInfo()).build(); 1015 } 1016 } 1017 } 1018 1019 // Create new HotspotNetworkEntry objects for each new device ID 1020 for (Long deviceId : newDeviceIds) { 1021 final HotspotNetworkEntry newEntry = new HotspotNetworkEntry(mInjector, mContext, 1022 mMainHandler, mWifiManager, mSharedConnectivityManager, 1023 hotspotNetworkDataById.get(deviceId)); 1024 if (network != null && capabilities != null) { 1025 newEntry.onNetworkCapabilitiesChanged(network, capabilities); 1026 } 1027 mHotspotNetworkEntryCache.add(newEntry); 1028 } 1029 } 1030 1031 @WorkerThread updateNetworkRequestEntryScans(@onNull List<ScanResult> scanResults)1032 private void updateNetworkRequestEntryScans(@NonNull List<ScanResult> scanResults) { 1033 checkNotNull(scanResults, "Scan Result list should not be null!"); 1034 if (mNetworkRequestEntry == null) { 1035 return; 1036 } 1037 1038 final ScanResultKey scanKey = 1039 mNetworkRequestEntry.getStandardWifiEntryKey().getScanResultKey(); 1040 List<ScanResult> matchedScans = scanResults.stream() 1041 .filter(scan -> scanKey.equals(new ScanResultKey(scan))) 1042 .collect(toList()); 1043 mNetworkRequestEntry.updateScanResultInfo(matchedScans); 1044 } 1045 1046 /** 1047 * Conditionally updates the WifiEntry scan results based on the current wifi state and 1048 * whether the last scan succeeded or not. 1049 */ 1050 @WorkerThread conditionallyUpdateScanResults(boolean lastScanSucceeded)1051 private void conditionallyUpdateScanResults(boolean lastScanSucceeded) { 1052 if (mWifiManager.getWifiState() == WifiManager.WIFI_STATE_DISABLED) { 1053 updateStandardWifiEntryScans(Collections.emptyList()); 1054 updateSuggestedWifiEntryScans(Collections.emptyList()); 1055 updatePasspointWifiEntryScans(Collections.emptyList()); 1056 updateOsuWifiEntryScans(Collections.emptyList()); 1057 if (mInjector.isSharedConnectivityFeatureEnabled() && BuildCompat.isAtLeastU()) { 1058 mKnownNetworkEntryCache.clear(); 1059 mHotspotNetworkEntryCache.clear(); 1060 } 1061 updateNetworkRequestEntryScans(Collections.emptyList()); 1062 updateContextualWifiEntryScans(Collections.emptyList()); 1063 return; 1064 } 1065 1066 long scanAgeWindow = mMaxScanAgeMillis; 1067 if (lastScanSucceeded) { 1068 // Scan succeeded, cache new scans 1069 mScanResultUpdater.update(mWifiManager.getScanResults()); 1070 } else { 1071 // Scan failed, increase scan age window to prevent WifiEntry list from 1072 // clearing prematurely. 1073 scanAgeWindow += mScanIntervalMillis; 1074 } 1075 1076 List<ScanResult> scanResults = mScanResultUpdater.getScanResults(scanAgeWindow); 1077 updateStandardWifiEntryScans(scanResults); 1078 updateSuggestedWifiEntryScans(scanResults); 1079 updatePasspointWifiEntryScans(scanResults); 1080 updateOsuWifiEntryScans(scanResults); 1081 if (mInjector.isSharedConnectivityFeatureEnabled() && BuildCompat.isAtLeastU()) { 1082 updateKnownNetworkEntryScans(scanResults); 1083 // Updating the hotspot entries here makes the UI more reliable when switching pages or 1084 // when toggling settings while the internet picker is shown. 1085 updateHotspotNetworkEntries(); 1086 } 1087 updateNetworkRequestEntryScans(scanResults); 1088 updateContextualWifiEntryScans(scanResults); 1089 } 1090 1091 /** 1092 * Updates the WifiConfiguration caches for saved/ephemeral/suggested networks and updates the 1093 * corresponding WifiEntries with the new configs. 1094 * 1095 * @param configs List of all saved/ephemeral/suggested WifiConfigurations 1096 */ 1097 @WorkerThread updateWifiConfigurations(@onNull List<WifiConfiguration> configs)1098 private void updateWifiConfigurations(@NonNull List<WifiConfiguration> configs) { 1099 checkNotNull(configs, "Config list should not be null!"); 1100 mStandardWifiConfigCache.clear(); 1101 mSuggestedConfigCache.clear(); 1102 mNetworkRequestConfigCache.clear(); 1103 for (WifiConfiguration config : configs) { 1104 if (config.carrierMerged) { 1105 continue; 1106 } 1107 StandardWifiEntryKey standardWifiEntryKey = 1108 new StandardWifiEntryKey(config, true /* isTargetingNewNetworks */); 1109 if (config.isPasspoint()) { 1110 mPasspointWifiConfigCache.put(config.networkId, config); 1111 } else if (config.fromWifiNetworkSuggestion) { 1112 if (!mSuggestedConfigCache.containsKey(standardWifiEntryKey)) { 1113 mSuggestedConfigCache.put(standardWifiEntryKey, new ArrayList<>()); 1114 } 1115 mSuggestedConfigCache.get(standardWifiEntryKey).add(config); 1116 } else if (config.fromWifiNetworkSpecifier) { 1117 if (!mNetworkRequestConfigCache.containsKey(standardWifiEntryKey)) { 1118 mNetworkRequestConfigCache.put(standardWifiEntryKey, new ArrayList<>()); 1119 } 1120 mNetworkRequestConfigCache.get(standardWifiEntryKey).add(config); 1121 } else { 1122 if (!mStandardWifiConfigCache.containsKey(standardWifiEntryKey)) { 1123 mStandardWifiConfigCache.put(standardWifiEntryKey, new ArrayList<>()); 1124 } 1125 mStandardWifiConfigCache.get(standardWifiEntryKey).add(config); 1126 } 1127 } 1128 mNumSavedNetworks = (int) mStandardWifiConfigCache.values().stream() 1129 .flatMap(List::stream) 1130 .filter(config -> !config.isEphemeral()) 1131 .map(config -> config.networkId) 1132 .distinct() 1133 .count(); 1134 1135 // Iterate through current entries and update each entry's config 1136 mStandardWifiEntryCache.forEach(entry -> 1137 entry.updateConfig(mStandardWifiConfigCache.get(entry.getStandardWifiEntryKey()))); 1138 1139 // Iterate through current suggestion entries and update each entry's config 1140 mSuggestedWifiEntryCache.removeIf(entry -> { 1141 entry.updateConfig(mSuggestedConfigCache.get(entry.getStandardWifiEntryKey())); 1142 // Remove if the suggestion does not have a config anymore. 1143 return !entry.isSuggestion(); 1144 }); 1145 // Update suggestion scans to make sure we mark which suggestions are user-shareable. 1146 updateSuggestedWifiEntryScans(mScanResultUpdater.getScanResults()); 1147 1148 if (mNetworkRequestEntry != null) { 1149 mNetworkRequestEntry.updateConfig( 1150 mNetworkRequestConfigCache.get(mNetworkRequestEntry.getStandardWifiEntryKey())); 1151 } 1152 } 1153 1154 @WorkerThread updatePasspointConfigurations(@onNull List<PasspointConfiguration> configs)1155 private void updatePasspointConfigurations(@NonNull List<PasspointConfiguration> configs) { 1156 checkNotNull(configs, "Config list should not be null!"); 1157 mPasspointConfigCache.clear(); 1158 mPasspointConfigCache.putAll(configs.stream().collect( 1159 toMap(config -> uniqueIdToPasspointWifiEntryKey( 1160 config.getUniqueId()), Function.identity()))); 1161 1162 // Iterate through current entries and update each entry's config or remove if no config 1163 // matches the entry anymore. 1164 mPasspointWifiEntryCache.entrySet().removeIf((entry) -> { 1165 final PasspointWifiEntry wifiEntry = entry.getValue(); 1166 final String key = wifiEntry.getKey(); 1167 wifiEntry.updatePasspointConfig(mPasspointConfigCache.get(key)); 1168 return !wifiEntry.isSubscription() && !wifiEntry.isSuggestion(); 1169 }); 1170 } 1171 1172 /** 1173 * Updates all matching WifiEntries with the given network capabilities. If there are 1174 * currently no matching WifiEntries, then a new WifiEntry will be created for the capabilities. 1175 * @param network Network for which the NetworkCapabilities have changed. 1176 * @param capabilities NetworkCapabilities that have changed. 1177 */ 1178 @WorkerThread updateNetworkCapabilities( @onNull Network network, @NonNull NetworkCapabilities capabilities)1179 private void updateNetworkCapabilities( 1180 @NonNull Network network, @NonNull NetworkCapabilities capabilities) { 1181 if (mStandardWifiConfigCache.size() 1182 + mSuggestedConfigCache.size() + mPasspointWifiConfigCache.size() 1183 + mNetworkRequestConfigCache.size() == 0) { 1184 // We're connected but don't have any configured networks, so fetch the list of configs 1185 // again. This can happen when we fetch the configured networks after SSR, but the Wifi 1186 // thread times out waiting for driver restart and returns an empty list of networks. 1187 updateWifiConfigurations(mWifiManager.getPrivilegedConfiguredNetworks()); 1188 } 1189 // Create a WifiEntry for the current connection if there are no scan results yet. 1190 conditionallyCreateConnectedWifiEntry(Utils.getWifiInfo(capabilities)); 1191 for (WifiEntry entry : getAllWifiEntries()) { 1192 entry.onNetworkCapabilitiesChanged(network, capabilities); 1193 } 1194 } 1195 conditionallyCreateConnectedWifiEntry(@onNull WifiInfo wifiInfo)1196 private void conditionallyCreateConnectedWifiEntry(@NonNull WifiInfo wifiInfo) { 1197 conditionallyCreateConnectedStandardWifiEntry(wifiInfo); 1198 conditionallyCreateConnectedSuggestedWifiEntry(wifiInfo); 1199 conditionallyCreateConnectedPasspointWifiEntry(wifiInfo); 1200 conditionallyCreateConnectedNetworkRequestEntry(wifiInfo); 1201 } 1202 1203 /** 1204 * Updates the connection info of the current NetworkRequestEntry. A new NetworkRequestEntry is 1205 * created if there is no existing entry, or the existing entry doesn't match WifiInfo. 1206 */ 1207 @WorkerThread conditionallyCreateConnectedNetworkRequestEntry(@onNull WifiInfo wifiInfo)1208 private void conditionallyCreateConnectedNetworkRequestEntry(@NonNull WifiInfo wifiInfo) { 1209 final List<WifiConfiguration> matchingConfigs = new ArrayList<>(); 1210 1211 if (wifiInfo != null) { 1212 for (int i = 0; i < mNetworkRequestConfigCache.size(); i++) { 1213 final List<WifiConfiguration> configs = mNetworkRequestConfigCache.valueAt(i); 1214 if (!configs.isEmpty() && configs.get(0).networkId == wifiInfo.getNetworkId()) { 1215 matchingConfigs.addAll(configs); 1216 break; 1217 } 1218 } 1219 } 1220 if (matchingConfigs.isEmpty()) { 1221 return; 1222 } 1223 1224 // WifiInfo matches a request config, create a NetworkRequestEntry or update the existing. 1225 final StandardWifiEntryKey entryKey = new StandardWifiEntryKey(matchingConfigs.get(0)); 1226 if (mNetworkRequestEntry == null 1227 || !mNetworkRequestEntry.getStandardWifiEntryKey().equals(entryKey)) { 1228 mNetworkRequestEntry = new NetworkRequestEntry(mInjector, mMainHandler, 1229 entryKey, mWifiManager, false /* forSavedNetworksPage */); 1230 mNetworkRequestEntry.updateConfig(matchingConfigs); 1231 updateNetworkRequestEntryScans(mScanResultUpdater.getScanResults()); 1232 } 1233 } 1234 1235 /** 1236 * If the given network is a standard Wi-Fi network and there are no scan results for this 1237 * network yet, create and cache a new StandardWifiEntry for it. 1238 */ 1239 @WorkerThread conditionallyCreateConnectedStandardWifiEntry(@onNull WifiInfo wifiInfo)1240 private void conditionallyCreateConnectedStandardWifiEntry(@NonNull WifiInfo wifiInfo) { 1241 if (wifiInfo == null || wifiInfo.isPasspointAp() || wifiInfo.isOsuAp()) { 1242 return; 1243 } 1244 1245 final int connectedNetId = wifiInfo.getNetworkId(); 1246 for (List<WifiConfiguration> configs : mStandardWifiConfigCache.values()) { 1247 // List of configs match as long as one of them matches the connected network ID. 1248 if (configs.stream() 1249 .map(config -> config.networkId) 1250 .filter(networkId -> networkId == connectedNetId) 1251 .count() == 0) { 1252 continue; 1253 } 1254 final StandardWifiEntryKey entryKey = 1255 new StandardWifiEntryKey(configs.get(0), true /* isTargetingNewNetworks */); 1256 for (StandardWifiEntry existingEntry : mStandardWifiEntryCache) { 1257 if (entryKey.equals(existingEntry.getStandardWifiEntryKey())) { 1258 return; 1259 } 1260 } 1261 final StandardWifiEntry connectedEntry = 1262 new StandardWifiEntry(mInjector, mMainHandler, entryKey, configs, 1263 null, mWifiManager, false /* forSavedNetworksPage */); 1264 mStandardWifiEntryCache.add(connectedEntry); 1265 return; 1266 } 1267 } 1268 1269 /** 1270 * If the given network is a suggestion network and there are no scan results for this network 1271 * yet, create and cache a new StandardWifiEntry for it. 1272 */ 1273 @WorkerThread conditionallyCreateConnectedSuggestedWifiEntry(@onNull WifiInfo wifiInfo)1274 private void conditionallyCreateConnectedSuggestedWifiEntry(@NonNull WifiInfo wifiInfo) { 1275 if (wifiInfo == null || wifiInfo.isPasspointAp() || wifiInfo.isOsuAp()) { 1276 return; 1277 } 1278 final int connectedNetId = wifiInfo.getNetworkId(); 1279 for (List<WifiConfiguration> configs : mSuggestedConfigCache.values()) { 1280 if (configs.isEmpty() || configs.get(0).networkId != connectedNetId) { 1281 continue; 1282 } 1283 final StandardWifiEntryKey entryKey = 1284 new StandardWifiEntryKey(configs.get(0), true /* isTargetingNewNetworks */); 1285 for (StandardWifiEntry existingEntry : mSuggestedWifiEntryCache) { 1286 if (entryKey.equals(existingEntry.getStandardWifiEntryKey())) { 1287 return; 1288 } 1289 } 1290 final StandardWifiEntry connectedEntry = 1291 new StandardWifiEntry(mInjector, mMainHandler, entryKey, configs, 1292 null, mWifiManager, false /* forSavedNetworksPage */); 1293 mSuggestedWifiEntryCache.add(connectedEntry); 1294 return; 1295 } 1296 } 1297 1298 /** 1299 * If the given network is a Passpoint network and there are no scan results for this network 1300 * yet, create and cache a new StandardWifiEntry for it. 1301 */ 1302 @WorkerThread conditionallyCreateConnectedPasspointWifiEntry(@onNull WifiInfo wifiInfo)1303 private void conditionallyCreateConnectedPasspointWifiEntry(@NonNull WifiInfo wifiInfo) { 1304 if (wifiInfo == null || !wifiInfo.isPasspointAp()) { 1305 return; 1306 } 1307 1308 WifiConfiguration cachedWifiConfig = mPasspointWifiConfigCache.get(wifiInfo.getNetworkId()); 1309 if (cachedWifiConfig == null) { 1310 return; 1311 } 1312 final String key = uniqueIdToPasspointWifiEntryKey(cachedWifiConfig.getKey()); 1313 if (mPasspointWifiEntryCache.containsKey(key)) { 1314 // Entry already exists, skip creating a new one. 1315 return; 1316 } 1317 PasspointConfiguration passpointConfig = mPasspointConfigCache.get( 1318 uniqueIdToPasspointWifiEntryKey(cachedWifiConfig.getKey())); 1319 PasspointWifiEntry connectedEntry; 1320 if (passpointConfig != null) { 1321 connectedEntry = new PasspointWifiEntry(mInjector, mMainHandler, 1322 passpointConfig, mWifiManager, 1323 false /* forSavedNetworksPage */); 1324 } else { 1325 // Suggested PasspointWifiEntry without a corresponding PasspointConfiguration 1326 connectedEntry = new PasspointWifiEntry(mInjector, mContext, mMainHandler, 1327 cachedWifiConfig, mWifiManager, 1328 false /* forSavedNetworksPage */); 1329 } 1330 mPasspointWifiEntryCache.put(connectedEntry.getKey(), connectedEntry); 1331 } 1332 1333 /** 1334 * Posts onWifiEntryChanged callback on the main thread. 1335 */ 1336 @WorkerThread notifyOnWifiEntriesChanged()1337 private void notifyOnWifiEntriesChanged() { 1338 if (mListener != null) { 1339 mMainHandler.post(mListener::onWifiEntriesChanged); 1340 } 1341 } 1342 1343 /** 1344 * Posts onNumSavedNetworksChanged callback on the main thread. 1345 */ 1346 @WorkerThread notifyOnNumSavedNetworksChanged()1347 private void notifyOnNumSavedNetworksChanged() { 1348 if (mListener != null) { 1349 mMainHandler.post(mListener::onNumSavedNetworksChanged); 1350 } 1351 } 1352 1353 /** 1354 * Posts onNumSavedSubscriptionsChanged callback on the main thread. 1355 */ 1356 @WorkerThread notifyOnNumSavedSubscriptionsChanged()1357 private void notifyOnNumSavedSubscriptionsChanged() { 1358 if (mListener != null) { 1359 mMainHandler.post(mListener::onNumSavedSubscriptionsChanged); 1360 } 1361 } 1362 1363 /** 1364 * Listener for changes to the list of visible WifiEntries as well as the number of saved 1365 * networks and subscriptions. 1366 * 1367 * These callbacks must be run on the MainThread. 1368 */ 1369 public interface WifiPickerTrackerCallback extends BaseWifiTracker.BaseWifiTrackerCallback { 1370 /** 1371 * Called when there are changes to 1372 * {@link #getConnectedWifiEntry()} 1373 * {@link #getWifiEntries()} 1374 * {@link #getMergedCarrierEntry()} 1375 */ 1376 @MainThread onWifiEntriesChanged()1377 void onWifiEntriesChanged(); 1378 1379 /** 1380 * Called when there are changes to 1381 * {@link #getNumSavedNetworks()} 1382 */ 1383 @MainThread onNumSavedNetworksChanged()1384 void onNumSavedNetworksChanged(); 1385 1386 /** 1387 * Called when there are changes to 1388 * {@link #getNumSavedSubscriptions()} 1389 */ 1390 @MainThread onNumSavedSubscriptionsChanged()1391 void onNumSavedSubscriptionsChanged(); 1392 } 1393 } 1394