summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--service/java/com/android/server/wifi/WifiNetworkFactory.java113
-rw-r--r--tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java143
2 files changed, 210 insertions, 46 deletions
diff --git a/service/java/com/android/server/wifi/WifiNetworkFactory.java b/service/java/com/android/server/wifi/WifiNetworkFactory.java
index 48797f773..be6ac6473 100644
--- a/service/java/com/android/server/wifi/WifiNetworkFactory.java
+++ b/service/java/com/android/server/wifi/WifiNetworkFactory.java
@@ -82,6 +82,8 @@ public class WifiNetworkFactory extends NetworkFactory {
@VisibleForTesting
private static final int SCORE_FILTER = 60;
@VisibleForTesting
+ public static final int CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS = 20 * 1000; // 20 seconds
+ @VisibleForTesting
public static final int PERIODIC_SCAN_INTERVAL_MS = 10 * 1000; // 10 seconds
@VisibleForTesting
public static final int NETWORK_CONNECTION_TIMEOUT_MS = 30 * 1000; // 30 seconds
@@ -230,37 +232,10 @@ public class WifiNetworkFactory extends NetworkFactory {
if (mVerboseLoggingEnabled) {
Log.v(TAG, "Received " + scanResults.length + " scan results");
}
- List<ScanResult> matchedScanResults =
- getNetworksMatchingActiveNetworkRequest(scanResults);
- if (mActiveMatchedScanResults == null) {
- // only note the first match size in metrics (chances of this changing in further
- // scans is pretty low)
- mWifiMetrics.incrementNetworkRequestApiMatchSizeHistogram(
- matchedScanResults.size());
- }
- mActiveMatchedScanResults = matchedScanResults;
-
- ScanResult approvedScanResult = null;
- if (isActiveRequestForSingleAccessPoint()) {
- approvedScanResult =
- findUserApprovedAccessPointForActiveRequestFromActiveMatchedScanResults();
- }
- if (approvedScanResult != null
- && !mWifiConfigManager.wasEphemeralNetworkDeleted(
- ScanResultUtil.createQuotedSSID(approvedScanResult.SSID))) {
- Log.v(TAG, "Approved access point found in matching scan results. "
- + "Triggering connect " + approvedScanResult);
- handleConnectToNetworkUserSelectionInternal(
- ScanResultUtil.createNetworkFromScanResult(approvedScanResult));
- mWifiMetrics.incrementNetworkRequestApiNumUserApprovalBypass();
- // TODO (b/122658039): Post notification.
- } else {
- if (mVerboseLoggingEnabled) {
- Log.v(TAG, "No approved access points found in matching scan results. "
- + "Sending match callback");
- }
- sendNetworkRequestMatchCallbacksForActiveRequest(matchedScanResults);
- // Didn't find an approved match, schedule the next scan.
+ if (!handleScanResultsAndTriggerConnectIfUserApprovedMatchFound(scanResults)) {
+ // Didn't find an approved match, send the matching results to UI and schedule the
+ // next scan.
+ sendNetworkRequestMatchCallbacksForActiveRequest(mActiveMatchedScanResults);
scheduleNextPeriodicScan();
}
}
@@ -612,10 +587,19 @@ public class WifiNetworkFactory extends NetworkFactory {
wns.requestorUid, wns.requestorPackageName);
mWifiMetrics.incrementNetworkRequestApiNumRequest();
- // Start UI to let the user grant/disallow this request from the app.
- startUi();
- // Trigger periodic scans for finding a network in the request.
- startPeriodicScans();
+ // Fetch the latest cached scan results to speed up network matching.
+ ScanResult[] cachedScanResults = getFilteredCachedScanResults();
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Using cached " + cachedScanResults.length + " scan results");
+ }
+ if (!handleScanResultsAndTriggerConnectIfUserApprovedMatchFound(cachedScanResults)) {
+ // Start UI to let the user grant/disallow this request from the app.
+ startUi();
+ // Didn't find an approved match, send the matching results to UI and trigger
+ // periodic scans for finding a network in the request.
+ sendNetworkRequestMatchCallbacksForActiveRequest(mActiveMatchedScanResults);
+ startPeriodicScans();
+ }
}
}
@@ -1331,6 +1315,65 @@ public class WifiNetworkFactory extends NetworkFactory {
}
/**
+ * Handle scan results
+ * a) Find all scan results matching the active network request.
+ * b) If the request is for a single bssid, check if the matching ScanResult was pre-approved
+ * by the user.
+ * c) If yes to (b), trigger a connect immediately and returns true. Else, returns false.
+ *
+ * @param scanResults Array of {@link ScanResult} to be processed.
+ * @return true if a pre-approved network was found for connection, false otherwise.
+ */
+ private boolean handleScanResultsAndTriggerConnectIfUserApprovedMatchFound(
+ ScanResult[] scanResults) {
+ List<ScanResult> matchedScanResults =
+ getNetworksMatchingActiveNetworkRequest(scanResults);
+ if ((mActiveMatchedScanResults == null || mActiveMatchedScanResults.isEmpty())
+ && !matchedScanResults.isEmpty()) {
+ // only note the first match size in metrics (chances of this changing in further
+ // scans is pretty low)
+ mWifiMetrics.incrementNetworkRequestApiMatchSizeHistogram(
+ matchedScanResults.size());
+ }
+ mActiveMatchedScanResults = matchedScanResults;
+
+ ScanResult approvedScanResult = null;
+ if (isActiveRequestForSingleAccessPoint()) {
+ approvedScanResult =
+ findUserApprovedAccessPointForActiveRequestFromActiveMatchedScanResults();
+ }
+ if (approvedScanResult != null
+ && !mWifiConfigManager.wasEphemeralNetworkDeleted(
+ ScanResultUtil.createQuotedSSID(approvedScanResult.SSID))) {
+ Log.v(TAG, "Approved access point found in matching scan results. "
+ + "Triggering connect " + approvedScanResult);
+ handleConnectToNetworkUserSelectionInternal(
+ ScanResultUtil.createNetworkFromScanResult(approvedScanResult));
+ mWifiMetrics.incrementNetworkRequestApiNumUserApprovalBypass();
+ return true;
+ }
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "No approved access points found in matching scan results");
+ }
+ return false;
+ }
+
+ /**
+ * Retrieve the latest cached scan results from wifi scanner and filter out any
+ * {@link ScanResult} older than {@link #CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS}.
+ */
+ private @NonNull ScanResult[] getFilteredCachedScanResults() {
+ List<ScanResult> cachedScanResults = mWifiScanner.getSingleScanResults();
+ if (cachedScanResults == null || cachedScanResults.isEmpty()) return new ScanResult[0];
+ long currentTimeInMillis = mClock.getElapsedSinceBootMillis();
+ return cachedScanResults.stream()
+ .filter(scanResult
+ -> ((currentTimeInMillis - (scanResult.timestamp / 1000))
+ < CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS))
+ .toArray(ScanResult[]::new);
+ }
+
+ /**
* Clean up least recently used Access Points if specified app reach the limit.
*/
private static void cleanUpLRUAccessPoints(Set<AccessPoint> approvedAccessPoints) {
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java
index 6a4dc25d8..36b820290 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java
@@ -84,6 +84,8 @@ import org.xmlpull.v1.XmlSerializer;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
@@ -184,6 +186,7 @@ public class WifiNetworkFactoryTest {
when(mWifiInjector.getClientModeImpl()).thenReturn(mClientModeImpl);
when(mWifiConfigManager.addOrUpdateNetwork(any(), anyInt(), anyString()))
.thenReturn(new NetworkUpdateResult(TEST_NETWORK_ID_1));
+ when(mWifiScanner.getSingleScanResults()).thenReturn(Collections.emptyList());
mWifiNetworkFactory = new WifiNetworkFactory(mLooper.getLooper(), mContext,
mNetworkCapabilities, mActivityManager, mAlarmManager, mAppOpsManager, mClock,
@@ -959,8 +962,8 @@ public class WifiNetworkFactoryTest {
// We expect no network match in this case.
assertEquals(0, matchedScanResultsCaptor.getValue().size());
- verify(mWifiMetrics).incrementNetworkRequestApiMatchSizeHistogram(
- matchedScanResultsCaptor.getValue().size());
+ // Don't increment metrics until we have a match
+ verify(mWifiMetrics, never()).incrementNetworkRequestApiMatchSizeHistogram(anyInt());
}
/**
@@ -1684,6 +1687,7 @@ public class WifiNetworkFactoryTest {
mLooper.dispatchAll();
verify(mNetworkRequestMatchCallback).onAbort();
+ verify(mWifiScanner, times(2)).getSingleScanResults();
verify(mWifiScanner, times(2)).startScan(any(), any(), any());
verifyUnfullfillableDispatched(mConnectivityMessenger);
@@ -1725,6 +1729,7 @@ public class WifiNetworkFactoryTest {
mLooper.dispatchAll();
verify(mNetworkRequestMatchCallback).onAbort();
+ verify(mWifiScanner, times(2)).getSingleScanResults();
verify(mWifiScanner, times(2)).startScan(any(), any(), any());
verify(mAlarmManager).cancel(mPeriodicScanListenerArgumentCaptor.getValue());
verifyUnfullfillableDispatched(mConnectivityMessenger);
@@ -1759,6 +1764,7 @@ public class WifiNetworkFactoryTest {
verify(mNetworkRequestMatchCallback).onAbort();
verify(mWifiConnectivityManager, times(1)).setSpecificNetworkRequestInProgress(true);
+ verify(mWifiScanner, times(2)).getSingleScanResults();
verify(mWifiScanner, times(2)).startScan(any(), any(), any());
verify(mAlarmManager).cancel(mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
@@ -1798,6 +1804,7 @@ public class WifiNetworkFactoryTest {
mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0);
verify(mWifiConnectivityManager, times(1)).setSpecificNetworkRequestInProgress(true);
+ verify(mWifiScanner, times(2)).getSingleScanResults();
verify(mWifiScanner, times(2)).startScan(any(), any(), any());
// we shouldn't disconnect until the user accepts the next request.
verify(mClientModeImpl, times(1)).disconnectCommand();
@@ -2090,10 +2097,10 @@ public class WifiNetworkFactoryTest {
/**
* Verify the user approval bypass for a specific request for an access point that was already
- * approved previously.
+ * approved previously with no cached scan results matching.
*/
@Test
- public void testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchPreviouslyApproved()
+ public void testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchApprovedWithNoCache()
throws Exception {
// 1. First request (no user approval bypass)
sendNetworkRequestAndSetupForConnectionStatus();
@@ -2113,6 +2120,9 @@ public class WifiNetworkFactoryTest {
WifiConfigurationTestUtil.createPskNetwork(), TEST_UID_1, TEST_PACKAGE_NAME_1);
mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier);
mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0);
+
+ validateUiStartParams(true);
+
mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
TEST_CALLBACK_IDENTIFIER);
// Trigger scan results & ensure we triggered a connect.
@@ -2134,8 +2144,7 @@ public class WifiNetworkFactoryTest {
* approved previously, but then the user forgot it sometime after.
*/
@Test
- public void
- testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchPreviouslyApprovedNForgot()
+ public void testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchApprovedNForgot()
throws Exception {
// 1. First request (no user approval bypass)
sendNetworkRequestAndSetupForConnectionStatus();
@@ -2179,7 +2188,7 @@ public class WifiNetworkFactoryTest {
* not approved previously.
*/
@Test
- public void testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchNotPreviouslyApproved()
+ public void testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchNotApproved()
throws Exception {
// 1. First request (no user approval bypass)
sendNetworkRequestAndSetupForConnectionStatus();
@@ -2220,7 +2229,7 @@ public class WifiNetworkFactoryTest {
* (not access point) that was approved previously.
*/
@Test
- public void testNetworkSpecifierMatchSuccessUsingLiteralSsidMatchPreviouslyApproved()
+ public void testNetworkSpecifierMatchSuccessUsingLiteralSsidMatchApproved()
throws Exception {
// 1. First request (no user approval bypass)
sendNetworkRequestAndSetupForConnectionStatus();
@@ -2412,7 +2421,7 @@ public class WifiNetworkFactoryTest {
* Verify the config store save and load could preserve the elements order.
*/
@Test
- public void testStoteConfigSaveAndLoadPreserveOrder() throws Exception {
+ public void testStoreConfigSaveAndLoadPreserveOrder() throws Exception {
LinkedHashSet<AccessPoint> approvedApSet = new LinkedHashSet<>();
approvedApSet.add(new AccessPoint(TEST_SSID_1,
MacAddress.fromString(TEST_BSSID_1), WifiConfiguration.SECURITY_TYPE_PSK));
@@ -2437,6 +2446,112 @@ public class WifiNetworkFactoryTest {
assertArrayEquals(approvedApSet.toArray(), storedApSet.toArray());
}
+ /**
+ * Verify the user approval bypass for a specific request for an access point that was already
+ * approved previously and the scan result is present in the cached scan results.
+ */
+ @Test
+ public void testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchApprovedWithCache()
+ throws Exception {
+ // 1. First request (no user approval bypass)
+ sendNetworkRequestAndSetupForConnectionStatus();
+
+ mWifiNetworkFactory.removeCallback(TEST_CALLBACK_IDENTIFIER);
+ reset(mNetworkRequestMatchCallback, mWifiScanner, mAlarmManager, mClientModeImpl);
+
+ // 2. Second request for the same access point (user approval bypass).
+ ScanResult matchingScanResult = mTestScanDatas[0].getResults()[0];
+ // simulate no cache expiry
+ when(mClock.getElapsedSinceBootMillis()).thenReturn(0L);
+ // Simulate the cached results matching.
+ when(mWifiScanner.getSingleScanResults())
+ .thenReturn(Arrays.asList(mTestScanDatas[0].getResults()));
+
+ PatternMatcher ssidPatternMatch =
+ new PatternMatcher(TEST_SSID_1, PatternMatcher.PATTERN_LITERAL);
+ Pair<MacAddress, MacAddress> bssidPatternMatch =
+ Pair.create(MacAddress.fromString(matchingScanResult.BSSID),
+ MacAddress.BROADCAST_ADDRESS);
+ WifiNetworkSpecifier specifier = new WifiNetworkSpecifier(
+ ssidPatternMatch, bssidPatternMatch,
+ WifiConfigurationTestUtil.createPskNetwork(), TEST_UID_1, TEST_PACKAGE_NAME_1);
+ mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier);
+ mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0);
+
+ // Verify we did not trigger the UI for the second request.
+ verify(mContext, times(1)).startActivityAsUser(any(), any());
+ // Verify we did not trigger a scan.
+ verify(mWifiScanner, never()).startScan(any(), any(), any());
+ // Verify we did not trigger the match callback.
+ verify(mNetworkRequestMatchCallback, never()).onMatch(anyList());
+ // Verify that we sent a connection attempt to ClientModeImpl
+ verify(mClientModeImpl).sendMessage(any());
+
+ verify(mWifiMetrics).incrementNetworkRequestApiNumUserApprovalBypass();
+ }
+
+ /**
+ * Verify the user approval bypass for a specific request for an access point that was already
+ * approved previously and the scan result is present in the cached scan results, but the
+ * results are stale.
+ */
+ @Test
+ public void
+ testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchApprovedWithStaleCache()
+ throws Exception {
+ // 1. First request (no user approval bypass)
+ sendNetworkRequestAndSetupForConnectionStatus();
+
+ mWifiNetworkFactory.removeCallback(TEST_CALLBACK_IDENTIFIER);
+ reset(mNetworkRequestMatchCallback, mWifiScanner, mAlarmManager, mClientModeImpl);
+
+ long scanResultsTimestampInUs = 39484839202L;
+ mTestScanDatas[0].getResults()[0].timestamp = scanResultsTimestampInUs;
+ mTestScanDatas[0].getResults()[1].timestamp = scanResultsTimestampInUs;
+ mTestScanDatas[0].getResults()[2].timestamp = scanResultsTimestampInUs;
+ mTestScanDatas[0].getResults()[3].timestamp = scanResultsTimestampInUs;
+
+ // 2. Second request for the same access point (user approval bypass).
+ ScanResult matchingScanResult = mTestScanDatas[0].getResults()[0];
+ // simulate cache expiry
+ when(mClock.getElapsedSinceBootMillis())
+ .thenReturn(Long.valueOf(
+ scanResultsTimestampInUs / 1000
+ + WifiNetworkFactory.CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS + 1));
+ // Simulate the cached results matching.
+ when(mWifiScanner.getSingleScanResults())
+ .thenReturn(Arrays.asList(mTestScanDatas[0].getResults()));
+
+ PatternMatcher ssidPatternMatch =
+ new PatternMatcher(TEST_SSID_1, PatternMatcher.PATTERN_LITERAL);
+ Pair<MacAddress, MacAddress> bssidPatternMatch =
+ Pair.create(MacAddress.fromString(matchingScanResult.BSSID),
+ MacAddress.BROADCAST_ADDRESS);
+ WifiNetworkSpecifier specifier = new WifiNetworkSpecifier(
+ ssidPatternMatch, bssidPatternMatch,
+ WifiConfigurationTestUtil.createPskNetwork(), TEST_UID_1, TEST_PACKAGE_NAME_1);
+ mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier);
+ mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0);
+
+ // Ensure we brought up the UI while the scan is ongoing.
+ validateUiStartParams(true);
+
+ mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
+ TEST_CALLBACK_IDENTIFIER);
+ // Trigger scan results & ensure we triggered a connect.
+ verify(mWifiScanner).startScan(any(), mScanListenerArgumentCaptor.capture(), any());
+ ScanListener scanListener = mScanListenerArgumentCaptor.getValue();
+ assertNotNull(scanListener);
+ scanListener.onResults(mTestScanDatas);
+
+ // Verify we did not trigger the match callback.
+ verify(mNetworkRequestMatchCallback, never()).onMatch(anyList());
+ // Verify that we sent a connection attempt to ClientModeImpl
+ verify(mClientModeImpl).sendMessage(any());
+
+ verify(mWifiMetrics).incrementNetworkRequestApiNumUserApprovalBypass();
+ }
+
private Messenger sendNetworkRequestAndSetupForConnectionStatus() throws RemoteException {
return sendNetworkRequestAndSetupForConnectionStatus(TEST_SSID_1);
}
@@ -2506,6 +2621,8 @@ public class WifiNetworkFactoryTest {
mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier);
mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0);
+ validateUiStartParams(true);
+
mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
TEST_CALLBACK_IDENTIFIER);
verify(mNetworkRequestMatchCallback).onUserSelectionCallbackRegistration(
@@ -2513,7 +2630,7 @@ public class WifiNetworkFactoryTest {
verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS);
- verify(mNetworkRequestMatchCallback).onMatch(anyList());
+ verify(mNetworkRequestMatchCallback, atLeastOnce()).onMatch(anyList());
}
// Simulates the periodic scans performed to find a matching network.
@@ -2528,6 +2645,10 @@ public class WifiNetworkFactoryTest {
ScanListener scanListener = null;
mInOrder = inOrder(mWifiScanner, mAlarmManager);
+
+ // Before we start scans, ensure that we look at the latest cached scan results.
+ mInOrder.verify(mWifiScanner).getSingleScanResults();
+
for (int i = 0; i < expectedIntervalsInSeconds.length - 1; i++) {
long expectedCurrentIntervalInMs = expectedIntervalsInSeconds[i];
long expectedNextIntervalInMs = expectedIntervalsInSeconds[i + 1];
@@ -2705,7 +2826,7 @@ public class WifiNetworkFactoryTest {
private void validateUiStartParams(boolean expectedIsReqForSingeNetwork) {
ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
- verify(mContext).startActivityAsUser(
+ verify(mContext, atLeastOnce()).startActivityAsUser(
intentArgumentCaptor.capture(), eq(UserHandle.getUserHandleForUid(TEST_UID_1)));
Intent intent = intentArgumentCaptor.getValue();
assertNotNull(intent);