diff options
-rw-r--r-- | service/java/com/android/server/wifi/WifiNetworkFactory.java | 113 | ||||
-rw-r--r-- | tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java | 143 |
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); |