diff options
3 files changed, 211 insertions, 57 deletions
diff --git a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/StandardWifiEntry.java b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/StandardWifiEntry.java index af7df4d9c..54076dbe1 100644 --- a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/StandardWifiEntry.java +++ b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/StandardWifiEntry.java @@ -63,7 +63,7 @@ class StandardWifiEntry extends WifiEntry { throw new IllegalArgumentException("Cannot construct with empty ScanResult list!"); } final ScanResult firstScan = scanResults.get(0); - mKey = createStandardWifiEntryKey(firstScan); + mKey = scanResultToStandardWifiEntryKey(firstScan); mSsid = firstScan.SSID; mSecurity = getSecurityFromScanResult(firstScan); updateScanResultInfo(scanResults); @@ -76,7 +76,7 @@ class StandardWifiEntry extends WifiEntry { checkNotNull(config, "Cannot construct with null config!"); checkNotNull(config.SSID, "Supplied config must have an SSID!"); - mKey = createStandardWifiEntryKey(config); + mKey = wifiConfigToStandardWifiEntryKey(config); mSsid = removeDoubleQuotes(config.SSID); mSecurity = getSecurityFromWifiConfiguration(config); mWifiConfig = config; @@ -102,6 +102,7 @@ class StandardWifiEntry extends WifiEntry { @Override public String getSummary() { // TODO(b/70983952): Fill this method in + if (isSaved()) return "Saved"; // Placeholder for visual verification return null; } @@ -266,9 +267,9 @@ class StandardWifiEntry extends WifiEntry { } @WorkerThread - void updateScanResultInfo(@NonNull List<ScanResult> scanResults) + void updateScanResultInfo(@Nullable List<ScanResult> scanResults) throws IllegalArgumentException { - checkNotNull(scanResults); + if (scanResults == null) scanResults = new ArrayList<>(); for (ScanResult result : scanResults) { if (!TextUtils.equals(result.SSID, mSsid)) { @@ -322,13 +323,13 @@ class StandardWifiEntry extends WifiEntry { } @NonNull - static String createStandardWifiEntryKey(@NonNull ScanResult scan) { + static String scanResultToStandardWifiEntryKey(@NonNull ScanResult scan) { checkNotNull(scan, "Cannot create key with null scan result!"); return KEY_PREFIX + scan.SSID + "," + getSecurityFromScanResult(scan); } @NonNull - static String createStandardWifiEntryKey(@NonNull WifiConfiguration config) { + static String wifiConfigToStandardWifiEntryKey(@NonNull WifiConfiguration config) { checkNotNull(config, "Cannot create key with null config!"); checkNotNull(config.SSID, "Cannot create key with null SSID in config!"); return KEY_PREFIX + removeDoubleQuotes(config.SSID) + "," diff --git a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiTracker2.java b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiTracker2.java index a3cfa8af9..3feab63d6 100644 --- a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiTracker2.java +++ b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiTracker2.java @@ -16,9 +16,13 @@ package com.android.wifitrackerlib; -import static com.android.wifitrackerlib.StandardWifiEntry.createStandardWifiEntryKey; +import static androidx.core.util.Preconditions.checkNotNull; + +import static com.android.wifitrackerlib.StandardWifiEntry.wifiConfigToStandardWifiEntryKey; +import static com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE; import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.toList; import android.content.BroadcastReceiver; import android.content.Context; @@ -27,6 +31,7 @@ import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.NetworkScoreManager; import android.net.wifi.ScanResult; +import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.os.Handler; import android.os.Looper; @@ -50,6 +55,8 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; /** * Keeps track of the state of Wi-Fi and supplies {@link WifiEntry} for use in Wi-Fi picker lists. @@ -113,21 +120,42 @@ public class WifiTracker2 implements LifecycleObserver { } notifyOnWifiStateChanged(); } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) { - final boolean lastScanSucceeded = - intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, true); - if (lastScanSucceeded) updateScanResults(); - updateWifiEntries(lastScanSucceeded); - notifyOnWifiEntriesChanged(); + if (intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, true)) { + // Scan succeeded, update scans aged to mMaxScanAgeMillis + mScanResultUpdater.update(mWifiManager.getScanResults()); + updateStandardWifiEntryScans(mScanResultUpdater.getScanResults( + mMaxScanAgeMillis)); + } else { + // Scan failed, keep results from previous scan to prevent WifiEntry list from + // clearing prematurely. + updateStandardWifiEntryScans(mScanResultUpdater.getScanResults( + mMaxScanAgeMillis + mScanIntervalMillis)); + } + updateWifiEntries(); + } else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action)) { + final WifiConfiguration config = + (WifiConfiguration) intent.getExtra(WifiManager.EXTRA_WIFI_CONFIGURATION); + if (config != null) { + updateStandardWifiEntryConfig( + config, (Integer) intent.getExtra(WifiManager.EXTRA_CHANGE_REASON)); + } else { + updateStandardWifiEntryConfigs(mWifiManager.getConfiguredNetworks()); + } + updateWifiEntries(); } } }; private final ScanResultUpdater mScanResultUpdater; private final Scanner mScanner; - // Lock object for mWifiEntries + // 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<>(); + + // Cache containing StandardWifiEntries for visible networks and saved networks + // Must be accessed only by the worker thread. private final Map<String, StandardWifiEntry> mStandardWifiEntryCache = new HashMap<>(); /** @@ -195,9 +223,10 @@ public class WifiTracker2 implements LifecycleObserver { } notifyOnWifiStateChanged(); - updateScanResults(); - updateWifiEntries(true); - notifyOnWifiEntriesChanged(); + mScanResultUpdater.update(mWifiManager.getScanResults()); + updateStandardWifiEntryScans(mScanResultUpdater.getScanResults(mMaxScanAgeMillis)); + updateStandardWifiEntryConfigs(mWifiManager.getConfiguredNetworks()); + updateWifiEntries(); }); } @@ -266,62 +295,122 @@ public class WifiTracker2 implements LifecycleObserver { return new ArrayList<>(); } + /** + * Update the list returned by getWifiEntries() with the current states of the entry caches. + */ @WorkerThread - private void updateScanResults() { - mScanResultUpdater.update(mWifiManager.getScanResults()); - if (isVerboseLoggingEnabled()) { - Log.v(TAG, "Updated scans: " + Arrays.toString( - mScanResultUpdater.getScanResults(mMaxScanAgeMillis).toArray())); - } - } - - @WorkerThread - private void updateWifiEntries(boolean lastScanSucceeded) { - updateStandardWifiEntryCache(lastScanSucceeded); - // TODO (b/70983952): Update Passpoint/Suggestion entries here. - // updatePasspointWifiEntries(); - // updateCarrierWifiEntries(); - // updateSuggestionWifiEntries(); + private void updateWifiEntries() { synchronized (mLock) { mWifiEntries.clear(); - mWifiEntries.addAll(mStandardWifiEntryCache.values()); - // mWifiEntries.addAll(mPasspointWifiEntries); - // mWifiEntries.addAll(mCarrierWifiEntries); - // mWifiEntries.addAll(mSuggestionWifiEntries); + mWifiEntries.addAll(mStandardWifiEntryCache.values().stream().filter( + entry -> entry.getLevel() != WIFI_LEVEL_UNREACHABLE).collect(toList())); + // mWifiEntries.addAll(mPasspointWifiEntryCache); + // mWifiEntries.addAll(mCarrierWifiEntryCache); + // mWifiEntries.addAll(mSuggestionWifiEntryCache); Collections.sort(mWifiEntries); if (isVerboseLoggingEnabled()) { Log.v(TAG, "Updated WifiEntries: " + Arrays.toString(mWifiEntries.toArray())); } } + notifyOnWifiEntriesChanged(); + } + + /** + * Updates or removes a single WifiConfiguration for the corresponding StandardWifiEntry. + * A new entry will be created for configs without an existing entry. + * Unsaved and unreachable entries will be removed. + * + * @param config WifiConfiguration to update + * @param changeReason WifiManager.CHANGE_REASON_ADDED, WifiManager.CHANGE_REASON_REMOVED, or + * WifiManager.CHANGE_REASON_CONFIG_CHANGE + */ + @WorkerThread + private void updateStandardWifiEntryConfig(WifiConfiguration config, int changeReason) { + checkNotNull(config, "Config should not be null!"); + + final String key = wifiConfigToStandardWifiEntryKey(config); + final StandardWifiEntry entry = mStandardWifiEntryCache.get(key); + + if (entry != null) { + if (changeReason == WifiManager.CHANGE_REASON_REMOVED) { + entry.updateConfig(null); + // Remove unsaved, unreachable entry from cache + if (entry.getLevel() == WIFI_LEVEL_UNREACHABLE) mStandardWifiEntryCache.remove(key); + } else { // CHANGE_REASON_ADDED || CHANGE_REASON_CONFIG_CHANGE + entry.updateConfig(config); + } + } else { + if (changeReason != WifiManager.CHANGE_REASON_REMOVED) { + mStandardWifiEntryCache.put(key, new StandardWifiEntry(mMainHandler, config)); + } + } } /** - * Updates mStandardWifiEntryCache with fresh scans. + * Updates or removes all saved WifiConfigurations for the corresponding StandardWifiEntries. + * New entries will be created for configs without an existing entry. + * Unsaved and unreachable entries will be removed. + * + * @param configs List of saved WifiConfigurations */ @WorkerThread - private void updateStandardWifiEntryCache(boolean lastScanSucceeded) { - // If the current scan failed, use results from the previous scan to prevent flicker. - final List<ScanResult> scanResults = mScanResultUpdater.getScanResults( - lastScanSucceeded ? mMaxScanAgeMillis : mMaxScanAgeMillis + mScanIntervalMillis); - final Map<String, StandardWifiEntry> updatedStandardWifiEntries = new HashMap<>(); + private void updateStandardWifiEntryConfigs(@NonNull List<WifiConfiguration> configs) { + checkNotNull(configs, "Config list should not be null!"); + + // Group configs by StandardWifiEntry key + final Map<String, WifiConfiguration> wifiConfigsByKey = + configs.stream().collect(Collectors.toMap( + StandardWifiEntry::wifiConfigToStandardWifiEntryKey, + Function.identity())); + + // Iterate through current entries and update each entry's config + mStandardWifiEntryCache.entrySet().removeIf(e -> { + final String key = e.getKey(); + final StandardWifiEntry entry = e.getValue(); + // Update the config if available, or set to null. + entry.updateConfig(wifiConfigsByKey.remove(key)); + // Entry is not saved and is unreachable, remove it. + return !entry.isSaved() && entry.getLevel() == WIFI_LEVEL_UNREACHABLE; + }); + + // Create new StandardWifiEntry objects for each leftover config + for (Map.Entry<String, WifiConfiguration> e: wifiConfigsByKey.entrySet()) { + mStandardWifiEntryCache.put(e.getKey(), + new StandardWifiEntry(mMainHandler, e.getValue())); + } + } - // Group scans by StandardWifiEntry Key + /** + * Updates or removes scan results for the corresponding StandardWifiEntries. + * New entries will be created for scan results without an existing entry. + * Unsaved and 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 = scanResults.stream() .filter(scanResult -> !TextUtils.isEmpty(scanResult.SSID)) - .collect(groupingBy(StandardWifiEntry::createStandardWifiEntryKey)); + .collect(groupingBy(StandardWifiEntry::scanResultToStandardWifiEntryKey)); + + // 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 not saved and is unreachable, remove it. + return !entry.isSaved() && entry.getLevel() == WIFI_LEVEL_UNREACHABLE; + }); - // Create or get cached StandardWifiEntry by key - for (String key : scanResultsByKey.keySet()) { - StandardWifiEntry entry = mStandardWifiEntryCache.get(key); - if (entry == null) { - entry = new StandardWifiEntry(mMainHandler, scanResultsByKey.get(key)); - } else { - entry.updateScanResultInfo(scanResultsByKey.get(key)); - } - updatedStandardWifiEntries.put(key, entry); + // Create new StandardWifiEntry objects for each leftover group of scan results. + for (Map.Entry<String, List<ScanResult>> e: scanResultsByKey.entrySet()) { + mStandardWifiEntryCache.put(e.getKey(), + new StandardWifiEntry(mMainHandler, e.getValue())); } - mStandardWifiEntryCache.clear(); - mStandardWifiEntryCache.putAll(updatedStandardWifiEntries); } /** diff --git a/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/WifiTracker2Test.java b/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/WifiTracker2Test.java index 8cbb04dab..f7dc5cc30 100644 --- a/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/WifiTracker2Test.java +++ b/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/WifiTracker2Test.java @@ -16,7 +16,6 @@ package com.android.wifitrackerlib; -import static com.android.wifitrackerlib.StandardWifiEntry.createStandardWifiEntryKey; import static com.android.wifitrackerlib.TestUtils.buildScanResult; import static com.google.common.truth.Truth.assertThat; @@ -32,6 +31,7 @@ import android.content.Intent; import android.net.ConnectivityManager; import android.net.NetworkScoreManager; import android.net.wifi.ScanResult; +import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.os.Handler; import android.os.test.TestLooper; @@ -47,6 +47,7 @@ import org.mockito.MockitoAnnotations; import java.time.Clock; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; public class WifiTracker2Test { @@ -228,8 +229,8 @@ public class WifiTracker2Test { } assertThat(seenKeys).containsExactly( - createStandardWifiEntryKey(openNetwork), - createStandardWifiEntryKey(secureNetwork)); + StandardWifiEntry.scanResultToStandardWifiEntryKey(openNetwork), + StandardWifiEntry.scanResultToStandardWifiEntryKey(secureNetwork)); } /** @@ -307,4 +308,67 @@ public class WifiTracker2Test { // Successful scan should time out old entries. assertThat(wifiTracker2.getWifiEntries()).isEmpty(); } + + /** + * Tests that a CONFIGURED_NETWORKS_CHANGED broadcast updates the correct WifiEntry from + * unsaved to saved. + */ + @Test + public void testGetWifiEntries_configuredNetworksChanged_unsavedToSaved() { + final WifiTracker2 wifiTracker2 = createTestWifiTracker2(); + wifiTracker2.onStart(); + verify(mMockContext).registerReceiver(mBroadcastReceiverCaptor.capture(), + any(), any(), any()); + + when(mMockWifiManager.getScanResults()).thenReturn(Arrays.asList( + buildScanResult("ssid", "bssid", START_MILLIS))); + mBroadcastReceiverCaptor.getValue().onReceive(mMockContext, + new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)); + final WifiEntry entry = wifiTracker2.getWifiEntries().get(0); + + assertThat(entry.isSaved()).isFalse(); + + final WifiConfiguration config = new WifiConfiguration(); + config.SSID = "\"ssid\""; + mBroadcastReceiverCaptor.getValue().onReceive(mMockContext, + new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION) + .putExtra(WifiManager.EXTRA_WIFI_CONFIGURATION, config) + .putExtra(WifiManager.EXTRA_CHANGE_REASON, + WifiManager.CHANGE_REASON_ADDED)); + + assertThat(entry.isSaved()).isTrue(); + } + + /** + * Tests that a CONFIGURED_NETWORKS_CHANGED broadcast updates the correct WifiEntry from + * saved to unsaved. + */ + @Test + public void testGetWifiEntries_configuredNetworksChanged_savedToUnsaved() { + final WifiTracker2 wifiTracker2 = createTestWifiTracker2(); + final WifiConfiguration config = new WifiConfiguration(); + config.SSID = "\"ssid\""; + when(mMockWifiManager.getConfiguredNetworks()) + .thenReturn(Collections.singletonList(config)); + wifiTracker2.onStart(); + verify(mMockContext).registerReceiver(mBroadcastReceiverCaptor.capture(), + any(), any(), any()); + mTestLooper.dispatchAll(); + + when(mMockWifiManager.getScanResults()).thenReturn(Arrays.asList( + buildScanResult("ssid", "bssid", START_MILLIS))); + mBroadcastReceiverCaptor.getValue().onReceive(mMockContext, + new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)); + final WifiEntry entry = wifiTracker2.getWifiEntries().get(0); + + assertThat(entry.isSaved()).isTrue(); + + mBroadcastReceiverCaptor.getValue().onReceive(mMockContext, + new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION) + .putExtra(WifiManager.EXTRA_WIFI_CONFIGURATION, config) + .putExtra(WifiManager.EXTRA_CHANGE_REASON, + WifiManager.CHANGE_REASON_REMOVED)); + + assertThat(entry.isSaved()).isFalse(); + } } |