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