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