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