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 androidx.core.util.Preconditions.checkNotNull; 20 21 import static com.android.wifitrackerlib.OsuWifiEntry.osuProviderToOsuWifiEntryKey; 22 import static com.android.wifitrackerlib.PasspointWifiEntry.uniqueIdToPasspointWifiEntryKey; 23 import static com.android.wifitrackerlib.StandardWifiEntry.ScanResultKey; 24 import static com.android.wifitrackerlib.StandardWifiEntry.StandardWifiEntryKey; 25 import static com.android.wifitrackerlib.WifiEntry.CONNECTED_STATE_CONNECTED; 26 import static com.android.wifitrackerlib.WifiEntry.CONNECTED_STATE_CONNECTING; 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.content.Context; 34 import android.content.Intent; 35 import android.net.ConnectivityManager; 36 import android.net.LinkProperties; 37 import android.net.Network; 38 import android.net.NetworkCapabilities; 39 import android.net.NetworkInfo; 40 import android.net.NetworkScoreManager; 41 import android.net.wifi.ScanResult; 42 import android.net.wifi.WifiConfiguration; 43 import android.net.wifi.WifiInfo; 44 import android.net.wifi.WifiManager; 45 import android.net.wifi.hotspot2.OsuProvider; 46 import android.net.wifi.hotspot2.PasspointConfiguration; 47 import android.os.Handler; 48 import android.telephony.SubscriptionManager; 49 import android.text.TextUtils; 50 import android.util.ArrayMap; 51 import android.util.ArraySet; 52 import android.util.Log; 53 import android.util.Pair; 54 import android.util.SparseArray; 55 56 import androidx.annotation.AnyThread; 57 import androidx.annotation.GuardedBy; 58 import androidx.annotation.MainThread; 59 import androidx.annotation.NonNull; 60 import androidx.annotation.Nullable; 61 import androidx.annotation.VisibleForTesting; 62 import androidx.annotation.WorkerThread; 63 import androidx.lifecycle.Lifecycle; 64 65 import java.time.Clock; 66 import java.util.ArrayList; 67 import java.util.Arrays; 68 import java.util.Collections; 69 import java.util.List; 70 import java.util.Map; 71 import java.util.Set; 72 import java.util.TreeSet; 73 import java.util.function.Function; 74 import java.util.stream.Collectors; 75 76 /** 77 * Wi-Fi tracker that provides all Wi-Fi related data to the Wi-Fi picker page. 78 * 79 * These include 80 * - The connected WifiEntry 81 * - List of all visible WifiEntries 82 * - Number of saved networks 83 * - Number of saved subscriptions 84 */ 85 public class WifiPickerTracker extends BaseWifiTracker { 86 87 private static final String TAG = "WifiPickerTracker"; 88 89 private final WifiPickerTrackerCallback mListener; 90 91 // Lock object for data returned by the public API 92 private final Object mLock = new Object(); 93 // List representing return value of the getWifiEntries() API 94 @GuardedBy("mLock") private final List<WifiEntry> mWifiEntries = new ArrayList<>(); 95 // Reference to the WifiEntry representing the network that is currently connected to 96 private WifiEntry mConnectedWifiEntry; 97 // NetworkRequestEntry representing a network that was connected through the NetworkRequest API 98 private NetworkRequestEntry mNetworkRequestEntry; 99 100 private NetworkInfo mCurrentNetworkInfo; 101 // Cache containing saved WifiConfigurations mapped by StandardWifiEntry key 102 private final Map<StandardWifiEntryKey, List<WifiConfiguration>> mStandardWifiConfigCache = 103 new ArrayMap<>(); 104 // Cache containing suggested WifiConfigurations mapped by StandardWifiEntry key 105 private final Map<StandardWifiEntryKey, List<WifiConfiguration>> mSuggestedConfigCache = 106 new ArrayMap<>(); 107 // Cache containing network request WifiConfigurations mapped by StandardWifiEntry key. 108 private final ArrayMap<StandardWifiEntryKey, List<WifiConfiguration>> 109 mNetworkRequestConfigCache = new ArrayMap<>(); 110 // Cache containing visible StandardWifiEntries. Must be accessed only by the worker thread. 111 private final List<StandardWifiEntry> mStandardWifiEntryCache = new ArrayList<>(); 112 // Cache containing available suggested StandardWifiEntries. These entries may be already 113 // represented in mStandardWifiEntryCache, so filtering must be done before they are returned in 114 // getWifiEntry() and getConnectedWifiEntry(). 115 private final List<StandardWifiEntry> mSuggestedWifiEntryCache = new ArrayList<>(); 116 // Cache containing saved PasspointConfigurations mapped by PasspointWifiEntry key. 117 private final Map<String, PasspointConfiguration> mPasspointConfigCache = new ArrayMap<>(); 118 // Cache containing Passpoint WifiConfigurations mapped by network id. 119 private final SparseArray<WifiConfiguration> mPasspointWifiConfigCache = new SparseArray<>(); 120 // Cache containing visible PasspointWifiEntries. Must be accessed only by the worker thread. 121 private final Map<String, PasspointWifiEntry> mPasspointWifiEntryCache = new ArrayMap<>(); 122 // Cache containing visible OsuWifiEntries. Must be accessed only by the worker thread. 123 private final Map<String, OsuWifiEntry> mOsuWifiEntryCache = new ArrayMap<>(); 124 125 private MergedCarrierEntry mMergedCarrierEntry; 126 127 private int mNumSavedNetworks; 128 129 /** 130 * Constructor for WifiPickerTracker. 131 * @param lifecycle Lifecycle this is tied to for lifecycle callbacks. 132 * @param context Context for registering broadcast receiver and for resource strings. 133 * @param wifiManager Provides all Wi-Fi info. 134 * @param connectivityManager Provides network info. 135 * @param networkScoreManager Provides network scores for network badging. 136 * @param mainHandler Handler for processing listener callbacks. 137 * @param workerHandler Handler for processing all broadcasts and running the Scanner. 138 * @param clock Clock used for evaluating the age of scans 139 * @param maxScanAgeMillis Max age for tracked WifiEntries. 140 * @param scanIntervalMillis Interval between initiating scans. 141 * @param listener WifiTrackerCallback listening on changes to WifiPickerTracker data. 142 */ WifiPickerTracker(@onNull Lifecycle lifecycle, @NonNull Context context, @NonNull WifiManager wifiManager, @NonNull ConnectivityManager connectivityManager, @NonNull NetworkScoreManager networkScoreManager, @NonNull Handler mainHandler, @NonNull Handler workerHandler, @NonNull Clock clock, long maxScanAgeMillis, long scanIntervalMillis, @Nullable WifiPickerTrackerCallback listener)143 public WifiPickerTracker(@NonNull Lifecycle lifecycle, @NonNull Context context, 144 @NonNull WifiManager wifiManager, 145 @NonNull ConnectivityManager connectivityManager, 146 @NonNull NetworkScoreManager networkScoreManager, 147 @NonNull Handler mainHandler, 148 @NonNull Handler workerHandler, 149 @NonNull Clock clock, 150 long maxScanAgeMillis, 151 long scanIntervalMillis, 152 @Nullable WifiPickerTrackerCallback listener) { 153 this(new WifiTrackerInjector(context), lifecycle, context, wifiManager, connectivityManager, 154 networkScoreManager, mainHandler, workerHandler, clock, maxScanAgeMillis, 155 scanIntervalMillis, listener); 156 } 157 158 @VisibleForTesting WifiPickerTracker( @onNull WifiTrackerInjector injector, @NonNull Lifecycle lifecycle, @NonNull Context context, @NonNull WifiManager wifiManager, @NonNull ConnectivityManager connectivityManager, @NonNull NetworkScoreManager networkScoreManager, @NonNull Handler mainHandler, @NonNull Handler workerHandler, @NonNull Clock clock, long maxScanAgeMillis, long scanIntervalMillis, @Nullable WifiPickerTrackerCallback listener)159 WifiPickerTracker( 160 @NonNull WifiTrackerInjector injector, 161 @NonNull Lifecycle lifecycle, 162 @NonNull Context context, 163 @NonNull WifiManager wifiManager, 164 @NonNull ConnectivityManager connectivityManager, 165 @NonNull NetworkScoreManager networkScoreManager, 166 @NonNull Handler mainHandler, 167 @NonNull Handler workerHandler, 168 @NonNull Clock clock, 169 long maxScanAgeMillis, 170 long scanIntervalMillis, 171 @Nullable WifiPickerTrackerCallback listener) { 172 super(injector, lifecycle, context, wifiManager, connectivityManager, networkScoreManager, 173 mainHandler, workerHandler, clock, maxScanAgeMillis, scanIntervalMillis, listener, 174 TAG); 175 mListener = listener; 176 } 177 178 /** 179 * Returns the WifiEntry representing the current connection. 180 */ 181 @AnyThread getConnectedWifiEntry()182 public @Nullable WifiEntry getConnectedWifiEntry() { 183 return mConnectedWifiEntry; 184 } 185 186 /** 187 * Returns a list of in-range WifiEntries. 188 * 189 * The currently connected entry is omitted and may be accessed through 190 * {@link #getConnectedWifiEntry()} 191 */ 192 @AnyThread getWifiEntries()193 public @NonNull List<WifiEntry> getWifiEntries() { 194 synchronized (mLock) { 195 return new ArrayList<>(mWifiEntries); 196 } 197 } 198 199 /** 200 * Returns the MergedCarrierEntry representing the active carrier subscription. 201 */ 202 @AnyThread getMergedCarrierEntry()203 public @Nullable MergedCarrierEntry getMergedCarrierEntry() { 204 return mMergedCarrierEntry; 205 } 206 207 /** 208 * Returns the number of saved networks. 209 */ 210 @AnyThread getNumSavedNetworks()211 public int getNumSavedNetworks() { 212 return mNumSavedNetworks; 213 } 214 215 /** 216 * Returns the number of saved subscriptions. 217 */ 218 @AnyThread getNumSavedSubscriptions()219 public int getNumSavedSubscriptions() { 220 return mPasspointConfigCache.size(); 221 } 222 223 @WorkerThread 224 @Override handleOnStart()225 protected void handleOnStart() { 226 updateWifiConfigurations(mWifiManager.getPrivilegedConfiguredNetworks()); 227 updatePasspointConfigurations(mWifiManager.getPasspointConfigurations()); 228 mScanResultUpdater.update(mWifiManager.getScanResults()); 229 conditionallyUpdateScanResults(true /* lastScanSucceeded */); 230 final WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); 231 final Network currentNetwork = mWifiManager.getCurrentNetwork(); 232 mCurrentNetworkInfo = mConnectivityManager.getNetworkInfo(currentNetwork); 233 updateConnectionInfo(wifiInfo, mCurrentNetworkInfo); 234 notifyOnNumSavedNetworksChanged(); 235 notifyOnNumSavedSubscriptionsChanged(); 236 handleDefaultSubscriptionChanged(SubscriptionManager.getDefaultDataSubscriptionId()); 237 updateWifiEntries(); 238 239 // Populate mConnectedWifiEntry with information from missed callbacks. 240 handleNetworkCapabilitiesChanged( 241 mConnectivityManager.getNetworkCapabilities(currentNetwork)); 242 handleLinkPropertiesChanged(mConnectivityManager.getLinkProperties(currentNetwork)); 243 handleDefaultRouteChanged(); 244 } 245 246 @WorkerThread 247 @Override handleWifiStateChangedAction()248 protected void handleWifiStateChangedAction() { 249 conditionallyUpdateScanResults(true /* lastScanSucceeded */); 250 if (mWifiManager.getWifiState() != WifiManager.WIFI_STATE_ENABLED) { 251 updateConnectionInfo(null, null); 252 } 253 updateWifiEntries(); 254 } 255 256 @WorkerThread 257 @Override handleScanResultsAvailableAction(@onNull Intent intent)258 protected void handleScanResultsAvailableAction(@NonNull Intent intent) { 259 checkNotNull(intent, "Intent cannot be null!"); 260 conditionallyUpdateScanResults( 261 intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, true)); 262 updateWifiEntries(); 263 } 264 265 @WorkerThread 266 @Override handleConfiguredNetworksChangedAction(@onNull Intent intent)267 protected void handleConfiguredNetworksChangedAction(@NonNull Intent intent) { 268 checkNotNull(intent, "Intent cannot be null!"); 269 270 processConfiguredNetworksChanged(); 271 } 272 273 @WorkerThread 274 /** All wifi entries and saved entries needs to be updated. */ processConfiguredNetworksChanged()275 protected void processConfiguredNetworksChanged() { 276 updateWifiConfigurations(mWifiManager.getPrivilegedConfiguredNetworks()); 277 updatePasspointConfigurations(mWifiManager.getPasspointConfigurations()); 278 // Update scans since config changes may result in different entries being shown. 279 final List<ScanResult> scanResults = mScanResultUpdater.getScanResults(); 280 updateStandardWifiEntryScans(scanResults); 281 updateNetworkRequestEntryScans(scanResults); 282 updatePasspointWifiEntryScans(scanResults); 283 updateOsuWifiEntryScans(scanResults); 284 notifyOnNumSavedNetworksChanged(); 285 notifyOnNumSavedSubscriptionsChanged(); 286 updateWifiEntries(); 287 } 288 289 @WorkerThread 290 @Override handleNetworkStateChangedAction(@onNull Intent intent)291 protected void handleNetworkStateChangedAction(@NonNull Intent intent) { 292 checkNotNull(intent, "Intent cannot be null!"); 293 mCurrentNetworkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); 294 updateConnectionInfo(mWifiManager.getConnectionInfo(), mCurrentNetworkInfo); 295 updateWifiEntries(); 296 } 297 298 @WorkerThread 299 @Override handleRssiChangedAction()300 protected void handleRssiChangedAction() { 301 final WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); 302 if (mConnectedWifiEntry != null) { 303 mConnectedWifiEntry.updateConnectionInfo(wifiInfo, mCurrentNetworkInfo); 304 } 305 if (mMergedCarrierEntry != null) { 306 mMergedCarrierEntry.updateConnectionInfo(wifiInfo, mCurrentNetworkInfo); 307 } 308 } 309 310 @WorkerThread 311 @Override handleLinkPropertiesChanged(@ullable LinkProperties linkProperties)312 protected void handleLinkPropertiesChanged(@Nullable LinkProperties linkProperties) { 313 if (mConnectedWifiEntry != null 314 && mConnectedWifiEntry.getConnectedState() == CONNECTED_STATE_CONNECTED) { 315 mConnectedWifiEntry.updateLinkProperties(linkProperties); 316 } 317 if (mMergedCarrierEntry != null) { 318 mMergedCarrierEntry.updateLinkProperties(linkProperties); 319 } 320 } 321 322 @WorkerThread 323 @Override handleNetworkCapabilitiesChanged(@ullable NetworkCapabilities capabilities)324 protected void handleNetworkCapabilitiesChanged(@Nullable NetworkCapabilities capabilities) { 325 if (mConnectedWifiEntry != null 326 && mConnectedWifiEntry.getConnectedState() == CONNECTED_STATE_CONNECTED) { 327 mConnectedWifiEntry.updateNetworkCapabilities(capabilities); 328 mConnectedWifiEntry.setIsLowQuality(mIsWifiValidated && mIsCellDefaultRoute); 329 } 330 if (mMergedCarrierEntry != null) { 331 mMergedCarrierEntry.updateNetworkCapabilities(capabilities); 332 } 333 } 334 335 @WorkerThread handleDefaultRouteChanged()336 protected void handleDefaultRouteChanged() { 337 if (mConnectedWifiEntry != null) { 338 mConnectedWifiEntry.setIsDefaultNetwork(mIsWifiDefaultRoute); 339 mConnectedWifiEntry.setIsLowQuality(mIsWifiValidated && mIsCellDefaultRoute); 340 } 341 if (mMergedCarrierEntry != null) { 342 if (mMergedCarrierEntry.getConnectedState() == CONNECTED_STATE_CONNECTED) { 343 mMergedCarrierEntry.setIsDefaultNetwork(mIsWifiDefaultRoute); 344 } 345 mMergedCarrierEntry.updateIsCellDefaultRoute(mIsCellDefaultRoute); 346 } 347 } 348 349 @WorkerThread 350 @Override handleNetworkScoreCacheUpdated()351 protected void handleNetworkScoreCacheUpdated() { 352 for (StandardWifiEntry entry : mStandardWifiEntryCache) { 353 entry.onScoreCacheUpdated(); 354 } 355 for (StandardWifiEntry entry : mSuggestedWifiEntryCache) { 356 entry.onScoreCacheUpdated(); 357 } 358 for (PasspointWifiEntry entry : mPasspointWifiEntryCache.values()) { 359 entry.onScoreCacheUpdated(); 360 } 361 } 362 363 @WorkerThread 364 @Override handleDefaultSubscriptionChanged(int defaultSubId)365 protected void handleDefaultSubscriptionChanged(int defaultSubId) { 366 updateMergedCarrierEntry(defaultSubId); 367 } 368 369 /** 370 * Update the list returned by getWifiEntries() with the current states of the entry caches. 371 */ 372 @WorkerThread updateWifiEntries()373 protected void updateWifiEntries() { 374 synchronized (mLock) { 375 mConnectedWifiEntry = mStandardWifiEntryCache.stream().filter(entry -> { 376 final @WifiEntry.ConnectedState int connectedState = entry.getConnectedState(); 377 return connectedState == CONNECTED_STATE_CONNECTED 378 || connectedState == CONNECTED_STATE_CONNECTING; 379 }).findAny().orElse(null /* other */); 380 if (mConnectedWifiEntry == null) { 381 mConnectedWifiEntry = mSuggestedWifiEntryCache.stream().filter(entry -> { 382 final @WifiEntry.ConnectedState int connectedState = entry.getConnectedState(); 383 return connectedState == CONNECTED_STATE_CONNECTED 384 || connectedState == CONNECTED_STATE_CONNECTING; 385 }).findAny().orElse(null /* other */); 386 } 387 if (mConnectedWifiEntry == null) { 388 mConnectedWifiEntry = mPasspointWifiEntryCache.values().stream().filter(entry -> { 389 final @WifiEntry.ConnectedState int connectedState = entry.getConnectedState(); 390 return connectedState == CONNECTED_STATE_CONNECTED 391 || connectedState == CONNECTED_STATE_CONNECTING; 392 }).findAny().orElse(null /* other */); 393 } 394 if (mConnectedWifiEntry == null && mNetworkRequestEntry != null 395 && mNetworkRequestEntry.getConnectedState() != CONNECTED_STATE_DISCONNECTED) { 396 mConnectedWifiEntry = mNetworkRequestEntry; 397 } 398 if (mConnectedWifiEntry != null) { 399 mConnectedWifiEntry.setIsDefaultNetwork(mIsWifiDefaultRoute); 400 } 401 mWifiEntries.clear(); 402 final Set<ScanResultKey> scanResultKeysWithVisibleSuggestions = 403 mSuggestedWifiEntryCache.stream() 404 .filter(entry -> entry.isUserShareable() 405 || entry == mConnectedWifiEntry) 406 .map(entry -> entry.getStandardWifiEntryKey().getScanResultKey()) 407 .collect(Collectors.toSet()); 408 for (StandardWifiEntry entry : mStandardWifiEntryCache) { 409 if (entry == mConnectedWifiEntry) { 410 continue; 411 } 412 if (!entry.isSaved() && scanResultKeysWithVisibleSuggestions 413 .contains(entry.getStandardWifiEntryKey().getScanResultKey())) { 414 continue; 415 } 416 mWifiEntries.add(entry); 417 } 418 mWifiEntries.addAll(mSuggestedWifiEntryCache.stream().filter(entry -> 419 entry.getConnectedState() == CONNECTED_STATE_DISCONNECTED 420 && entry.isUserShareable()).collect(toList())); 421 mWifiEntries.addAll(mPasspointWifiEntryCache.values().stream().filter(entry -> 422 entry.getConnectedState() == CONNECTED_STATE_DISCONNECTED).collect(toList())); 423 mWifiEntries.addAll(mOsuWifiEntryCache.values().stream().filter(entry -> 424 entry.getConnectedState() == CONNECTED_STATE_DISCONNECTED 425 && !entry.isAlreadyProvisioned()).collect(toList())); 426 mWifiEntries.addAll(getContextualWifiEntries().stream().filter(entry -> 427 entry.getConnectedState() == CONNECTED_STATE_DISCONNECTED).collect(toList())); 428 Collections.sort(mWifiEntries); 429 if (isVerboseLoggingEnabled()) { 430 Log.v(TAG, "Connected WifiEntry: " + mConnectedWifiEntry); 431 Log.v(TAG, "Updated WifiEntries: " + Arrays.toString(mWifiEntries.toArray())); 432 } 433 } 434 notifyOnWifiEntriesChanged(); 435 } 436 437 /** 438 * Updates the MergedCarrierEntry returned by {@link #getMergedCarrierEntry()) with the current 439 * default data subscription ID, or sets it to null if not available. 440 */ 441 @WorkerThread updateMergedCarrierEntry(int subId)442 private void updateMergedCarrierEntry(int subId) { 443 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 444 if (mMergedCarrierEntry == null) { 445 return; 446 } 447 mMergedCarrierEntry = null; 448 } else { 449 if (mMergedCarrierEntry != null && subId == mMergedCarrierEntry.getSubscriptionId()) { 450 return; 451 } 452 mMergedCarrierEntry = new MergedCarrierEntry(mWorkerHandler, mWifiManager, 453 mWifiNetworkScoreCache, /* forSavedNetworksPage */ false, mContext, subId); 454 mMergedCarrierEntry.updateConnectionInfo( 455 mWifiManager.getConnectionInfo(), mCurrentNetworkInfo); 456 } 457 notifyOnWifiEntriesChanged(); 458 } 459 460 /** 461 * Get the contextual WifiEntries added according to customized conditions. 462 */ getContextualWifiEntries()463 protected List<WifiEntry> getContextualWifiEntries() { 464 return Collections.emptyList(); 465 } 466 467 /** 468 * Update the contextual wifi entry according to customized conditions. 469 */ updateContextualWifiEntryScans(@onNull List<ScanResult> scanResults)470 protected void updateContextualWifiEntryScans(@NonNull List<ScanResult> scanResults) { 471 // do nothing 472 } 473 474 /** 475 * Updates or removes scan results for the corresponding StandardWifiEntries. 476 * New entries will be created for scan results without an existing entry. 477 * Unreachable entries will be removed. 478 * 479 * @param scanResults List of valid scan results to convey as StandardWifiEntries 480 */ 481 @WorkerThread updateStandardWifiEntryScans(@onNull List<ScanResult> scanResults)482 private void updateStandardWifiEntryScans(@NonNull List<ScanResult> scanResults) { 483 checkNotNull(scanResults, "Scan Result list should not be null!"); 484 485 // Group scans by ScanResultKey key 486 final Map<ScanResultKey, List<ScanResult>> scanResultsByKey = scanResults.stream() 487 .filter(scan -> !TextUtils.isEmpty(scan.SSID)) 488 .collect(Collectors.groupingBy(ScanResultKey::new)); 489 final Set<ScanResultKey> newScanKeys = new ArraySet<>(scanResultsByKey.keySet()); 490 491 // Iterate through current entries and update each entry's scan results 492 mStandardWifiEntryCache.forEach(entry -> { 493 final ScanResultKey scanKey = entry.getStandardWifiEntryKey().getScanResultKey(); 494 newScanKeys.remove(scanKey); 495 // Update scan results if available, or set to null. 496 entry.updateScanResultInfo(scanResultsByKey.get(scanKey)); 497 }); 498 // Create new StandardWifiEntry objects for each leftover group of scan results. 499 for (ScanResultKey scanKey: newScanKeys) { 500 final StandardWifiEntryKey entryKey = 501 new StandardWifiEntryKey(scanKey, true /* isTargetingNewNetworks */); 502 final StandardWifiEntry newEntry = new StandardWifiEntry(mInjector, mContext, 503 mMainHandler, entryKey, mStandardWifiConfigCache.get(entryKey), 504 scanResultsByKey.get(scanKey), mWifiManager, mWifiNetworkScoreCache, 505 false /* forSavedNetworksPage */); 506 mStandardWifiEntryCache.add(newEntry); 507 } 508 509 // Remove any entry that is now unreachable due to no scans or unsupported 510 // security types. 511 mStandardWifiEntryCache.removeIf( 512 entry -> entry.getLevel() == WIFI_LEVEL_UNREACHABLE); 513 } 514 515 /** 516 * Updates or removes scan results for the corresponding StandardWifiEntries. 517 * New entries will be created for scan results without an existing entry. 518 * Unreachable entries will be removed. 519 * 520 * @param scanResults List of valid scan results to convey as StandardWifiEntries 521 */ 522 @WorkerThread updateSuggestedWifiEntryScans(@onNull List<ScanResult> scanResults)523 private void updateSuggestedWifiEntryScans(@NonNull List<ScanResult> scanResults) { 524 checkNotNull(scanResults, "Scan Result list should not be null!"); 525 526 // Get every ScanResultKey that is user shareable 527 final Set<StandardWifiEntryKey> userSharedEntryKeys = 528 mWifiManager.getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(scanResults) 529 .stream() 530 .map(StandardWifiEntryKey::new) 531 .collect(Collectors.toSet()); 532 533 // Group scans by ScanResultKey key 534 final Map<ScanResultKey, List<ScanResult>> scanResultsByKey = scanResults.stream() 535 .filter(scan -> !TextUtils.isEmpty(scan.SSID)) 536 .collect(Collectors.groupingBy(ScanResultKey::new)); 537 538 // Iterate through current entries and update each entry's scan results and shareability. 539 final Set<StandardWifiEntryKey> seenEntryKeys = new ArraySet<>(); 540 mSuggestedWifiEntryCache.forEach(entry -> { 541 final StandardWifiEntryKey entryKey = entry.getStandardWifiEntryKey(); 542 seenEntryKeys.add(entryKey); 543 // Update scan results if available, or set to null. 544 entry.updateScanResultInfo(scanResultsByKey.get(entryKey.getScanResultKey())); 545 entry.setUserShareable(userSharedEntryKeys.contains(entryKey)); 546 }); 547 // Create new StandardWifiEntry objects for each leftover config with scan results. 548 for (StandardWifiEntryKey entryKey : mSuggestedConfigCache.keySet()) { 549 final ScanResultKey scanKey = entryKey.getScanResultKey(); 550 if (seenEntryKeys.contains(entryKey) 551 || !scanResultsByKey.containsKey(scanKey)) { 552 continue; 553 } 554 final StandardWifiEntry newEntry = new StandardWifiEntry(mInjector, mContext, 555 mMainHandler, entryKey, mSuggestedConfigCache.get(entryKey), 556 scanResultsByKey.get(scanKey), mWifiManager, mWifiNetworkScoreCache, 557 false /* forSavedNetworksPage */); 558 newEntry.setUserShareable(userSharedEntryKeys.contains(entryKey)); 559 mSuggestedWifiEntryCache.add(newEntry); 560 } 561 562 // Remove any entry that is now unreachable due to no scans or unsupported 563 // security types. 564 mSuggestedWifiEntryCache.removeIf(entry -> entry.getLevel() == WIFI_LEVEL_UNREACHABLE); 565 } 566 567 @WorkerThread updatePasspointWifiEntryScans(@onNull List<ScanResult> scanResults)568 private void updatePasspointWifiEntryScans(@NonNull List<ScanResult> scanResults) { 569 checkNotNull(scanResults, "Scan Result list should not be null!"); 570 571 Set<String> seenKeys = new TreeSet<>(); 572 List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> matchingWifiConfigs = 573 mWifiManager.getAllMatchingWifiConfigs(scanResults); 574 575 for (Pair<WifiConfiguration, Map<Integer, List<ScanResult>>> pair : matchingWifiConfigs) { 576 final WifiConfiguration wifiConfig = pair.first; 577 final List<ScanResult> homeScans = 578 pair.second.get(WifiManager.PASSPOINT_HOME_NETWORK); 579 final List<ScanResult> roamingScans = 580 pair.second.get(WifiManager.PASSPOINT_ROAMING_NETWORK); 581 final String key = uniqueIdToPasspointWifiEntryKey(wifiConfig.getKey()); 582 seenKeys.add(key); 583 584 // Create PasspointWifiEntry if one doesn't exist for the seen key yet. 585 if (!mPasspointWifiEntryCache.containsKey(key)) { 586 if (wifiConfig.fromWifiNetworkSuggestion) { 587 mPasspointWifiEntryCache.put(key, new PasspointWifiEntry(mInjector, mContext, 588 mMainHandler, wifiConfig, mWifiManager, 589 mWifiNetworkScoreCache, false /* forSavedNetworksPage */)); 590 } else if (mPasspointConfigCache.containsKey(key)) { 591 mPasspointWifiEntryCache.put(key, new PasspointWifiEntry(mInjector, mContext, 592 mMainHandler, mPasspointConfigCache.get(key), mWifiManager, 593 mWifiNetworkScoreCache, false /* forSavedNetworksPage */)); 594 } else { 595 // Failed to find PasspointConfig for a provisioned Passpoint network 596 continue; 597 } 598 } 599 mPasspointWifiEntryCache.get(key).updateScanResultInfo(wifiConfig, 600 homeScans, roamingScans); 601 } 602 603 // Remove entries that are now unreachable 604 mPasspointWifiEntryCache.entrySet() 605 .removeIf(entry -> entry.getValue().getLevel() == WIFI_LEVEL_UNREACHABLE 606 || (!seenKeys.contains(entry.getKey())) 607 && entry.getValue().getConnectedState() == CONNECTED_STATE_DISCONNECTED); 608 } 609 610 @WorkerThread updateOsuWifiEntryScans(@onNull List<ScanResult> scanResults)611 private void updateOsuWifiEntryScans(@NonNull List<ScanResult> scanResults) { 612 checkNotNull(scanResults, "Scan Result list should not be null!"); 613 614 Map<OsuProvider, List<ScanResult>> osuProviderToScans = 615 mWifiManager.getMatchingOsuProviders(scanResults); 616 Map<OsuProvider, PasspointConfiguration> osuProviderToPasspointConfig = 617 mWifiManager.getMatchingPasspointConfigsForOsuProviders( 618 osuProviderToScans.keySet()); 619 // Update each OsuWifiEntry with new scans (or empty scans). 620 for (OsuWifiEntry entry : mOsuWifiEntryCache.values()) { 621 entry.updateScanResultInfo(osuProviderToScans.remove(entry.getOsuProvider())); 622 } 623 624 // Create a new entry for each OsuProvider not already matched to an OsuWifiEntry 625 for (OsuProvider provider : osuProviderToScans.keySet()) { 626 OsuWifiEntry newEntry = new OsuWifiEntry(mContext, mMainHandler, provider, mWifiManager, 627 mWifiNetworkScoreCache, false /* forSavedNetworksPage */); 628 newEntry.updateScanResultInfo(osuProviderToScans.get(provider)); 629 mOsuWifiEntryCache.put(osuProviderToOsuWifiEntryKey(provider), newEntry); 630 } 631 632 // Pass a reference of each OsuWifiEntry to any matching provisioned PasspointWifiEntries 633 // for expiration handling. 634 mOsuWifiEntryCache.values().forEach(osuEntry -> { 635 PasspointConfiguration provisionedConfig = 636 osuProviderToPasspointConfig.get(osuEntry.getOsuProvider()); 637 if (provisionedConfig == null) { 638 osuEntry.setAlreadyProvisioned(false); 639 return; 640 } 641 osuEntry.setAlreadyProvisioned(true); 642 PasspointWifiEntry provisionedEntry = mPasspointWifiEntryCache.get( 643 uniqueIdToPasspointWifiEntryKey(provisionedConfig.getUniqueId())); 644 if (provisionedEntry == null) { 645 return; 646 } 647 provisionedEntry.setOsuWifiEntry(osuEntry); 648 }); 649 650 // Remove entries that are now unreachable 651 mOsuWifiEntryCache.entrySet() 652 .removeIf(entry -> entry.getValue().getLevel() == WIFI_LEVEL_UNREACHABLE); 653 } 654 655 @WorkerThread updateNetworkRequestEntryScans(@onNull List<ScanResult> scanResults)656 private void updateNetworkRequestEntryScans(@NonNull List<ScanResult> scanResults) { 657 checkNotNull(scanResults, "Scan Result list should not be null!"); 658 if (mNetworkRequestEntry == null) { 659 return; 660 } 661 662 final ScanResultKey scanKey = 663 mNetworkRequestEntry.getStandardWifiEntryKey().getScanResultKey(); 664 List<ScanResult> matchedScans = scanResults.stream() 665 .filter(scan -> scanKey.equals(new ScanResultKey(scan))) 666 .collect(toList()); 667 mNetworkRequestEntry.updateScanResultInfo(matchedScans); 668 } 669 670 /** 671 * Conditionally updates the WifiEntry scan results based on the current wifi state and 672 * whether the last scan succeeded or not. 673 */ 674 @WorkerThread conditionallyUpdateScanResults(boolean lastScanSucceeded)675 private void conditionallyUpdateScanResults(boolean lastScanSucceeded) { 676 if (mWifiManager.getWifiState() == WifiManager.WIFI_STATE_DISABLED) { 677 updateStandardWifiEntryScans(Collections.emptyList()); 678 updateSuggestedWifiEntryScans(Collections.emptyList()); 679 updatePasspointWifiEntryScans(Collections.emptyList()); 680 updateOsuWifiEntryScans(Collections.emptyList()); 681 updateNetworkRequestEntryScans(Collections.emptyList()); 682 updateContextualWifiEntryScans(Collections.emptyList()); 683 return; 684 } 685 686 long scanAgeWindow = mMaxScanAgeMillis; 687 if (lastScanSucceeded) { 688 // Scan succeeded, cache new scans 689 mScanResultUpdater.update(mWifiManager.getScanResults()); 690 } else { 691 // Scan failed, increase scan age window to prevent WifiEntry list from 692 // clearing prematurely. 693 scanAgeWindow += mScanIntervalMillis; 694 } 695 696 List<ScanResult> scanResults = mScanResultUpdater.getScanResults(scanAgeWindow); 697 updateStandardWifiEntryScans(scanResults); 698 updateSuggestedWifiEntryScans(scanResults); 699 updatePasspointWifiEntryScans(scanResults); 700 updateOsuWifiEntryScans(scanResults); 701 updateNetworkRequestEntryScans(scanResults); 702 updateContextualWifiEntryScans(scanResults); 703 } 704 705 /** 706 * Updates the WifiConfiguration caches for saved/ephemeral/suggested networks and updates the 707 * corresponding WifiEntries with the new configs. 708 * 709 * @param configs List of all saved/ephemeral/suggested WifiConfigurations 710 */ 711 @WorkerThread updateWifiConfigurations(@onNull List<WifiConfiguration> configs)712 private void updateWifiConfigurations(@NonNull List<WifiConfiguration> configs) { 713 checkNotNull(configs, "Config list should not be null!"); 714 mStandardWifiConfigCache.clear(); 715 mSuggestedConfigCache.clear(); 716 mNetworkRequestConfigCache.clear(); 717 final List<WifiConfiguration> networkRequestConfigs = new ArrayList<>(); 718 for (WifiConfiguration config : configs) { 719 if (config.carrierMerged) { 720 continue; 721 } 722 StandardWifiEntryKey standardWifiEntryKey = 723 new StandardWifiEntryKey(config, true /* isTargetingNewNetworks */); 724 if (config.isPasspoint()) { 725 mPasspointWifiConfigCache.put(config.networkId, config); 726 } else if (config.fromWifiNetworkSuggestion) { 727 if (!mSuggestedConfigCache.containsKey(standardWifiEntryKey)) { 728 mSuggestedConfigCache.put(standardWifiEntryKey, new ArrayList<>()); 729 } 730 mSuggestedConfigCache.get(standardWifiEntryKey).add(config); 731 } else if (config.fromWifiNetworkSpecifier) { 732 if (!mNetworkRequestConfigCache.containsKey(standardWifiEntryKey)) { 733 mNetworkRequestConfigCache.put(standardWifiEntryKey, new ArrayList<>()); 734 } 735 mNetworkRequestConfigCache.get(standardWifiEntryKey).add(config); 736 } else { 737 if (!mStandardWifiConfigCache.containsKey(standardWifiEntryKey)) { 738 mStandardWifiConfigCache.put(standardWifiEntryKey, new ArrayList<>()); 739 } 740 mStandardWifiConfigCache.get(standardWifiEntryKey).add(config); 741 } 742 } 743 mNumSavedNetworks = (int) mStandardWifiConfigCache.values().stream() 744 .flatMap(List::stream) 745 .filter(config -> !config.isEphemeral()) 746 .map(config -> config.networkId) 747 .distinct() 748 .count(); 749 750 // Iterate through current entries and update each entry's config 751 mStandardWifiEntryCache.forEach(entry -> 752 entry.updateConfig(mStandardWifiConfigCache.get(entry.getStandardWifiEntryKey()))); 753 754 // Iterate through current suggestion entries and update each entry's config 755 mSuggestedWifiEntryCache.removeIf(entry -> { 756 entry.updateConfig(mSuggestedConfigCache.get(entry.getStandardWifiEntryKey())); 757 // Remove if the suggestion does not have a config anymore. 758 return !entry.isSuggestion(); 759 }); 760 761 if (mNetworkRequestEntry != null) { 762 mNetworkRequestEntry.updateConfig( 763 mNetworkRequestConfigCache.get(mNetworkRequestEntry.getStandardWifiEntryKey())); 764 } 765 } 766 767 @WorkerThread updatePasspointConfigurations(@onNull List<PasspointConfiguration> configs)768 private void updatePasspointConfigurations(@NonNull List<PasspointConfiguration> configs) { 769 checkNotNull(configs, "Config list should not be null!"); 770 mPasspointConfigCache.clear(); 771 mPasspointConfigCache.putAll(configs.stream().collect( 772 toMap(config -> uniqueIdToPasspointWifiEntryKey( 773 config.getUniqueId()), Function.identity()))); 774 775 // Iterate through current entries and update each entry's config or remove if no config 776 // matches the entry anymore. 777 mPasspointWifiEntryCache.entrySet().removeIf((entry) -> { 778 final PasspointWifiEntry wifiEntry = entry.getValue(); 779 final String key = wifiEntry.getKey(); 780 wifiEntry.updatePasspointConfig(mPasspointConfigCache.get(key)); 781 return !wifiEntry.isSubscription() && !wifiEntry.isSuggestion(); 782 }); 783 } 784 785 /** 786 * Updates all WifiEntries with the current connection info. 787 * @param wifiInfo WifiInfo of the current connection 788 * @param networkInfo NetworkInfo of the current connection 789 */ 790 @WorkerThread updateConnectionInfo(@ullable WifiInfo wifiInfo, @Nullable NetworkInfo networkInfo)791 private void updateConnectionInfo(@Nullable WifiInfo wifiInfo, 792 @Nullable NetworkInfo networkInfo) { 793 for (WifiEntry entry : mStandardWifiEntryCache) { 794 entry.updateConnectionInfo(wifiInfo, networkInfo); 795 } 796 for (WifiEntry entry : mSuggestedWifiEntryCache) { 797 entry.updateConnectionInfo(wifiInfo, networkInfo); 798 } 799 for (WifiEntry entry : mPasspointWifiEntryCache.values()) { 800 entry.updateConnectionInfo(wifiInfo, networkInfo); 801 } 802 for (WifiEntry entry : mOsuWifiEntryCache.values()) { 803 entry.updateConnectionInfo(wifiInfo, networkInfo); 804 } 805 if (mNetworkRequestEntry != null) { 806 mNetworkRequestEntry.updateConnectionInfo(wifiInfo, networkInfo); 807 } 808 updateNetworkRequestEntryConnectionInfo(wifiInfo, networkInfo); 809 if (mMergedCarrierEntry != null) { 810 mMergedCarrierEntry.updateConnectionInfo(wifiInfo, networkInfo); 811 } 812 // Create a StandardWifiEntry for the current connection if there are no scan results yet. 813 conditionallyCreateConnectedStandardWifiEntry(wifiInfo, networkInfo); 814 conditionallyCreateConnectedSuggestedWifiEntry(wifiInfo, networkInfo); 815 conditionallyCreateConnectedPasspointWifiEntry(wifiInfo, networkInfo); 816 } 817 818 /** 819 * Updates the connection info of the current NetworkRequestEntry. A new NetworkRequestEntry is 820 * created if there is no existing entry, or the existing entry doesn't match WifiInfo. 821 */ 822 @WorkerThread updateNetworkRequestEntryConnectionInfo( @ullable WifiInfo wifiInfo, @Nullable NetworkInfo networkInfo)823 private void updateNetworkRequestEntryConnectionInfo( 824 @Nullable WifiInfo wifiInfo, @Nullable NetworkInfo networkInfo) { 825 final List<WifiConfiguration> matchingConfigs = new ArrayList<>(); 826 827 if (wifiInfo != null) { 828 for (int i = 0; i < mNetworkRequestConfigCache.size(); i++) { 829 final List<WifiConfiguration> configs = mNetworkRequestConfigCache.valueAt(i); 830 if (!configs.isEmpty() && configs.get(0).networkId == wifiInfo.getNetworkId()) { 831 matchingConfigs.addAll(configs); 832 break; 833 } 834 } 835 } 836 837 // WifiInfo isn't for any request configs, remove the NetworkRequestEntry 838 if (matchingConfigs.isEmpty()) { 839 mNetworkRequestEntry = null; 840 return; 841 } 842 843 // WifiInfo matches a request config, create a NetworkRequestEntry or update the existing. 844 final StandardWifiEntryKey entryKey = new StandardWifiEntryKey(matchingConfigs.get(0)); 845 if (mNetworkRequestEntry == null 846 || !mNetworkRequestEntry.getStandardWifiEntryKey().equals(entryKey)) { 847 mNetworkRequestEntry = new NetworkRequestEntry(mInjector, mContext, mMainHandler, 848 entryKey, mWifiManager, mWifiNetworkScoreCache, 849 false /* forSavedNetworksPage */); 850 mNetworkRequestEntry.updateConfig(matchingConfigs); 851 updateNetworkRequestEntryScans(mScanResultUpdater.getScanResults()); 852 } 853 mNetworkRequestEntry.updateConnectionInfo(wifiInfo, networkInfo); 854 } 855 856 /** 857 * Creates and caches a StandardWifiEntry representing the current connection using the current 858 * WifiInfo and NetworkInfo if there are no scans results available for the network yet. 859 * @param wifiInfo WifiInfo of the current connection 860 * @param networkInfo NetworkInfo of the current connection 861 */ 862 @WorkerThread conditionallyCreateConnectedStandardWifiEntry(@ullable WifiInfo wifiInfo, @Nullable NetworkInfo networkInfo)863 private void conditionallyCreateConnectedStandardWifiEntry(@Nullable WifiInfo wifiInfo, 864 @Nullable NetworkInfo networkInfo) { 865 if (wifiInfo == null || wifiInfo.isPasspointAp() || wifiInfo.isOsuAp()) { 866 return; 867 } 868 869 final int connectedNetId = wifiInfo.getNetworkId(); 870 for (List<WifiConfiguration> configs : mStandardWifiConfigCache.values()) { 871 // List of configs match as long as one of them matches the connected network ID. 872 if (configs.stream() 873 .map(config -> config.networkId) 874 .filter(networkId -> networkId == connectedNetId) 875 .count() == 0) { 876 continue; 877 } 878 final StandardWifiEntryKey entryKey = 879 new StandardWifiEntryKey(configs.get(0), true /* isTargetingNewNetworks */); 880 for (StandardWifiEntry existingEntry : mStandardWifiEntryCache) { 881 if (entryKey.equals(existingEntry.getStandardWifiEntryKey())) { 882 return; 883 } 884 } 885 final StandardWifiEntry connectedEntry = 886 new StandardWifiEntry(mInjector, mContext, mMainHandler, entryKey, configs, 887 null, mWifiManager, mWifiNetworkScoreCache, 888 false /* forSavedNetworksPage */); 889 connectedEntry.updateConnectionInfo(wifiInfo, networkInfo); 890 mStandardWifiEntryCache.add(connectedEntry); 891 return; 892 } 893 } 894 895 /** 896 * Creates and caches a suggested StandardWifiEntry representing the current connection using 897 * the current WifiInfo and NetworkInfo if there are no scans results available for the network 898 * yet. 899 * @param wifiInfo WifiInfo of the current connection 900 * @param networkInfo NetworkInfo of the current connection 901 */ 902 @WorkerThread conditionallyCreateConnectedSuggestedWifiEntry(@ullable WifiInfo wifiInfo, @Nullable NetworkInfo networkInfo)903 private void conditionallyCreateConnectedSuggestedWifiEntry(@Nullable WifiInfo wifiInfo, 904 @Nullable NetworkInfo networkInfo) { 905 if (wifiInfo == null || wifiInfo.isPasspointAp() || wifiInfo.isOsuAp()) { 906 return; 907 } 908 909 final int connectedNetId = wifiInfo.getNetworkId(); 910 for (List<WifiConfiguration> configs : mSuggestedConfigCache.values()) { 911 if (configs.isEmpty() || configs.get(0).networkId != connectedNetId) { 912 continue; 913 } 914 final StandardWifiEntryKey entryKey = 915 new StandardWifiEntryKey(configs.get(0), true /* isTargetingNewNetworks */); 916 for (StandardWifiEntry existingEntry : mSuggestedWifiEntryCache) { 917 if (entryKey.equals(existingEntry.getStandardWifiEntryKey())) { 918 return; 919 } 920 } 921 final StandardWifiEntry connectedEntry = 922 new StandardWifiEntry(mInjector, mContext, mMainHandler, entryKey, configs, 923 null, mWifiManager, mWifiNetworkScoreCache, 924 false /* forSavedNetworksPage */); 925 connectedEntry.updateConnectionInfo(wifiInfo, networkInfo); 926 mSuggestedWifiEntryCache.add(connectedEntry); 927 return; 928 } 929 } 930 931 932 /** 933 * Creates and caches a PasspointWifiEntry representing the current connection using the current 934 * WifiInfo and NetworkInfo if there are no scans results available for the network yet. 935 * @param wifiInfo WifiInfo of the current connection 936 * @param networkInfo NetworkInfo of the current connection 937 */ 938 @WorkerThread conditionallyCreateConnectedPasspointWifiEntry(@ullable WifiInfo wifiInfo, @Nullable NetworkInfo networkInfo)939 private void conditionallyCreateConnectedPasspointWifiEntry(@Nullable WifiInfo wifiInfo, 940 @Nullable NetworkInfo networkInfo) { 941 if (wifiInfo == null || !wifiInfo.isPasspointAp()) { 942 return; 943 } 944 945 WifiConfiguration cachedWifiConfig = mPasspointWifiConfigCache.get(wifiInfo.getNetworkId()); 946 if (cachedWifiConfig == null) { 947 return; 948 } 949 final String key = uniqueIdToPasspointWifiEntryKey(cachedWifiConfig.getKey()); 950 if (mPasspointWifiEntryCache.containsKey(key)) { 951 // Entry already exists, skip creating a new one. 952 return; 953 } 954 PasspointConfiguration passpointConfig = mPasspointConfigCache.get( 955 uniqueIdToPasspointWifiEntryKey(cachedWifiConfig.getKey())); 956 PasspointWifiEntry connectedEntry; 957 if (passpointConfig != null) { 958 connectedEntry = new PasspointWifiEntry(mInjector, mContext, mMainHandler, 959 passpointConfig, mWifiManager, mWifiNetworkScoreCache, 960 false /* forSavedNetworksPage */); 961 } else { 962 // Suggested PasspointWifiEntry without a corresponding PasspointConfiguration 963 connectedEntry = new PasspointWifiEntry(mInjector, mContext, mMainHandler, 964 cachedWifiConfig, mWifiManager, mWifiNetworkScoreCache, 965 false /* forSavedNetworksPage */); 966 } 967 connectedEntry.updateConnectionInfo(wifiInfo, networkInfo); 968 mPasspointWifiEntryCache.put(connectedEntry.getKey(), connectedEntry); 969 } 970 971 /** 972 * Posts onWifiEntryChanged callback on the main thread. 973 */ 974 @WorkerThread notifyOnWifiEntriesChanged()975 private void notifyOnWifiEntriesChanged() { 976 if (mListener != null) { 977 mMainHandler.post(mListener::onWifiEntriesChanged); 978 } 979 } 980 981 /** 982 * Posts onNumSavedNetworksChanged callback on the main thread. 983 */ 984 @WorkerThread notifyOnNumSavedNetworksChanged()985 private void notifyOnNumSavedNetworksChanged() { 986 if (mListener != null) { 987 mMainHandler.post(mListener::onNumSavedNetworksChanged); 988 } 989 } 990 991 /** 992 * Posts onNumSavedSubscriptionsChanged callback on the main thread. 993 */ 994 @WorkerThread notifyOnNumSavedSubscriptionsChanged()995 private void notifyOnNumSavedSubscriptionsChanged() { 996 if (mListener != null) { 997 mMainHandler.post(mListener::onNumSavedSubscriptionsChanged); 998 } 999 } 1000 1001 /** 1002 * Listener for changes to the list of visible WifiEntries as well as the number of saved 1003 * networks and subscriptions. 1004 * 1005 * These callbacks must be run on the MainThread. 1006 */ 1007 public interface WifiPickerTrackerCallback extends BaseWifiTracker.BaseWifiTrackerCallback { 1008 /** 1009 * Called when there are changes to 1010 * {@link #getConnectedWifiEntry()} 1011 * {@link #getWifiEntries()} 1012 * {@link #getMergedCarrierEntry()} 1013 */ 1014 @MainThread onWifiEntriesChanged()1015 void onWifiEntriesChanged(); 1016 1017 /** 1018 * Called when there are changes to 1019 * {@link #getNumSavedNetworks()} 1020 */ 1021 @MainThread onNumSavedNetworksChanged()1022 void onNumSavedNetworksChanged(); 1023 1024 /** 1025 * Called when there are changes to 1026 * {@link #getNumSavedSubscriptions()} 1027 */ 1028 @MainThread onNumSavedSubscriptionsChanged()1029 void onNumSavedSubscriptionsChanged(); 1030 } 1031 } 1032