| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.wifitrackerlib; |
| |
| import static androidx.core.util.Preconditions.checkNotNull; |
| |
| import static com.android.wifitrackerlib.OsuWifiEntry.osuProviderToOsuWifiEntryKey; |
| import static com.android.wifitrackerlib.PasspointWifiEntry.uniqueIdToPasspointWifiEntryKey; |
| import static com.android.wifitrackerlib.StandardWifiEntry.wifiConfigToStandardWifiEntryKey; |
| import static com.android.wifitrackerlib.Utils.mapScanResultsToKey; |
| import static com.android.wifitrackerlib.WifiEntry.CONNECTED_STATE_CONNECTED; |
| import static com.android.wifitrackerlib.WifiEntry.CONNECTED_STATE_CONNECTING; |
| import static com.android.wifitrackerlib.WifiEntry.CONNECTED_STATE_DISCONNECTED; |
| import static com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE; |
| |
| import static java.util.stream.Collectors.toList; |
| import static java.util.stream.Collectors.toMap; |
| |
| import android.content.Context; |
| import android.content.Intent; |
| import android.net.ConnectivityManager; |
| import android.net.LinkProperties; |
| import android.net.NetworkCapabilities; |
| import android.net.NetworkInfo; |
| import android.net.NetworkScoreManager; |
| import android.net.wifi.ScanResult; |
| import android.net.wifi.WifiConfiguration; |
| import android.net.wifi.WifiInfo; |
| import android.net.wifi.WifiManager; |
| import android.net.wifi.hotspot2.OsuProvider; |
| import android.net.wifi.hotspot2.PasspointConfiguration; |
| import android.os.Handler; |
| import android.text.TextUtils; |
| import android.util.Log; |
| import android.util.Pair; |
| |
| import androidx.annotation.AnyThread; |
| import androidx.annotation.GuardedBy; |
| import androidx.annotation.MainThread; |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.annotation.WorkerThread; |
| import androidx.lifecycle.Lifecycle; |
| |
| import java.time.Clock; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.function.Function; |
| import java.util.stream.Collectors; |
| import java.util.stream.Stream; |
| |
| /** |
| * Wi-Fi tracker that provides all Wi-Fi related data to the Wi-Fi picker page. |
| * |
| * These include |
| * - The connected WifiEntry |
| * - List of all visible WifiEntries |
| * - Number of saved networks |
| * - Number of saved subscriptions |
| */ |
| public class WifiPickerTracker extends BaseWifiTracker { |
| |
| private static final String TAG = "WifiPickerTracker"; |
| |
| private final WifiPickerTrackerCallback mListener; |
| |
| // Lock object for data returned by the public API |
| private final Object mLock = new Object(); |
| // List representing return value of the getWifiEntries() API |
| @GuardedBy("mLock") private final List<WifiEntry> mWifiEntries = new ArrayList<>(); |
| // Reference to the WifiEntry representing the network that is currently connected to |
| private WifiEntry mConnectedWifiEntry; |
| |
| // Cache containing saved WifiConfigurations mapped by StandardWifiEntry key |
| private final Map<String, WifiConfiguration> mWifiConfigCache = new HashMap<>(); |
| |
| private final Map<String, WifiConfiguration> mSuggestedConfigCache = new HashMap<>(); |
| // Cache containing visible StandardWifiEntries. Must be accessed only by the worker thread. |
| private final Map<String, StandardWifiEntry> mStandardWifiEntryCache = new HashMap<>(); |
| // Cache containing available suggested StandardWifiEntries. These entries may be already |
| // represented in mStandardWifiEntryCache, so filtering must be done before they are returned in |
| // getWifiEntry() and getConnectedWifiEntry(). |
| private final Map<String, StandardWifiEntry> mSuggestedWifiEntryCache = new HashMap<>(); |
| // Cache containing saved PasspointConfigurations mapped by PasspointWifiEntry key. |
| private final Map<String, PasspointConfiguration> mPasspointConfigCache = new HashMap<>(); |
| // Cache containing visible PasspointWifiEntries. Must be accessed only by the worker thread. |
| private final Map<String, PasspointWifiEntry> mPasspointWifiEntryCache = new HashMap<>(); |
| // Cache containing visible OsuWifiEntries. Must be accessed only by the worker thread. |
| private final Map<String, OsuWifiEntry> mOsuWifiEntryCache = new HashMap<>(); |
| |
| private int mNumSavedNetworks; |
| |
| /** |
| * Constructor for WifiPickerTracker. |
| * |
| * @param lifecycle Lifecycle this is tied to for lifecycle callbacks. |
| * @param context Context for registering broadcast receiver and for resource strings. |
| * @param wifiManager Provides all Wi-Fi info. |
| * @param connectivityManager Provides network info. |
| * @param networkScoreManager Provides network scores for network badging. |
| * @param mainHandler Handler for processing listener callbacks. |
| * @param workerHandler Handler for processing all broadcasts and running the Scanner. |
| * @param clock Clock used for evaluating the age of scans |
| * @param maxScanAgeMillis Max age for tracked WifiEntries. |
| * @param scanIntervalMillis Interval between initiating scans. |
| * @param listener WifiTrackerCallback listening on changes to WifiPickerTracker data. |
| */ |
| public WifiPickerTracker(@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) { |
| super(lifecycle, context, wifiManager, connectivityManager, networkScoreManager, |
| mainHandler, workerHandler, clock, maxScanAgeMillis, scanIntervalMillis, listener, |
| TAG); |
| mListener = listener; |
| } |
| |
| /** |
| * Returns the WifiEntry representing the current connection. |
| */ |
| @AnyThread |
| public @Nullable WifiEntry getConnectedWifiEntry() { |
| return mConnectedWifiEntry; |
| } |
| |
| /** |
| * Returns a list of in-range WifiEntries. |
| * |
| * The currently connected entry is omitted and may be accessed through |
| * {@link #getConnectedWifiEntry()} |
| */ |
| @AnyThread |
| public @NonNull List<WifiEntry> getWifiEntries() { |
| synchronized (mLock) { |
| return new ArrayList<>(mWifiEntries); |
| } |
| } |
| |
| /** |
| * Returns the number of saved networks. |
| */ |
| @AnyThread |
| public int getNumSavedNetworks() { |
| return mNumSavedNetworks; |
| } |
| |
| /** |
| * Returns the number of saved subscriptions. |
| */ |
| @AnyThread |
| public int getNumSavedSubscriptions() { |
| return mPasspointConfigCache.size(); |
| } |
| |
| @WorkerThread |
| @Override |
| protected void handleOnStart() { |
| updateWifiConfigurations(mWifiManager.getPrivilegedConfiguredNetworks()); |
| updatePasspointConfigurations(mWifiManager.getPasspointConfigurations()); |
| mScanResultUpdater.update(mWifiManager.getScanResults()); |
| conditionallyUpdateScanResults(true /* lastScanSucceeded */); |
| final WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); |
| final NetworkInfo networkInfo = mConnectivityManager.getActiveNetworkInfo(); |
| updateConnectionInfo(wifiInfo, networkInfo); |
| handleLinkPropertiesChanged(mConnectivityManager.getLinkProperties( |
| mWifiManager.getCurrentNetwork())); |
| notifyOnNumSavedNetworksChanged(); |
| notifyOnNumSavedSubscriptionsChanged(); |
| updateWifiEntries(); |
| } |
| |
| @WorkerThread |
| @Override |
| protected void handleWifiStateChangedAction() { |
| conditionallyUpdateScanResults(true /* lastScanSucceeded */); |
| updateWifiEntries(); |
| } |
| |
| @WorkerThread |
| @Override |
| protected void handleScanResultsAvailableAction(@NonNull Intent intent) { |
| checkNotNull(intent, "Intent cannot be null!"); |
| conditionallyUpdateScanResults( |
| intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, true)); |
| updateWifiEntries(); |
| } |
| |
| @WorkerThread |
| @Override |
| protected void handleConfiguredNetworksChangedAction(@NonNull Intent intent) { |
| // TODO(b/70983952): Handle Passpoint configurations here |
| checkNotNull(intent, "Intent cannot be null!"); |
| |
| final WifiConfiguration config = |
| (WifiConfiguration) intent.getExtra(WifiManager.EXTRA_WIFI_CONFIGURATION); |
| if (config != null && !config.isPasspoint()) { |
| updateWifiConfiguration( |
| config, (Integer) intent.getExtra(WifiManager.EXTRA_CHANGE_REASON)); |
| } else { |
| updateWifiConfigurations(mWifiManager.getPrivilegedConfiguredNetworks()); |
| } |
| updatePasspointConfigurations(mWifiManager.getPasspointConfigurations()); |
| // Update scans since config changes may result in different entries being shown. |
| final List<ScanResult> scanResults = mScanResultUpdater.getScanResults(); |
| updateStandardWifiEntryScans(scanResults); |
| updatePasspointWifiEntryScans(scanResults); |
| updateOsuWifiEntryScans(scanResults); |
| notifyOnNumSavedNetworksChanged(); |
| notifyOnNumSavedSubscriptionsChanged(); |
| updateWifiEntries(); |
| } |
| |
| @WorkerThread |
| @Override |
| protected void handleNetworkStateChangedAction(@NonNull Intent intent) { |
| checkNotNull(intent, "Intent cannot be null!"); |
| final WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); |
| final NetworkInfo networkInfo = |
| (NetworkInfo) intent.getExtra(WifiManager.EXTRA_NETWORK_INFO); |
| updateConnectionInfo(wifiInfo, networkInfo); |
| updateWifiEntries(); |
| } |
| |
| @WorkerThread |
| @Override |
| protected void handleRssiChangedAction() { |
| if (mConnectedWifiEntry != null) { |
| final WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); |
| final NetworkInfo networkInfo = mConnectivityManager.getActiveNetworkInfo(); |
| mConnectedWifiEntry.updateConnectionInfo(wifiInfo, networkInfo); |
| } |
| } |
| |
| @WorkerThread |
| @Override |
| protected void handleLinkPropertiesChanged(@Nullable LinkProperties linkProperties) { |
| if (mConnectedWifiEntry != null |
| && mConnectedWifiEntry.getConnectedState() == CONNECTED_STATE_CONNECTED) { |
| mConnectedWifiEntry.updateLinkProperties(linkProperties); |
| } |
| } |
| |
| @WorkerThread |
| @Override |
| protected void handleNetworkCapabilitiesChanged(@Nullable NetworkCapabilities capabilities) { |
| if (mConnectedWifiEntry != null |
| && mConnectedWifiEntry.getConnectedState() == CONNECTED_STATE_CONNECTED) { |
| mConnectedWifiEntry.updateNetworkCapabilities(capabilities); |
| } |
| } |
| |
| @WorkerThread |
| @Override |
| protected void handleNetworkScoreCacheUpdated() { |
| for (StandardWifiEntry entry : mStandardWifiEntryCache.values()) { |
| entry.onScoreCacheUpdated(); |
| } |
| for (StandardWifiEntry entry : mSuggestedWifiEntryCache.values()) { |
| entry.onScoreCacheUpdated(); |
| } |
| for (PasspointWifiEntry entry : mPasspointWifiEntryCache.values()) { |
| entry.onScoreCacheUpdated(); |
| } |
| } |
| |
| /** |
| * Update the list returned by getWifiEntries() with the current states of the entry caches. |
| */ |
| @WorkerThread |
| private void updateWifiEntries() { |
| synchronized (mLock) { |
| mConnectedWifiEntry = mStandardWifiEntryCache.values().stream().filter(entry -> { |
| final @WifiEntry.ConnectedState int connectedState = entry.getConnectedState(); |
| return connectedState == CONNECTED_STATE_CONNECTED |
| || connectedState == CONNECTED_STATE_CONNECTING; |
| }).findAny().orElse(null /* other */); |
| if (mConnectedWifiEntry == null) { |
| mConnectedWifiEntry = mSuggestedWifiEntryCache.values().stream().filter(entry -> { |
| final @WifiEntry.ConnectedState int connectedState = entry.getConnectedState(); |
| return connectedState == CONNECTED_STATE_CONNECTED |
| || connectedState == CONNECTED_STATE_CONNECTING; |
| }).findAny().orElse(null /* other */); |
| } |
| if (mConnectedWifiEntry == null) { |
| mConnectedWifiEntry = mPasspointWifiEntryCache.values().stream().filter(entry -> { |
| final @WifiEntry.ConnectedState int connectedState = entry.getConnectedState(); |
| return connectedState == CONNECTED_STATE_CONNECTED |
| || connectedState == CONNECTED_STATE_CONNECTING; |
| }).findAny().orElse(null /* other */); |
| } |
| if (mConnectedWifiEntry == null) { |
| mConnectedWifiEntry = mOsuWifiEntryCache.values().stream().filter(entry -> { |
| final @WifiEntry.ConnectedState int connectedState = entry.getConnectedState(); |
| return connectedState == CONNECTED_STATE_CONNECTED |
| || connectedState == CONNECTED_STATE_CONNECTING; |
| }).findAny().orElse(null /* other */); |
| } |
| mWifiEntries.clear(); |
| for (String key : mStandardWifiEntryCache.keySet()) { |
| // Continue if we're connected to this network with a non-user-shareable config. |
| if (mConnectedWifiEntry != null |
| && TextUtils.equals(key, mConnectedWifiEntry.getKey())) { |
| continue; |
| } |
| StandardWifiEntry entry = mStandardWifiEntryCache.get(key); |
| StandardWifiEntry suggestedEntry = mSuggestedWifiEntryCache.get(key); |
| // Return a user-shareable suggested network to the user if one exists |
| if (!entry.isSaved() |
| && suggestedEntry != null && suggestedEntry.isUserShareable()) { |
| if (suggestedEntry.getConnectedState() == CONNECTED_STATE_DISCONNECTED) { |
| mWifiEntries.add(suggestedEntry); |
| } |
| } else { |
| if (entry.getConnectedState() == CONNECTED_STATE_DISCONNECTED) { |
| mWifiEntries.add(entry); |
| } |
| } |
| |
| } |
| mWifiEntries.addAll(mPasspointWifiEntryCache.values().stream().filter(entry -> |
| entry.getConnectedState() == CONNECTED_STATE_DISCONNECTED).collect(toList())); |
| mWifiEntries.addAll(mOsuWifiEntryCache.values().stream().filter(entry -> |
| entry.getConnectedState() == CONNECTED_STATE_DISCONNECTED).collect(toList())); |
| Collections.sort(mWifiEntries); |
| if (isVerboseLoggingEnabled()) { |
| Log.v(TAG, "Connected WifiEntry: " + mConnectedWifiEntry); |
| Log.v(TAG, "Updated WifiEntries: " + Arrays.toString(mWifiEntries.toArray())); |
| } |
| } |
| notifyOnWifiEntriesChanged(); |
| } |
| |
| /** |
| * Updates or removes scan results for the corresponding StandardWifiEntries. |
| * New entries will be created for scan results without an existing entry. |
| * Unreachable entries will be removed. |
| * |
| * @param scanResults List of valid scan results to convey as StandardWifiEntries |
| */ |
| @WorkerThread |
| private void updateStandardWifiEntryScans(@NonNull List<ScanResult> scanResults) { |
| checkNotNull(scanResults, "Scan Result list should not be null!"); |
| |
| // Group scans by StandardWifiEntry key |
| final Map<String, List<ScanResult>> scanResultsByKey = mapScanResultsToKey( |
| scanResults, |
| true /* chooseSingleSecurity */, |
| mWifiConfigCache, |
| mWifiManager.isWpa3SaeSupported(), |
| mWifiManager.isWpa3SuiteBSupported(), |
| mWifiManager.isEnhancedOpenSupported()); |
| |
| // Iterate through current entries and update each entry's scan results |
| mStandardWifiEntryCache.entrySet().removeIf(e -> { |
| final String key = e.getKey(); |
| final StandardWifiEntry entry = e.getValue(); |
| // Update scan results if available, or set to null. |
| entry.updateScanResultInfo(scanResultsByKey.remove(key)); |
| // Entry is now unreachable, remove it. |
| return entry.getLevel() == WIFI_LEVEL_UNREACHABLE; |
| }); |
| |
| // Create new StandardWifiEntry objects for each leftover group of scan results. |
| for (Map.Entry<String, List<ScanResult>> e: scanResultsByKey.entrySet()) { |
| final StandardWifiEntry newEntry = new StandardWifiEntry(mContext, mMainHandler, |
| e.getKey(), e.getValue(), mWifiManager, mWifiNetworkScoreCache, |
| false /* forSavedNetworksPage */); |
| // Populate with a saved config, if available |
| newEntry.updateConfig(mWifiConfigCache.get(newEntry.getKey())); |
| mStandardWifiEntryCache.put(newEntry.getKey(), newEntry); |
| } |
| } |
| |
| /** |
| * Updates or removes scan results for the corresponding StandardWifiEntries. |
| * New entries will be created for scan results without an existing entry. |
| * Unreachable entries will be removed. |
| * |
| * @param scanResults List of valid scan results to convey as StandardWifiEntries |
| */ |
| @WorkerThread |
| private void updateSuggestedWifiEntryScans(@NonNull List<ScanResult> scanResults) { |
| checkNotNull(scanResults, "Scan Result list should not be null!"); |
| |
| // Group scans by StandardWifiEntry key |
| final Map<String, List<ScanResult>> scanResultsByKey = mapScanResultsToKey( |
| scanResults, |
| true /* chooseSingleSecurity */, |
| mWifiConfigCache, |
| mWifiManager.isWpa3SaeSupported(), |
| mWifiManager.isWpa3SuiteBSupported(), |
| mWifiManager.isEnhancedOpenSupported()); |
| |
| Map<String, WifiConfiguration> userSharedConfigsByKey = |
| mWifiManager.getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(scanResults) |
| .stream() |
| .collect(Collectors.toMap( |
| StandardWifiEntry::wifiConfigToStandardWifiEntryKey, |
| Function.identity())); |
| |
| Set<String> seenKeys = new TreeSet<>(); |
| for (String key : userSharedConfigsByKey.keySet()) { |
| seenKeys.add(key); |
| if (!mSuggestedWifiEntryCache.containsKey(key)) { |
| mSuggestedWifiEntryCache.put(key, new StandardWifiEntry(mContext, mMainHandler, key, |
| userSharedConfigsByKey.get(key), mWifiManager, |
| mWifiNetworkScoreCache, false /* forSavedNetworksPage */)); |
| } |
| final StandardWifiEntry entry = mSuggestedWifiEntryCache.get(key); |
| entry.setUserShareable(true); |
| entry.updateScanResultInfo(scanResultsByKey.get(key)); |
| } |
| |
| // Remove entries without configs |
| mSuggestedWifiEntryCache.entrySet() |
| .removeIf(entry -> { |
| StandardWifiEntry wifiEntry = entry.getValue(); |
| String key = entry.getKey(); |
| if (!seenKeys.contains(key)) { |
| wifiEntry.updateConfig(mSuggestedConfigCache.get(key)); |
| wifiEntry.setUserShareable(false); |
| } |
| return !wifiEntry.isSuggestion(); |
| }); |
| } |
| |
| @WorkerThread |
| private void updatePasspointWifiEntryScans(@NonNull List<ScanResult> scanResults) { |
| checkNotNull(scanResults, "Scan Result list should not be null!"); |
| |
| Set<String> seenKeys = new TreeSet<>(); |
| List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> matchingWifiConfigs = |
| mWifiManager.getAllMatchingWifiConfigs(scanResults); |
| |
| for (Pair<WifiConfiguration, Map<Integer, List<ScanResult>>> pair : matchingWifiConfigs) { |
| final WifiConfiguration wifiConfig = pair.first; |
| final List<ScanResult> homeScans = |
| pair.second.get(WifiManager.PASSPOINT_HOME_NETWORK); |
| final List<ScanResult> roamingScans = |
| pair.second.get(WifiManager.PASSPOINT_ROAMING_NETWORK); |
| final String key = uniqueIdToPasspointWifiEntryKey(wifiConfig.getKey()); |
| seenKeys.add(key); |
| |
| // Create PasspointWifiEntry if one doesn't exist for the seen key yet. |
| if (!mPasspointWifiEntryCache.containsKey(key)) { |
| if (wifiConfig.fromWifiNetworkSuggestion) { |
| mPasspointWifiEntryCache.put(key, new PasspointWifiEntry(mContext, |
| mMainHandler, wifiConfig, mWifiManager, |
| mWifiNetworkScoreCache, false /* forSavedNetworksPage */)); |
| } else if (mPasspointConfigCache.containsKey(key)) { |
| mPasspointWifiEntryCache.put(key, new PasspointWifiEntry(mContext, |
| mMainHandler, mPasspointConfigCache.get(key), mWifiManager, |
| mWifiNetworkScoreCache, false /* forSavedNetworksPage */)); |
| } else { |
| // Failed to find PasspointConfig for a provisioned Passpoint network |
| continue; |
| } |
| } |
| mPasspointWifiEntryCache.get(key).updateScanResultInfo(wifiConfig, |
| homeScans, roamingScans); |
| } |
| |
| // Remove entries that are now unreachable |
| mPasspointWifiEntryCache.entrySet() |
| .removeIf(entry -> entry.getValue().getLevel() == WIFI_LEVEL_UNREACHABLE |
| || !seenKeys.contains(entry.getKey())); |
| } |
| |
| @WorkerThread |
| private void updateOsuWifiEntryScans(@NonNull List<ScanResult> scanResults) { |
| checkNotNull(scanResults, "Scan Result list should not be null!"); |
| |
| Map<OsuProvider, List<ScanResult>> osuProviderToScans = |
| mWifiManager.getMatchingOsuProviders(scanResults); |
| Set<OsuProvider> alreadyProvisioned = |
| mWifiManager.getMatchingPasspointConfigsForOsuProviders(osuProviderToScans.keySet()) |
| .keySet(); |
| // Update each OsuWifiEntry with new scans (or empty scans). |
| for (OsuWifiEntry entry : mOsuWifiEntryCache.values()) { |
| entry.updateScanResultInfo(osuProviderToScans.remove(entry.getOsuProvider())); |
| } |
| |
| // Create a new entry for each OsuProvider not already matched to an OsuWifiEntry |
| for (OsuProvider provider : osuProviderToScans.keySet()) { |
| OsuWifiEntry newEntry = new OsuWifiEntry(mContext, mMainHandler, provider, mWifiManager, |
| mWifiNetworkScoreCache, false /* forSavedNetworksPage */); |
| newEntry.updateScanResultInfo(osuProviderToScans.get(provider)); |
| mOsuWifiEntryCache.put(osuProviderToOsuWifiEntryKey(provider), newEntry); |
| } |
| |
| // Remove entries that are now unreachable or already provisioned |
| mOsuWifiEntryCache.entrySet() |
| .removeIf(entry -> entry.getValue().getLevel() == WIFI_LEVEL_UNREACHABLE |
| || alreadyProvisioned.contains(entry.getValue().getOsuProvider())); |
| } |
| |
| /** |
| * Conditionally updates the WifiEntry scan results based on the current wifi state and |
| * whether the last scan succeeded or not. |
| */ |
| @WorkerThread |
| private void conditionallyUpdateScanResults(boolean lastScanSucceeded) { |
| if (mWifiManager.getWifiState() == WifiManager.WIFI_STATE_DISABLED) { |
| updateStandardWifiEntryScans(Collections.emptyList()); |
| updateSuggestedWifiEntryScans(Collections.emptyList()); |
| updatePasspointWifiEntryScans(Collections.emptyList()); |
| updateOsuWifiEntryScans(Collections.emptyList()); |
| return; |
| } |
| |
| long scanAgeWindow = mMaxScanAgeMillis; |
| if (lastScanSucceeded) { |
| // Scan succeeded, cache new scans |
| mScanResultUpdater.update(mWifiManager.getScanResults()); |
| } else { |
| // Scan failed, increase scan age window to prevent WifiEntry list from |
| // clearing prematurely. |
| scanAgeWindow += mScanIntervalMillis; |
| } |
| |
| List<ScanResult> scanResults = mScanResultUpdater.getScanResults(scanAgeWindow); |
| updateStandardWifiEntryScans(scanResults); |
| updateSuggestedWifiEntryScans(scanResults); |
| updatePasspointWifiEntryScans(scanResults); |
| updateOsuWifiEntryScans(scanResults); |
| } |
| |
| /** |
| * Updates the WifiConfiguration caches for a single saved/ephemeral/suggested network and |
| * updates the corresponding WifiEntry with the new config. |
| * |
| * @param config WifiConfiguration to update |
| * @param changeReason WifiManager.CHANGE_REASON_ADDED, WifiManager.CHANGE_REASON_REMOVED, or |
| * WifiManager.CHANGE_REASON_CONFIG_CHANGE |
| */ |
| @WorkerThread |
| private void updateWifiConfiguration(@NonNull WifiConfiguration config, |
| int changeReason) { |
| checkNotNull(config, "Config should not be null!"); |
| |
| final String key = wifiConfigToStandardWifiEntryKey(config); |
| StandardWifiEntry updatedEntry; |
| WifiConfiguration updatedConfig; |
| if (config.fromWifiNetworkSuggestion) { |
| if (changeReason == WifiManager.CHANGE_REASON_REMOVED) { |
| mSuggestedConfigCache.remove(key); |
| } else { // CHANGE_REASON_ADDED || CHANGE_REASON_CONFIG_CHANGE |
| mSuggestedConfigCache.put(key, config); |
| } |
| updatedConfig = mSuggestedConfigCache.get(key); |
| updatedEntry = mSuggestedWifiEntryCache.get(key); |
| } else { |
| if (changeReason == WifiManager.CHANGE_REASON_REMOVED) { |
| mWifiConfigCache.remove(key); |
| } else { // CHANGE_REASON_ADDED || CHANGE_REASON_CONFIG_CHANGE |
| mWifiConfigCache.put(key, config); |
| } |
| updatedConfig = mWifiConfigCache.get(key); |
| updatedEntry = mStandardWifiEntryCache.get(key); |
| mNumSavedNetworks = (int) mWifiConfigCache.values().stream() |
| .filter(cachedConfig -> |
| !cachedConfig.isEphemeral() && !cachedConfig.isPasspoint()).count(); |
| } |
| |
| if (updatedEntry != null) { |
| updatedEntry.updateConfig(updatedConfig); |
| } |
| } |
| |
| /** |
| * Updates the WifiConfiguration caches for saved/ephemeral/suggested networks and updates the |
| * corresponding WifiEntries with the new configs. |
| * |
| * @param configs List of all saved/ephemeral/suggested WifiConfigurations |
| */ |
| @WorkerThread |
| private void updateWifiConfigurations(@NonNull List<WifiConfiguration> configs) { |
| checkNotNull(configs, "Config list should not be null!"); |
| mWifiConfigCache.clear(); |
| mSuggestedConfigCache.clear(); |
| for (WifiConfiguration config : configs) { |
| if (config.fromWifiNetworkSuggestion) { |
| mSuggestedConfigCache.put(wifiConfigToStandardWifiEntryKey(config), config); |
| } else { |
| mWifiConfigCache.put(wifiConfigToStandardWifiEntryKey(config), config); |
| } |
| } |
| mNumSavedNetworks = (int) mWifiConfigCache.values().stream() |
| .filter(cachedConfig -> |
| !cachedConfig.isEphemeral() && !cachedConfig.isPasspoint()).count(); |
| |
| // Iterate through current entries and update each entry's config |
| mStandardWifiEntryCache.entrySet().forEach((entry) -> { |
| final StandardWifiEntry wifiEntry = entry.getValue(); |
| final String key = wifiEntry.getKey(); |
| final WifiConfiguration config = mWifiConfigCache.get(key); |
| if (config != null && config.isPasspoint()) { |
| return; |
| } |
| wifiEntry.updateConfig(config); |
| }); |
| |
| // Iterate through current suggestion entries and update each entry's config |
| mSuggestedWifiEntryCache.entrySet().removeIf((entry) -> { |
| final StandardWifiEntry wifiEntry = entry.getValue(); |
| final String key = wifiEntry.getKey(); |
| final WifiConfiguration config = mSuggestedConfigCache.get(key); |
| if (config != null && !config.isPasspoint()) { |
| wifiEntry.updateConfig(config); |
| return false; |
| } else { |
| return true; |
| } |
| }); |
| } |
| |
| @WorkerThread |
| private void updatePasspointConfigurations(@NonNull List<PasspointConfiguration> configs) { |
| checkNotNull(configs, "Config list should not be null!"); |
| mPasspointConfigCache.clear(); |
| mPasspointConfigCache.putAll(configs.stream().collect( |
| toMap(config -> uniqueIdToPasspointWifiEntryKey( |
| config.getUniqueId()), Function.identity()))); |
| |
| // Iterate through current entries and update each entry's config or remove if no config |
| // matches the entry anymore. |
| mPasspointWifiEntryCache.entrySet().removeIf((entry) -> { |
| final PasspointWifiEntry wifiEntry = entry.getValue(); |
| final String key = wifiEntry.getKey(); |
| wifiEntry.updatePasspointConfig(mPasspointConfigCache.get(key)); |
| return !wifiEntry.isSubscription() && !wifiEntry.isSuggestion(); |
| }); |
| } |
| |
| /** |
| * Updates all WifiEntries with the current connection info. |
| * @param wifiInfo WifiInfo of the current connection |
| * @param networkInfo NetworkInfo of the current connection |
| */ |
| @WorkerThread |
| private void updateConnectionInfo(@Nullable WifiInfo wifiInfo, |
| @Nullable NetworkInfo networkInfo) { |
| for (WifiEntry entry : mStandardWifiEntryCache.values()) { |
| entry.updateConnectionInfo(wifiInfo, networkInfo); |
| } |
| for (WifiEntry entry : mSuggestedWifiEntryCache.values()) { |
| entry.updateConnectionInfo(wifiInfo, networkInfo); |
| } |
| for (WifiEntry entry : mPasspointWifiEntryCache.values()) { |
| entry.updateConnectionInfo(wifiInfo, networkInfo); |
| } |
| for (WifiEntry entry : mOsuWifiEntryCache.values()) { |
| entry.updateConnectionInfo(wifiInfo, networkInfo); |
| } |
| // Create a StandardWifiEntry for the current connection if there are no scan results yet. |
| conditionallyCreateConnectedStandardWifiEntry(wifiInfo, networkInfo); |
| conditionallyCreateConnectedSuggestedWifiEntry(wifiInfo, networkInfo); |
| conditionallyCreateConnectedPasspointWifiEntry(wifiInfo, networkInfo); |
| } |
| |
| /** |
| * Creates and caches a StandardWifiEntry representing the current connection using the current |
| * WifiInfo and NetworkInfo if there are no scans results available for the network yet. |
| * @param wifiInfo WifiInfo of the current connection |
| * @param networkInfo NetworkInfo of the current connection |
| */ |
| @WorkerThread |
| private void conditionallyCreateConnectedStandardWifiEntry(@Nullable WifiInfo wifiInfo, |
| @Nullable NetworkInfo networkInfo) { |
| if (wifiInfo.isPasspointAp() || wifiInfo.isOsuAp()) { |
| return; |
| } |
| |
| final int connectedNetId = wifiInfo.getNetworkId(); |
| mWifiConfigCache.values().stream() |
| .filter(config -> |
| config.networkId == connectedNetId && !mStandardWifiEntryCache.containsKey( |
| wifiConfigToStandardWifiEntryKey(config))) |
| .findAny().ifPresent(config -> { |
| final StandardWifiEntry connectedEntry = |
| new StandardWifiEntry(mContext, mMainHandler, |
| wifiConfigToStandardWifiEntryKey(config), config, mWifiManager, |
| mWifiNetworkScoreCache, false /* forSavedNetworksPage */); |
| connectedEntry.updateConnectionInfo(wifiInfo, networkInfo); |
| mStandardWifiEntryCache.put(connectedEntry.getKey(), connectedEntry); |
| }); |
| } |
| |
| /** |
| * Creates and caches a suggested StandardWifiEntry representing the current connection using |
| * the current WifiInfo and NetworkInfo if there are no scans results available for the network |
| * yet. |
| * @param wifiInfo WifiInfo of the current connection |
| * @param networkInfo NetworkInfo of the current connection |
| */ |
| @WorkerThread |
| private void conditionallyCreateConnectedSuggestedWifiEntry(@Nullable WifiInfo wifiInfo, |
| @Nullable NetworkInfo networkInfo) { |
| if (wifiInfo.isPasspointAp() || wifiInfo.isOsuAp()) { |
| return; |
| } |
| |
| final int connectedNetId = wifiInfo.getNetworkId(); |
| mSuggestedConfigCache.values().stream() |
| .filter(config -> |
| config.networkId == connectedNetId && !mSuggestedWifiEntryCache.containsKey( |
| wifiConfigToStandardWifiEntryKey(config))) |
| .findAny().ifPresent(config -> { |
| final StandardWifiEntry connectedEntry = |
| new StandardWifiEntry(mContext, mMainHandler, |
| wifiConfigToStandardWifiEntryKey(config), config, mWifiManager, |
| mWifiNetworkScoreCache, false /* forSavedNetworksPage */); |
| connectedEntry.updateConnectionInfo(wifiInfo, networkInfo); |
| mSuggestedWifiEntryCache.put(connectedEntry.getKey(), connectedEntry); |
| }); |
| } |
| |
| |
| /** |
| * Creates and caches a PasspointWifiEntry representing the current connection using the current |
| * WifiInfo and NetworkInfo if there are no scans results available for the network yet. |
| * @param wifiInfo WifiInfo of the current connection |
| * @param networkInfo NetworkInfo of the current connection |
| */ |
| @WorkerThread |
| private void conditionallyCreateConnectedPasspointWifiEntry(@Nullable WifiInfo wifiInfo, |
| @Nullable NetworkInfo networkInfo) { |
| if (!wifiInfo.isPasspointAp()) { |
| return; |
| } |
| |
| final int connectedNetId = wifiInfo.getNetworkId(); |
| Stream.concat(mWifiConfigCache.values().stream(), mSuggestedConfigCache.values().stream()) |
| .filter(wifiConfig -> |
| wifiConfig.isPasspoint() && wifiConfig.networkId == connectedNetId |
| && !mPasspointWifiEntryCache.containsKey( |
| uniqueIdToPasspointWifiEntryKey(wifiConfig.getKey()))) |
| .findAny().ifPresent(wifiConfig -> { |
| PasspointConfiguration passpointConfig = mPasspointConfigCache.get( |
| uniqueIdToPasspointWifiEntryKey(wifiConfig.getKey())); |
| PasspointWifiEntry connectedEntry; |
| if (passpointConfig != null) { |
| connectedEntry = new PasspointWifiEntry(mContext, mMainHandler, |
| passpointConfig, mWifiManager, mWifiNetworkScoreCache, |
| false /* forSavedNetworksPage */); |
| } else { |
| // Suggested PasspointWifiEntry without a corresponding Passpoint config |
| connectedEntry = new PasspointWifiEntry(mContext, mMainHandler, |
| wifiConfig, mWifiManager, mWifiNetworkScoreCache, |
| false /* forSavedNetworksPage */); |
| } |
| connectedEntry.updateConnectionInfo(wifiInfo, networkInfo); |
| mPasspointWifiEntryCache.put(connectedEntry.getKey(), connectedEntry); |
| }); |
| } |
| |
| /** |
| * Posts onWifiEntryChanged callback on the main thread. |
| */ |
| @WorkerThread |
| private void notifyOnWifiEntriesChanged() { |
| if (mListener != null) { |
| mMainHandler.post(mListener::onWifiEntriesChanged); |
| } |
| } |
| |
| /** |
| * Posts onNumSavedNetworksChanged callback on the main thread. |
| */ |
| @WorkerThread |
| private void notifyOnNumSavedNetworksChanged() { |
| if (mListener != null) { |
| mMainHandler.post(mListener::onNumSavedNetworksChanged); |
| } |
| } |
| |
| /** |
| * Posts onNumSavedSubscriptionsChanged callback on the main thread. |
| */ |
| @WorkerThread |
| private void notifyOnNumSavedSubscriptionsChanged() { |
| if (mListener != null) { |
| mMainHandler.post(mListener::onNumSavedSubscriptionsChanged); |
| } |
| } |
| |
| /** |
| * Listener for changes to the list of visible WifiEntries as well as the number of saved |
| * networks and subscriptions. |
| * |
| * These callbacks must be run on the MainThread. |
| */ |
| public interface WifiPickerTrackerCallback extends BaseWifiTracker.BaseWifiTrackerCallback { |
| /** |
| * Called when there are changes to |
| * {@link #getConnectedWifiEntry()} |
| * {@link #getWifiEntries()} |
| */ |
| @MainThread |
| void onWifiEntriesChanged(); |
| |
| /** |
| * Called when there are changes to |
| * {@link #getNumSavedNetworks()} |
| */ |
| @MainThread |
| void onNumSavedNetworksChanged(); |
| |
| /** |
| * Called when there are changes to |
| * {@link #getNumSavedSubscriptions()} |
| */ |
| @MainThread |
| void onNumSavedSubscriptionsChanged(); |
| } |
| } |