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