blob: bdb6b14edcf903215d0405cc49766ede3c854925 [file] [log] [blame]
/*
* Copyright (C) 2016 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.server.wifi;
import static com.android.server.wifi.WifiConfigurationTestUtil.generateWifiConfig;
import static com.android.server.wifi.WifiStateMachine.WIFI_WORK_SOURCE;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import android.app.test.MockAnswerUtil.AnswerWithArguments;
import android.app.test.TestAlarmManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.NetworkScoreManager;
import android.net.wifi.ScanResult;
import android.net.wifi.ScanResult.InformationElement;
import android.net.wifi.SupplicantState;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiNetworkScoreCache;
import android.net.wifi.WifiScanner;
import android.net.wifi.WifiScanner.PnoScanListener;
import android.net.wifi.WifiScanner.PnoSettings;
import android.net.wifi.WifiScanner.ScanData;
import android.net.wifi.WifiScanner.ScanListener;
import android.net.wifi.WifiScanner.ScanSettings;
import android.net.wifi.WifiSsid;
import android.os.SystemClock;
import android.os.WorkSource;
import android.os.test.TestLooper;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.LocalLog;
import com.android.internal.R;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
/**
* Unit tests for {@link com.android.server.wifi.WifiConnectivityManager}.
*/
@SmallTest
public class WifiConnectivityManagerTest {
/**
* Called before each test
*/
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mResource = mockResource();
mAlarmManager = new TestAlarmManager();
mContext = mockContext();
mLocalLog = new LocalLog(512);
mWifiStateMachine = mockWifiStateMachine();
mWifiConfigManager = mockWifiConfigManager();
mWifiInfo = getWifiInfo();
mScanData = mockScanData();
mWifiScanner = mockWifiScanner();
mWifiConnectivityHelper = mockWifiConnectivityHelper();
mWifiNS = mockWifiNetworkSelector();
mWifiConnectivityManager = createConnectivityManager();
verify(mWifiConfigManager).setOnSavedNetworkUpdateListener(anyObject());
mWifiConnectivityManager.setWifiEnabled(true);
when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime());
mFullScanMaxTxPacketRate = mResource.getInteger(
R.integer.config_wifi_framework_max_tx_rate_for_full_scan);
mFullScanMaxRxPacketRate = mResource.getInteger(
R.integer.config_wifi_framework_max_rx_rate_for_full_scan);
}
/**
* Called after each test
*/
@After
public void cleanup() {
validateMockitoUsage();
}
private Resources mResource;
private Context mContext;
private TestAlarmManager mAlarmManager;
private TestLooper mLooper = new TestLooper();
private WifiConnectivityManager mWifiConnectivityManager;
private WifiNetworkSelector mWifiNS;
private WifiStateMachine mWifiStateMachine;
private WifiScanner mWifiScanner;
private WifiConnectivityHelper mWifiConnectivityHelper;
private ScanData mScanData;
private WifiConfigManager mWifiConfigManager;
private WifiInfo mWifiInfo;
private LocalLog mLocalLog;
@Mock private FrameworkFacade mFrameworkFacade;
@Mock private NetworkScoreManager mNetworkScoreManager;
@Mock private Clock mClock;
@Mock private WifiLastResortWatchdog mWifiLastResortWatchdog;
@Mock private WifiNotificationController mWifiNotificationController;
@Mock private WifiMetrics mWifiMetrics;
@Mock private WifiNetworkScoreCache mScoreCache;
@Captor ArgumentCaptor<ScanResult> mCandidateScanResultCaptor;
@Captor ArgumentCaptor<ArrayList<String>> mBssidBlacklistCaptor;
@Captor ArgumentCaptor<ArrayList<String>> mSsidWhitelistCaptor;
private MockResources mResources;
private int mFullScanMaxTxPacketRate;
private int mFullScanMaxRxPacketRate;
private static final int CANDIDATE_NETWORK_ID = 0;
private static final String CANDIDATE_SSID = "\"AnSsid\"";
private static final String CANDIDATE_BSSID = "6c:f3:7f:ae:8c:f3";
private static final String INVALID_SCAN_RESULT_BSSID = "6c:f3:7f:ae:8c:f4";
private static final long CURRENT_SYSTEM_TIME_MS = 1000;
private static final int MAX_BSSID_BLACKLIST_SIZE = 16;
Resources mockResource() {
Resources resource = mock(Resources.class);
when(resource.getInteger(R.integer.config_wifi_framework_SECURITY_AWARD)).thenReturn(80);
when(resource.getInteger(R.integer.config_wifi_framework_SAME_BSSID_AWARD)).thenReturn(24);
when(resource.getBoolean(
R.bool.config_wifi_framework_enable_associated_network_selection)).thenReturn(true);
when(resource.getInteger(
R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz))
.thenReturn(-60);
when(resource.getInteger(
R.integer.config_wifi_framework_current_network_boost)).thenReturn(16);
when(resource.getInteger(
R.integer.config_wifi_framework_max_tx_rate_for_full_scan)).thenReturn(8);
when(resource.getInteger(
R.integer.config_wifi_framework_max_rx_rate_for_full_scan)).thenReturn(16);
return resource;
}
Context mockContext() {
Context context = mock(Context.class);
when(context.getResources()).thenReturn(mResource);
when(context.getSystemService(Context.ALARM_SERVICE)).thenReturn(
mAlarmManager.getAlarmManager());
when(context.getPackageManager()).thenReturn(mock(PackageManager.class));
return context;
}
ScanData mockScanData() {
ScanData scanData = mock(ScanData.class);
when(scanData.isAllChannelsScanned()).thenReturn(true);
return scanData;
}
WifiScanner mockWifiScanner() {
WifiScanner scanner = mock(WifiScanner.class);
ArgumentCaptor<ScanListener> allSingleScanListenerCaptor =
ArgumentCaptor.forClass(ScanListener.class);
doNothing().when(scanner).registerScanListener(allSingleScanListenerCaptor.capture());
ScanData[] scanDatas = new ScanData[1];
scanDatas[0] = mScanData;
// do a synchronous answer for the ScanListener callbacks
doAnswer(new AnswerWithArguments() {
public void answer(ScanSettings settings, ScanListener listener,
WorkSource workSource) throws Exception {
listener.onResults(scanDatas);
}}).when(scanner).startBackgroundScan(anyObject(), anyObject(), anyObject());
doAnswer(new AnswerWithArguments() {
public void answer(ScanSettings settings, ScanListener listener,
WorkSource workSource) throws Exception {
listener.onResults(scanDatas);
allSingleScanListenerCaptor.getValue().onResults(scanDatas);
}}).when(scanner).startScan(anyObject(), anyObject(), anyObject());
// This unfortunately needs to be a somewhat valid scan result, otherwise
// |ScanDetailUtil.toScanDetail| raises exceptions.
final ScanResult[] scanResults = new ScanResult[1];
scanResults[0] = new ScanResult(WifiSsid.createFromAsciiEncoded(CANDIDATE_SSID),
CANDIDATE_SSID, CANDIDATE_BSSID, 1245, 0, "some caps",
-78, 2450, 1025, 22, 33, 20, 0, 0, true);
scanResults[0].informationElements = new InformationElement[1];
scanResults[0].informationElements[0] = new InformationElement();
scanResults[0].informationElements[0].id = InformationElement.EID_SSID;
scanResults[0].informationElements[0].bytes =
CANDIDATE_SSID.getBytes(StandardCharsets.UTF_8);
doAnswer(new AnswerWithArguments() {
public void answer(ScanSettings settings, PnoSettings pnoSettings,
PnoScanListener listener) throws Exception {
listener.onPnoNetworkFound(scanResults);
}}).when(scanner).startDisconnectedPnoScan(anyObject(), anyObject(), anyObject());
doAnswer(new AnswerWithArguments() {
public void answer(ScanSettings settings, PnoSettings pnoSettings,
PnoScanListener listener) throws Exception {
listener.onPnoNetworkFound(scanResults);
}}).when(scanner).startConnectedPnoScan(anyObject(), anyObject(), anyObject());
return scanner;
}
WifiConnectivityHelper mockWifiConnectivityHelper() {
WifiConnectivityHelper connectivityHelper = mock(WifiConnectivityHelper.class);
when(connectivityHelper.isFirmwareRoamingSupported()).thenReturn(false);
when(connectivityHelper.getMaxNumBlacklistBssid()).thenReturn(MAX_BSSID_BLACKLIST_SIZE);
return connectivityHelper;
}
WifiStateMachine mockWifiStateMachine() {
WifiStateMachine stateMachine = mock(WifiStateMachine.class);
when(stateMachine.isLinkDebouncing()).thenReturn(false);
when(stateMachine.isConnected()).thenReturn(false);
when(stateMachine.isDisconnected()).thenReturn(true);
when(stateMachine.isSupplicantTransientState()).thenReturn(false);
return stateMachine;
}
WifiNetworkSelector mockWifiNetworkSelector() {
WifiNetworkSelector ns = mock(WifiNetworkSelector.class);
WifiConfiguration candidate = generateWifiConfig(
0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
candidate.BSSID = WifiStateMachine.SUPPLICANT_BSSID_ANY;
ScanResult candidateScanResult = new ScanResult();
candidateScanResult.SSID = CANDIDATE_SSID;
candidateScanResult.BSSID = CANDIDATE_BSSID;
candidate.getNetworkSelectionStatus().setCandidate(candidateScanResult);
when(ns.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
anyBoolean(), anyBoolean())).thenReturn(candidate);
return ns;
}
WifiInfo getWifiInfo() {
WifiInfo wifiInfo = new WifiInfo();
wifiInfo.setNetworkId(WifiConfiguration.INVALID_NETWORK_ID);
wifiInfo.setBSSID(null);
wifiInfo.setSupplicantState(SupplicantState.DISCONNECTED);
return wifiInfo;
}
WifiConfigManager mockWifiConfigManager() {
WifiConfigManager wifiConfigManager = mock(WifiConfigManager.class);
when(wifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(null);
// Pass dummy pno network list, otherwise Pno scan requests will not be triggered.
PnoSettings.PnoNetwork pnoNetwork = new PnoSettings.PnoNetwork(CANDIDATE_SSID);
ArrayList<PnoSettings.PnoNetwork> pnoNetworkList = new ArrayList<>();
pnoNetworkList.add(pnoNetwork);
when(wifiConfigManager.retrievePnoNetworkList()).thenReturn(pnoNetworkList);
when(wifiConfigManager.retrievePnoNetworkList()).thenReturn(pnoNetworkList);
return wifiConfigManager;
}
WifiConnectivityManager createConnectivityManager() {
return new WifiConnectivityManager(mContext, mWifiStateMachine, mWifiScanner,
mWifiConfigManager, mWifiInfo, mWifiNS, mWifiConnectivityHelper,
mWifiLastResortWatchdog, mWifiNotificationController, mWifiMetrics,
mLooper.getLooper(), mClock, mLocalLog, true, mFrameworkFacade, null, null, null);
}
/**
* Wifi enters disconnected state while screen is on.
*
* Expected behavior: WifiConnectivityManager calls
* WifiStateMachine.startConnectToNetwork() with the
* expected candidate network ID and BSSID.
*/
@Test
public void enterWifiDisconnectedStateWhenScreenOn() {
// Set screen to on
mWifiConnectivityManager.handleScreenStateChanged(true);
// Set WiFi to disconnected state
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
}
/**
* Wifi enters connected state while screen is on.
*
* Expected behavior: WifiConnectivityManager calls
* WifiStateMachine.startConnectToNetwork() with the
* expected candidate network ID and BSSID.
*/
@Test
public void enterWifiConnectedStateWhenScreenOn() {
// Set screen to on
mWifiConnectivityManager.handleScreenStateChanged(true);
// Set WiFi to connected state
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_CONNECTED);
verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
}
/**
* Screen turned on while WiFi in disconnected state.
*
* Expected behavior: WifiConnectivityManager calls
* WifiStateMachine.startConnectToNetwork() with the
* expected candidate network ID and BSSID.
*/
@Test
public void turnScreenOnWhenWifiInDisconnectedState() {
// Set WiFi to disconnected state
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
// Set screen to on
mWifiConnectivityManager.handleScreenStateChanged(true);
verify(mWifiStateMachine, atLeastOnce()).startConnectToNetwork(
CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
}
/**
* Screen turned on while WiFi in connected state.
*
* Expected behavior: WifiConnectivityManager calls
* WifiStateMachine.startConnectToNetwork() with the
* expected candidate network ID and BSSID.
*/
@Test
public void turnScreenOnWhenWifiInConnectedState() {
// Set WiFi to connected state
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_CONNECTED);
// Set screen to on
mWifiConnectivityManager.handleScreenStateChanged(true);
verify(mWifiStateMachine, atLeastOnce()).startConnectToNetwork(
CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
}
/**
* Screen turned on while WiFi in connected state but
* auto roaming is disabled.
*
* Expected behavior: WifiConnectivityManager doesn't invoke
* WifiStateMachine.startConnectToNetwork() because roaming
* is turned off.
*/
@Test
public void turnScreenOnWhenWifiInConnectedStateRoamingDisabled() {
// Turn off auto roaming
when(mResource.getBoolean(
R.bool.config_wifi_framework_enable_associated_network_selection))
.thenReturn(false);
mWifiConnectivityManager = createConnectivityManager();
// Set WiFi to connected state
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_CONNECTED);
// Set screen to on
mWifiConnectivityManager.handleScreenStateChanged(true);
verify(mWifiStateMachine, times(0)).startConnectToNetwork(
CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
}
/**
* Multiple back to back connection attempts within the rate interval should be rate limited.
*
* Expected behavior: WifiConnectivityManager calls WifiStateMachine.startConnectToNetwork()
* with the expected candidate network ID and BSSID for only the expected number of times within
* the given interval.
*/
@Test
public void connectionAttemptRateLimitedWhenScreenOff() {
int maxAttemptRate = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_RATE;
int timeInterval = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS;
int numAttempts = 0;
int connectionAttemptIntervals = timeInterval / maxAttemptRate;
mWifiConnectivityManager.handleScreenStateChanged(false);
// First attempt the max rate number of connections within the rate interval.
long currentTimeStamp = 0;
for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
currentTimeStamp += connectionAttemptIntervals;
when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
// Set WiFi to disconnected state to trigger PNO scan
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
numAttempts++;
}
// Now trigger another connection attempt before the rate interval, this should be
// skipped because we've crossed rate limit.
when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
// Set WiFi to disconnected state to trigger PNO scan
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
// Verify that we attempt to connect upto the rate.
verify(mWifiStateMachine, times(numAttempts)).startConnectToNetwork(
CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
}
/**
* Multiple back to back connection attempts outside the rate interval should not be rate
* limited.
*
* Expected behavior: WifiConnectivityManager calls WifiStateMachine.startConnectToNetwork()
* with the expected candidate network ID and BSSID for only the expected number of times within
* the given interval.
*/
@Test
public void connectionAttemptNotRateLimitedWhenScreenOff() {
int maxAttemptRate = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_RATE;
int timeInterval = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS;
int numAttempts = 0;
int connectionAttemptIntervals = timeInterval / maxAttemptRate;
mWifiConnectivityManager.handleScreenStateChanged(false);
// First attempt the max rate number of connections within the rate interval.
long currentTimeStamp = 0;
for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
currentTimeStamp += connectionAttemptIntervals;
when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
// Set WiFi to disconnected state to trigger PNO scan
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
numAttempts++;
}
// Now trigger another connection attempt after the rate interval, this should not be
// skipped because we should've evicted the older attempt.
when(mClock.getElapsedSinceBootMillis()).thenReturn(
currentTimeStamp + connectionAttemptIntervals * 2);
// Set WiFi to disconnected state to trigger PNO scan
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
numAttempts++;
// Verify that all the connection attempts went through
verify(mWifiStateMachine, times(numAttempts)).startConnectToNetwork(
CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
}
/**
* Multiple back to back connection attempts after a user selection should not be rate limited.
*
* Expected behavior: WifiConnectivityManager calls WifiStateMachine.startConnectToNetwork()
* with the expected candidate network ID and BSSID for only the expected number of times within
* the given interval.
*/
@Test
public void connectionAttemptNotRateLimitedWhenScreenOffAfterUserSelection() {
int maxAttemptRate = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_RATE;
int timeInterval = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS;
int numAttempts = 0;
int connectionAttemptIntervals = timeInterval / maxAttemptRate;
mWifiConnectivityManager.handleScreenStateChanged(false);
// First attempt the max rate number of connections within the rate interval.
long currentTimeStamp = 0;
for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
currentTimeStamp += connectionAttemptIntervals;
when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
// Set WiFi to disconnected state to trigger PNO scan
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
numAttempts++;
}
mWifiConnectivityManager.setUserConnectChoice(CANDIDATE_NETWORK_ID);
mWifiConnectivityManager.prepareForForcedConnection(CANDIDATE_NETWORK_ID);
for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
currentTimeStamp += connectionAttemptIntervals;
when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
// Set WiFi to disconnected state to trigger PNO scan
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
numAttempts++;
}
// Verify that all the connection attempts went through
verify(mWifiStateMachine, times(numAttempts)).startConnectToNetwork(
CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
}
/**
* PNO retry for low RSSI networks.
*
* Expected behavior: WifiConnectivityManager doubles the low RSSI
* network retry delay value after QNS skips the PNO scan results
* because of their low RSSI values.
*/
@Test
public void pnoRetryForLowRssiNetwork() {
when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
anyBoolean(), anyBoolean())).thenReturn(null);
// Set screen to off
mWifiConnectivityManager.handleScreenStateChanged(false);
// Get the current retry delay value
int lowRssiNetworkRetryDelayStartValue = mWifiConnectivityManager
.getLowRssiNetworkRetryDelay();
// Set WiFi to disconnected state to trigger PNO scan
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
// Get the retry delay value after QNS didn't select a
// network candicate from the PNO scan results.
int lowRssiNetworkRetryDelayAfterPnoValue = mWifiConnectivityManager
.getLowRssiNetworkRetryDelay();
assertEquals(lowRssiNetworkRetryDelayStartValue * 2,
lowRssiNetworkRetryDelayAfterPnoValue);
}
/**
* Ensure that the watchdog bite increments the "Pno bad" metric.
*
* Expected behavior: WifiConnectivityManager detects that the PNO scan failed to find
* a candidate while watchdog single scan did.
*/
@Test
public void watchdogBitePnoBadIncrementsMetrics() {
// Set screen to off
mWifiConnectivityManager.handleScreenStateChanged(false);
// Set WiFi to disconnected state to trigger PNO scan
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
// Now fire the watchdog alarm and verify the metrics were incremented.
mAlarmManager.dispatch(WifiConnectivityManager.WATCHDOG_TIMER_TAG);
mLooper.dispatchAll();
verify(mWifiMetrics).incrementNumConnectivityWatchdogPnoBad();
verify(mWifiMetrics, never()).incrementNumConnectivityWatchdogPnoGood();
}
/**
* Ensure that the watchdog bite increments the "Pno good" metric.
*
* Expected behavior: WifiConnectivityManager detects that the PNO scan failed to find
* a candidate which was the same with watchdog single scan.
*/
@Test
public void watchdogBitePnoGoodIncrementsMetrics() {
// Qns returns no candidate after watchdog single scan.
when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
anyBoolean(), anyBoolean())).thenReturn(null);
// Set screen to off
mWifiConnectivityManager.handleScreenStateChanged(false);
// Set WiFi to disconnected state to trigger PNO scan
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
// Now fire the watchdog alarm and verify the metrics were incremented.
mAlarmManager.dispatch(WifiConnectivityManager.WATCHDOG_TIMER_TAG);
mLooper.dispatchAll();
verify(mWifiMetrics).incrementNumConnectivityWatchdogPnoGood();
verify(mWifiMetrics, never()).incrementNumConnectivityWatchdogPnoBad();
}
/**
* {@link WifiNotificationController} handles scan results on network selection.
*
* Expected behavior: ONA handles scan results
*/
@Test
public void wifiDisconnected_noConnectionCandidate_openNetworkNotificationScanResultsHandled() {
// no connection candidate selected
when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
anyBoolean(), anyBoolean())).thenReturn(null);
List<ScanDetail> expectedOpenNetworks = new ArrayList<>();
expectedOpenNetworks.add(
new ScanDetail(
new ScanResult(WifiSsid.createFromAsciiEncoded(CANDIDATE_SSID),
CANDIDATE_SSID, CANDIDATE_BSSID, 1245, 0, "some caps", -78, 2450,
1025, 22, 33, 20, 0, 0, true), null));
when(mWifiNS.getFilteredScanDetailsForOpenUnsavedNetworks())
.thenReturn(expectedOpenNetworks);
// Set WiFi to disconnected state to trigger PNO scan
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
verify(mWifiNotificationController).handleScanResults(expectedOpenNetworks);
}
/**
* When wifi is connected, {@link WifiNotificationController} tries to clear the pending
* notification and does not reset notification repeat delay.
*
* Expected behavior: ONA clears pending notification and does not reset repeat delay.
*/
@Test
public void wifiConnected_openNetworkNotificationClearsPendingNotification() {
// Set WiFi to connected state
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_CONNECTED);
verify(mWifiNotificationController).clearPendingNotification(false /* isRepeatDelayReset*/);
}
/**
* When wifi is connected, {@link WifiNotificationController} handles connection state
* change.
*
* Expected behavior: ONA does not clear pending notification.
*/
@Test
public void wifiDisconnected_openNetworkNotificationDoesNotClearPendingNotification() {
// Set WiFi to disconnected state
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
verify(mWifiNotificationController, never()).clearPendingNotification(anyBoolean());
}
/**
* When Wi-Fi is disabled, clear the pending notification and reset notification repeat delay.
*
* Expected behavior: clear pending notification and reset notification repeat delay
* */
@Test
public void openNetworkNotificationControllerToggledOnWifiStateChanges() {
mWifiConnectivityManager.setWifiEnabled(false);
verify(mWifiNotificationController).clearPendingNotification(true /* isRepeatDelayReset */);
}
/**
* Verify that the ONA controller tracks screen state changes.
*/
@Test
public void openNetworkNotificationControllerTracksScreenStateChanges() {
mWifiConnectivityManager.handleScreenStateChanged(false);
verify(mWifiNotificationController).handleScreenStateChanged(false);
mWifiConnectivityManager.handleScreenStateChanged(true);
verify(mWifiNotificationController).handleScreenStateChanged(true);
}
/**
* Verify that scan interval for screen on and wifi disconnected scenario
* is in the exponential backoff fashion.
*
* Expected behavior: WifiConnectivityManager doubles periodic
* scan interval.
*/
@Test
public void checkPeriodicScanIntervalWhenDisconnected() {
long currentTimeStamp = CURRENT_SYSTEM_TIME_MS;
when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
// Set screen to ON
mWifiConnectivityManager.handleScreenStateChanged(true);
// Wait for MAX_PERIODIC_SCAN_INTERVAL_MS so that any impact triggered
// by screen state change can settle
currentTimeStamp += WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS;
when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
// Set WiFi to disconnected state to trigger periodic scan
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
// Get the first periodic scan interval
long firstIntervalMs = mAlarmManager
.getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
- currentTimeStamp;
assertEquals(firstIntervalMs, WifiConnectivityManager.PERIODIC_SCAN_INTERVAL_MS);
currentTimeStamp += firstIntervalMs;
when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
// Now fire the first periodic scan alarm timer
mAlarmManager.dispatch(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
mLooper.dispatchAll();
// Get the second periodic scan interval
long secondIntervalMs = mAlarmManager
.getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
- currentTimeStamp;
// Verify the intervals are exponential back off
assertEquals(firstIntervalMs * 2, secondIntervalMs);
currentTimeStamp += secondIntervalMs;
when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
// Make sure we eventually stay at the maximum scan interval.
long intervalMs = 0;
for (int i = 0; i < 5; i++) {
mAlarmManager.dispatch(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
mLooper.dispatchAll();
intervalMs = mAlarmManager
.getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
- currentTimeStamp;
currentTimeStamp += intervalMs;
when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
}
assertEquals(intervalMs, WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS);
}
/**
* Verify that scan interval for screen on and wifi connected scenario
* is in the exponential backoff fashion.
*
* Expected behavior: WifiConnectivityManager doubles periodic
* scan interval.
*/
@Test
public void checkPeriodicScanIntervalWhenConnected() {
long currentTimeStamp = CURRENT_SYSTEM_TIME_MS;
when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
// Set screen to ON
mWifiConnectivityManager.handleScreenStateChanged(true);
// Wait for MAX_PERIODIC_SCAN_INTERVAL_MS so that any impact triggered
// by screen state change can settle
currentTimeStamp += WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS;
when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
// Set WiFi to connected state to trigger periodic scan
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_CONNECTED);
// Get the first periodic scan interval
long firstIntervalMs = mAlarmManager
.getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
- currentTimeStamp;
assertEquals(firstIntervalMs, WifiConnectivityManager.PERIODIC_SCAN_INTERVAL_MS);
currentTimeStamp += firstIntervalMs;
when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
// Now fire the first periodic scan alarm timer
mAlarmManager.dispatch(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
mLooper.dispatchAll();
// Get the second periodic scan interval
long secondIntervalMs = mAlarmManager
.getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
- currentTimeStamp;
// Verify the intervals are exponential back off
assertEquals(firstIntervalMs * 2, secondIntervalMs);
currentTimeStamp += secondIntervalMs;
when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
// Make sure we eventually stay at the maximum scan interval.
long intervalMs = 0;
for (int i = 0; i < 5; i++) {
mAlarmManager.dispatch(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
mLooper.dispatchAll();
intervalMs = mAlarmManager
.getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
- currentTimeStamp;
currentTimeStamp += intervalMs;
when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
}
assertEquals(intervalMs, WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS);
}
/**
* When screen on trigger a disconnected state change event then a connected state
* change event back to back to verify that the minium scan interval is enforced.
*
* Expected behavior: WifiConnectivityManager start the second periodic single
* scan PERIODIC_SCAN_INTERVAL_MS after the first one.
*/
@Test
public void checkMinimumPeriodicScanIntervalWhenScreenOnAndConnected() {
long currentTimeStamp = CURRENT_SYSTEM_TIME_MS;
when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
// Set screen to ON
mWifiConnectivityManager.handleScreenStateChanged(true);
// Wait for MAX_PERIODIC_SCAN_INTERVAL_MS so that any impact triggered
// by screen state change can settle
currentTimeStamp += WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS;
long scanForDisconnectedTimeStamp = currentTimeStamp;
when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
// Set WiFi to disconnected state which triggers a scan immediately
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
verify(mWifiScanner, times(1)).startScan(anyObject(), anyObject(), anyObject());
// Set up time stamp for when entering CONNECTED state
currentTimeStamp += 2000;
when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
// Set WiFi to connected state to trigger its periodic scan
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_CONNECTED);
// The very first scan triggered for connected state is actually via the alarm timer
// and it obeys the minimum scan interval
long firstScanForConnectedTimeStamp = mAlarmManager
.getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
// Verify that the first scan for connected state is scheduled PERIODIC_SCAN_INTERVAL_MS
// after the scan for disconnected state
assertEquals(firstScanForConnectedTimeStamp, scanForDisconnectedTimeStamp
+ WifiConnectivityManager.PERIODIC_SCAN_INTERVAL_MS);
}
/**
* When screen on trigger a connected state change event then a disconnected state
* change event back to back to verify that a scan is fired immediately for the
* disconnected state change event.
*
* Expected behavior: WifiConnectivityManager directly starts the periodic immediately
* for the disconnected state change event. The second scan for disconnected state is
* via alarm timer.
*/
@Test
public void scanImmediatelyWhenScreenOnAndDisconnected() {
long currentTimeStamp = CURRENT_SYSTEM_TIME_MS;
when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
// Set screen to ON
mWifiConnectivityManager.handleScreenStateChanged(true);
// Wait for MAX_PERIODIC_SCAN_INTERVAL_MS so that any impact triggered
// by screen state change can settle
currentTimeStamp += WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS;
long scanForConnectedTimeStamp = currentTimeStamp;
when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
// Set WiFi to connected state to trigger the periodic scan
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_CONNECTED);
verify(mWifiScanner, times(1)).startScan(anyObject(), anyObject(), anyObject());
// Set up the time stamp for when entering DISCONNECTED state
currentTimeStamp += 2000;
long enteringDisconnectedStateTimeStamp = currentTimeStamp;
when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
// Set WiFi to disconnected state to trigger its periodic scan
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
// Verify the very first scan for DISCONNECTED state is fired immediately
verify(mWifiScanner, times(2)).startScan(anyObject(), anyObject(), anyObject());
long secondScanForDisconnectedTimeStamp = mAlarmManager
.getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
// Verify that the second scan is scheduled PERIODIC_SCAN_INTERVAL_MS after
// entering DISCONNECTED state.
assertEquals(secondScanForDisconnectedTimeStamp, enteringDisconnectedStateTimeStamp
+ WifiConnectivityManager.PERIODIC_SCAN_INTERVAL_MS);
}
/**
* When screen on trigger a connection state change event and a forced connectivity
* scan event back to back to verify that the minimum scan interval is not applied
* in this scenario.
*
* Expected behavior: WifiConnectivityManager starts the second periodic single
* scan immediately.
*/
@Test
public void checkMinimumPeriodicScanIntervalNotEnforced() {
long currentTimeStamp = CURRENT_SYSTEM_TIME_MS;
when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
// Set screen to ON
mWifiConnectivityManager.handleScreenStateChanged(true);
// Wait for MAX_PERIODIC_SCAN_INTERVAL_MS so that any impact triggered
// by screen state change can settle
currentTimeStamp += WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS;
long firstScanTimeStamp = currentTimeStamp;
when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
// Set WiFi to connected state to trigger the periodic scan
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_CONNECTED);
// Set the second scan attempt time stamp
currentTimeStamp += 2000;
when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
// Allow untrusted networks so WifiConnectivityManager starts a periodic scan
// immediately.
mWifiConnectivityManager.setUntrustedConnectionAllowed(true);
// Get the second periodic scan actual time stamp. Note, this scan is not
// started from the AlarmManager.
long secondScanTimeStamp = mWifiConnectivityManager.getLastPeriodicSingleScanTimeStamp();
// Verify that the second scan is fired immediately
assertEquals(secondScanTimeStamp, currentTimeStamp);
}
/**
* Verify that we perform full band scan when the currently connected network's tx/rx success
* rate is low.
*
* Expected behavior: WifiConnectivityManager does full band scan.
*/
@Test
public void checkSingleScanSettingsWhenConnectedWithLowDataRate() {
mWifiInfo.txSuccessRate = 0;
mWifiInfo.rxSuccessRate = 0;
final HashSet<Integer> channelList = new HashSet<>();
channelList.add(1);
channelList.add(2);
channelList.add(3);
when(mWifiStateMachine.getCurrentWifiConfiguration())
.thenReturn(new WifiConfiguration());
when(mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(anyInt(), anyLong(),
anyInt())).thenReturn(channelList);
doAnswer(new AnswerWithArguments() {
public void answer(ScanSettings settings, ScanListener listener,
WorkSource workSource) throws Exception {
assertEquals(settings.band, WifiScanner.WIFI_BAND_BOTH_WITH_DFS);
assertNull(settings.channels);
}}).when(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
// Set screen to ON
mWifiConnectivityManager.handleScreenStateChanged(true);
// Set WiFi to connected state to trigger periodic scan
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_CONNECTED);
verify(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
}
/**
* Verify that we perform partial scan when the currently connected network's tx/rx success
* rate is high and when the currently connected network is present in scan
* cache in WifiConfigManager.
*
* Expected behavior: WifiConnectivityManager does full band scan.
*/
@Test
public void checkSingleScanSettingsWhenConnectedWithHighDataRate() {
mWifiInfo.txSuccessRate = mFullScanMaxTxPacketRate * 2;
mWifiInfo.rxSuccessRate = mFullScanMaxRxPacketRate * 2;
final HashSet<Integer> channelList = new HashSet<>();
channelList.add(1);
channelList.add(2);
channelList.add(3);
when(mWifiStateMachine.getCurrentWifiConfiguration())
.thenReturn(new WifiConfiguration());
when(mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(anyInt(), anyLong(),
anyInt())).thenReturn(channelList);
doAnswer(new AnswerWithArguments() {
public void answer(ScanSettings settings, ScanListener listener,
WorkSource workSource) throws Exception {
assertEquals(settings.band, WifiScanner.WIFI_BAND_UNSPECIFIED);
assertEquals(settings.channels.length, channelList.size());
for (int chanIdx = 0; chanIdx < settings.channels.length; chanIdx++) {
assertTrue(channelList.contains(settings.channels[chanIdx].frequency));
}
}}).when(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
// Set screen to ON
mWifiConnectivityManager.handleScreenStateChanged(true);
// Set WiFi to connected state to trigger periodic scan
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_CONNECTED);
verify(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
}
/**
* Verify that we fall back to full band scan when the currently connected network's tx/rx
* success rate is high and the currently connected network is not present in scan cache in
* WifiConfigManager. This is simulated by returning an empty hashset in |makeChannelList|.
*
* Expected behavior: WifiConnectivityManager does full band scan.
*/
@Test
public void checkSingleScanSettingsWhenConnectedWithHighDataRateNotInCache() {
mWifiInfo.txSuccessRate = mFullScanMaxTxPacketRate * 2;
mWifiInfo.rxSuccessRate = mFullScanMaxRxPacketRate * 2;
final HashSet<Integer> channelList = new HashSet<>();
when(mWifiStateMachine.getCurrentWifiConfiguration())
.thenReturn(new WifiConfiguration());
when(mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(anyInt(), anyLong(),
anyInt())).thenReturn(channelList);
doAnswer(new AnswerWithArguments() {
public void answer(ScanSettings settings, ScanListener listener,
WorkSource workSource) throws Exception {
assertEquals(settings.band, WifiScanner.WIFI_BAND_BOTH_WITH_DFS);
assertNull(settings.channels);
}}).when(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
// Set screen to ON
mWifiConnectivityManager.handleScreenStateChanged(true);
// Set WiFi to connected state to trigger periodic scan
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_CONNECTED);
verify(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
}
/**
* Verify that we retry connectivity scan up to MAX_SCAN_RESTART_ALLOWED times
* when Wifi somehow gets into a bad state and fails to scan.
*
* Expected behavior: WifiConnectivityManager schedules connectivity scan
* MAX_SCAN_RESTART_ALLOWED times.
*/
@Test
public void checkMaximumScanRetry() {
// Set screen to ON
mWifiConnectivityManager.handleScreenStateChanged(true);
doAnswer(new AnswerWithArguments() {
public void answer(ScanSettings settings, ScanListener listener,
WorkSource workSource) throws Exception {
listener.onFailure(-1, "ScanFailure");
}}).when(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
// Set WiFi to disconnected state to trigger the single scan based periodic scan
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
// Fire the alarm timer 2x timers
for (int i = 0; i < (WifiConnectivityManager.MAX_SCAN_RESTART_ALLOWED * 2); i++) {
mAlarmManager.dispatch(WifiConnectivityManager.RESTART_SINGLE_SCAN_TIMER_TAG);
mLooper.dispatchAll();
}
// Verify that the connectivity scan has been retried for MAX_SCAN_RESTART_ALLOWED
// times. Note, WifiScanner.startScan() is invoked MAX_SCAN_RESTART_ALLOWED + 1 times.
// The very first scan is the initial one, and the other MAX_SCAN_RESTART_ALLOWED
// are the retrial ones.
verify(mWifiScanner, times(WifiConnectivityManager.MAX_SCAN_RESTART_ALLOWED + 1)).startScan(
anyObject(), anyObject(), anyObject());
}
/**
* Listen to scan results not requested by WifiConnectivityManager and
* act on them.
*
* Expected behavior: WifiConnectivityManager calls
* WifiStateMachine.startConnectToNetwork() with the
* expected candidate network ID and BSSID.
*/
@Test
public void listenToAllSingleScanResults() {
ScanSettings settings = new ScanSettings();
ScanListener scanListener = mock(ScanListener.class);
// Request a single scan outside of WifiConnectivityManager.
mWifiScanner.startScan(settings, scanListener, WIFI_WORK_SOURCE);
// Verify that WCM receives the scan results and initiates a connection
// to the network.
verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
}
/**
* Verify that a forced connectivity scan waits for full band scan
* results.
*
* Expected behavior: WifiConnectivityManager doesn't invoke
* WifiStateMachine.startConnectToNetwork() when full band scan
* results are not available.
*/
@Test
public void waitForFullBandScanResults() {
// Set WiFi to connected state.
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_CONNECTED);
// Set up as partial scan results.
when(mScanData.isAllChannelsScanned()).thenReturn(false);
// Force a connectivity scan which enables WifiConnectivityManager
// to wait for full band scan results.
mWifiConnectivityManager.forceConnectivityScan();
// No roaming because no full band scan results.
verify(mWifiStateMachine, times(0)).startConnectToNetwork(
CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
// Set up as full band scan results.
when(mScanData.isAllChannelsScanned()).thenReturn(true);
// Force a connectivity scan which enables WifiConnectivityManager
// to wait for full band scan results.
mWifiConnectivityManager.forceConnectivityScan();
// Roaming attempt because full band scan results are available.
verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
}
/**
* Verify the BSSID blacklist implementation.
*
* Expected behavior: A BSSID gets blacklisted after being disabled
* for 3 times, and becomes available after being re-enabled. Firmware
* controlled roaming is supported, its roaming configuration needs to be
* updated as well.
*/
@Test
public void blacklistAndReenableBssid() {
String bssid = "6c:f3:7f:ae:8c:f3";
when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
// Verify that a BSSID gets blacklisted only after being disabled
// for BSSID_BLACKLIST_THRESHOLD times for reasons other than
// REASON_CODE_AP_UNABLE_TO_HANDLE_NEW_STA.
for (int i = 0; i < WifiConnectivityManager.BSSID_BLACKLIST_THRESHOLD; i++) {
assertFalse(mWifiConnectivityManager.isBssidDisabled(bssid));
mWifiConnectivityManager.trackBssid(bssid, false, 1);
}
// Verify the BSSID is now blacklisted.
assertTrue(mWifiConnectivityManager.isBssidDisabled(bssid));
// Verify the BSSID gets sent to firmware.
verify(mWifiConnectivityHelper).setFirmwareRoamingConfiguration(
mBssidBlacklistCaptor.capture(), mSsidWhitelistCaptor.capture());
assertTrue(mBssidBlacklistCaptor.getValue().contains(bssid));
assertTrue(mSsidWhitelistCaptor.getValue().isEmpty());
// Re-enable the bssid.
mWifiConnectivityManager.trackBssid(bssid, true, 1);
// Verify the bssid is no longer blacklisted.
assertFalse(mWifiConnectivityManager.isBssidDisabled(bssid));
// Verify the BSSID gets cleared from firmware.
verify(mWifiConnectivityHelper, times(2)).setFirmwareRoamingConfiguration(
mBssidBlacklistCaptor.capture(), mSsidWhitelistCaptor.capture());
assertFalse(mBssidBlacklistCaptor.getValue().contains(bssid));
assertTrue(mSsidWhitelistCaptor.getValue().isEmpty());
}
/**
* Verify that a network gets blacklisted immediately if it is unable
* to handle new stations.
*/
@Test
public void blacklistNetworkImmediatelyIfApHasNoCapacityForNewStation() {
String bssid = "6c:f3:7f:ae:8c:f3";
when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
// Blacklist the BSSID
mWifiConnectivityManager.trackBssid(bssid, false,
WifiConnectivityManager.REASON_CODE_AP_UNABLE_TO_HANDLE_NEW_STA);
// Verify the BSSID is now blacklisted.
assertTrue(mWifiConnectivityManager.isBssidDisabled(bssid));
// Verify the BSSID gets sent to firmware.
verify(mWifiConnectivityHelper).setFirmwareRoamingConfiguration(
mBssidBlacklistCaptor.capture(), mSsidWhitelistCaptor.capture());
assertTrue(mBssidBlacklistCaptor.getValue().contains(bssid));
assertTrue(mSsidWhitelistCaptor.getValue().isEmpty());
}
/**
* Verify that a blacklisted BSSID becomes available only after
* BSSID_BLACKLIST_EXPIRE_TIME_MS.
*/
@Test
public void verifyBlacklistRefreshedAfterScanResults() {
String bssid = "6c:f3:7f:ae:8c:f3";
when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
// Blacklist the BSSID.
mWifiConnectivityManager.trackBssid(bssid, false,
WifiConnectivityManager.REASON_CODE_AP_UNABLE_TO_HANDLE_NEW_STA);
// Verify the BSSID is now blacklisted.
assertTrue(mWifiConnectivityManager.isBssidDisabled(bssid));
// Verify the BSSID gets sent to firmware.
verify(mWifiConnectivityHelper).setFirmwareRoamingConfiguration(
mBssidBlacklistCaptor.capture(), mSsidWhitelistCaptor.capture());
assertTrue(mBssidBlacklistCaptor.getValue().contains(bssid));
assertTrue(mSsidWhitelistCaptor.getValue().isEmpty());
// Force a connectivity scan in less than BSSID_BLACKLIST_EXPIRE_TIME_MS.
// Arrival of scan results will trigger WifiConnectivityManager to refresh its
// BSSID blacklist. Verify that the blacklisted BSSId is not freed because
// its blacklist expiration time hasn't reached yet.
when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
+ WifiConnectivityManager.BSSID_BLACKLIST_EXPIRE_TIME_MS / 2);
mWifiConnectivityManager.forceConnectivityScan();
assertTrue(mWifiConnectivityManager.isBssidDisabled(bssid));
// Force another connectivity scan at BSSID_BLACKLIST_EXPIRE_TIME_MS from when the
// BSSID was blacklisted. Verify that the blacklisted BSSId is freed.
when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
+ WifiConnectivityManager.BSSID_BLACKLIST_EXPIRE_TIME_MS);
mWifiConnectivityManager.forceConnectivityScan();
// Verify the BSSID is no longer blacklisted.
assertFalse(mWifiConnectivityManager.isBssidDisabled(bssid));
// Verify the BSSID gets cleared from firmware.
verify(mWifiConnectivityHelper, times(2)).setFirmwareRoamingConfiguration(
mBssidBlacklistCaptor.capture(), mSsidWhitelistCaptor.capture());
assertFalse(mBssidBlacklistCaptor.getValue().contains(bssid));
assertTrue(mSsidWhitelistCaptor.getValue().isEmpty());
}
/**
* Verify that BSSID blacklist gets cleared when exiting Wifi client mode.
*/
@Test
public void clearBssidBlacklistWhenExitingWifiClientMode() {
String bssid = "6c:f3:7f:ae:8c:f3";
when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
// Blacklist the BSSID.
mWifiConnectivityManager.trackBssid(bssid, false,
WifiConnectivityManager.REASON_CODE_AP_UNABLE_TO_HANDLE_NEW_STA);
// Verify the BSSID is now blacklisted.
assertTrue(mWifiConnectivityManager.isBssidDisabled(bssid));
// Verify the BSSID gets sent to firmware.
verify(mWifiConnectivityHelper).setFirmwareRoamingConfiguration(
mBssidBlacklistCaptor.capture(), mSsidWhitelistCaptor.capture());
assertTrue(mBssidBlacklistCaptor.getValue().contains(bssid));
assertTrue(mSsidWhitelistCaptor.getValue().isEmpty());
// Exit Wifi client mode.
mWifiConnectivityManager.setWifiEnabled(false);
// Verify the BSSID blacklist is empty.
assertFalse(mWifiConnectivityManager.isBssidDisabled(bssid));
verify(mWifiConnectivityHelper, times(2)).setFirmwareRoamingConfiguration(
mBssidBlacklistCaptor.capture(), mSsidWhitelistCaptor.capture());
assertTrue(mBssidBlacklistCaptor.getValue().isEmpty());
assertTrue(mSsidWhitelistCaptor.getValue().isEmpty());
}
/**
* Verify that BSSID blacklist gets cleared when preparing for a forced connection
* initiated by user/app.
*/
@Test
public void clearBssidBlacklistWhenPreparingForForcedConnection() {
String bssid = "6c:f3:7f:ae:8c:f3";
when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
// Blacklist the BSSID.
mWifiConnectivityManager.trackBssid(bssid, false,
WifiConnectivityManager.REASON_CODE_AP_UNABLE_TO_HANDLE_NEW_STA);
// Verify the BSSID is now blacklisted.
assertTrue(mWifiConnectivityManager.isBssidDisabled(bssid));
// Verify the BSSID gets sent to firmware.
verify(mWifiConnectivityHelper).setFirmwareRoamingConfiguration(
mBssidBlacklistCaptor.capture(), mSsidWhitelistCaptor.capture());
assertTrue(mBssidBlacklistCaptor.getValue().contains(bssid));
assertTrue(mSsidWhitelistCaptor.getValue().isEmpty());
// Prepare for a forced connection attempt.
mWifiConnectivityManager.prepareForForcedConnection(1);
// Verify the BSSID blacklist is empty.
assertFalse(mWifiConnectivityManager.isBssidDisabled(bssid));
verify(mWifiConnectivityHelper, times(2)).setFirmwareRoamingConfiguration(
mBssidBlacklistCaptor.capture(), mSsidWhitelistCaptor.capture());
assertTrue(mBssidBlacklistCaptor.getValue().isEmpty());
assertTrue(mSsidWhitelistCaptor.getValue().isEmpty());
}
/**
/**
* Verify that BSSID blacklist gets trimmed down to fit firmware capability.
*/
@Test
public void trimDownBssidBlacklistForFirmware() {
when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
// Blacklist more than MAX_BSSID_BLACKLIST_SIZE BSSIDs.
for (int i = 0; i < MAX_BSSID_BLACKLIST_SIZE + 6; i++) {
StringBuilder bssid = new StringBuilder("55:44:33:22:11:00");
bssid.setCharAt(16, (char) ('0' + i));
mWifiConnectivityManager.trackBssid(bssid.toString(), false,
WifiConnectivityManager.REASON_CODE_AP_UNABLE_TO_HANDLE_NEW_STA);
// Verify that up to MAX_BSSID_BLACKLIST_SIZE BSSIDs gets sent to firmware.
verify(mWifiConnectivityHelper, times(i + 1)).setFirmwareRoamingConfiguration(
mBssidBlacklistCaptor.capture(), mSsidWhitelistCaptor.capture());
assertEquals((i + 1) < MAX_BSSID_BLACKLIST_SIZE ? (i + 1) : MAX_BSSID_BLACKLIST_SIZE,
mBssidBlacklistCaptor.getValue().size());
assertTrue(mSsidWhitelistCaptor.getValue().isEmpty());
}
}
/**
* When WifiConnectivityManager is on and Wifi client mode is enabled, framework
* queries firmware via WifiConnectivityHelper to check if firmware roaming is
* supported and its capability.
*
* Expected behavior: WifiConnectivityManager#setWifiEnabled calls into
* WifiConnectivityHelper#getFirmwareRoamingInfo
*/
@Test
public void verifyGetFirmwareRoamingInfoIsCalledWhenEnableWiFiAndWcmOn() {
reset(mWifiConnectivityHelper);
// WifiConnectivityManager is on by default
mWifiConnectivityManager.setWifiEnabled(true);
verify(mWifiConnectivityHelper).getFirmwareRoamingInfo();
}
/**
* When WifiConnectivityManager is off, verify that framework does not
* query firmware via WifiConnectivityHelper to check if firmware roaming is
* supported and its capability when enabling Wifi client mode.
*
* Expected behavior: WifiConnectivityManager#setWifiEnabled does not call into
* WifiConnectivityHelper#getFirmwareRoamingInfo
*/
@Test
public void verifyGetFirmwareRoamingInfoIsNotCalledWhenEnableWiFiAndWcmOff() {
reset(mWifiConnectivityHelper);
mWifiConnectivityManager.enable(false);
mWifiConnectivityManager.setWifiEnabled(true);
verify(mWifiConnectivityHelper, times(0)).getFirmwareRoamingInfo();
}
/*
* Firmware supports controlled roaming.
* Connect to a network which doesn't have a config specified BSSID.
*
* Expected behavior: WifiConnectivityManager calls
* WifiStateMachine.startConnectToNetwork() with the
* expected candidate network ID, and the BSSID value should be
* 'any' since firmware controls the roaming.
*/
@Test
public void useAnyBssidToConnectWhenFirmwareRoamingOnAndConfigHasNoBssidSpecified() {
// Firmware controls roaming
when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
// Set screen to on
mWifiConnectivityManager.handleScreenStateChanged(true);
// Set WiFi to disconnected state
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
verify(mWifiStateMachine).startConnectToNetwork(
CANDIDATE_NETWORK_ID, WifiStateMachine.SUPPLICANT_BSSID_ANY);
}
/*
* Firmware supports controlled roaming.
* Connect to a network which has a config specified BSSID.
*
* Expected behavior: WifiConnectivityManager calls
* WifiStateMachine.startConnectToNetwork() with the
* expected candidate network ID, and the BSSID value should be
* the config specified one.
*/
@Test
public void useConfigSpecifiedBssidToConnectWhenFirmwareRoamingOn() {
// Firmware controls roaming
when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
// Set up the candidate configuration such that it has a BSSID specified.
WifiConfiguration candidate = generateWifiConfig(
0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
candidate.BSSID = CANDIDATE_BSSID; // config specified
ScanResult candidateScanResult = new ScanResult();
candidateScanResult.SSID = CANDIDATE_SSID;
candidateScanResult.BSSID = CANDIDATE_BSSID;
candidate.getNetworkSelectionStatus().setCandidate(candidateScanResult);
when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
anyBoolean(), anyBoolean())).thenReturn(candidate);
// Set screen to on
mWifiConnectivityManager.handleScreenStateChanged(true);
// Set WiFi to disconnected state
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
}
/*
* Firmware does not support controlled roaming.
* Connect to a network which doesn't have a config specified BSSID.
*
* Expected behavior: WifiConnectivityManager calls
* WifiStateMachine.startConnectToNetwork() with the expected candidate network ID,
* and the BSSID value should be the candidate scan result specified.
*/
@Test
public void useScanResultBssidToConnectWhenFirmwareRoamingOffAndConfigHasNoBssidSpecified() {
// Set screen to on
mWifiConnectivityManager.handleScreenStateChanged(true);
// Set WiFi to disconnected state
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
}
/*
* Firmware does not support controlled roaming.
* Connect to a network which has a config specified BSSID.
*
* Expected behavior: WifiConnectivityManager calls
* WifiStateMachine.startConnectToNetwork() with the expected candidate network ID,
* and the BSSID value should be the config specified one.
*/
@Test
public void useConfigSpecifiedBssidToConnectionWhenFirmwareRoamingOff() {
// Set up the candidate configuration such that it has a BSSID specified.
WifiConfiguration candidate = generateWifiConfig(
0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
candidate.BSSID = CANDIDATE_BSSID; // config specified
ScanResult candidateScanResult = new ScanResult();
candidateScanResult.SSID = CANDIDATE_SSID;
candidateScanResult.BSSID = CANDIDATE_BSSID;
candidate.getNetworkSelectionStatus().setCandidate(candidateScanResult);
when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
anyBoolean(), anyBoolean())).thenReturn(candidate);
// Set screen to on
mWifiConnectivityManager.handleScreenStateChanged(true);
// Set WiFi to disconnected state
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
}
/**
* Firmware does not support controlled roaming.
* WiFi in connected state, framework triggers roaming.
*
* Expected behavior: WifiConnectivityManager invokes
* WifiStateMachine.startRoamToNetwork().
*/
@Test
public void frameworkInitiatedRoaming() {
// Mock the currently connected network which has the same networkID and
// SSID as the one to be selected.
WifiConfiguration currentNetwork = generateWifiConfig(
0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(currentNetwork);
// Set WiFi to connected state
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_CONNECTED);
// Set screen to on
mWifiConnectivityManager.handleScreenStateChanged(true);
verify(mWifiStateMachine).startRoamToNetwork(eq(CANDIDATE_NETWORK_ID),
mCandidateScanResultCaptor.capture());
assertEquals(mCandidateScanResultCaptor.getValue().BSSID, CANDIDATE_BSSID);
}
/**
* Firmware supports controlled roaming.
* WiFi in connected state, framework does not trigger roaming
* as it's handed off to the firmware.
*
* Expected behavior: WifiConnectivityManager doesn't invoke
* WifiStateMachine.startRoamToNetwork().
*/
@Test
public void noFrameworkRoamingIfConnectedAndFirmwareRoamingSupported() {
// Mock the currently connected network which has the same networkID and
// SSID as the one to be selected.
WifiConfiguration currentNetwork = generateWifiConfig(
0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(currentNetwork);
// Firmware controls roaming
when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
// Set WiFi to connected state
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_CONNECTED);
// Set screen to on
mWifiConnectivityManager.handleScreenStateChanged(true);
verify(mWifiStateMachine, times(0)).startRoamToNetwork(anyInt(), anyObject());
}
/*
* Wifi in disconnected state. Drop the connection attempt if the recommended
* network configuration has a BSSID specified but the scan result BSSID doesn't
* match it.
*
* Expected behavior: WifiConnectivityManager doesn't invoke
* WifiStateMachine.startConnectToNetwork().
*/
@Test
public void dropConnectAttemptIfConfigSpecifiedBssidDifferentFromScanResultBssid() {
// Set up the candidate configuration such that it has a BSSID specified.
WifiConfiguration candidate = generateWifiConfig(
0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
candidate.BSSID = CANDIDATE_BSSID; // config specified
ScanResult candidateScanResult = new ScanResult();
candidateScanResult.SSID = CANDIDATE_SSID;
// Set up the scan result BSSID to be different from the config specified one.
candidateScanResult.BSSID = INVALID_SCAN_RESULT_BSSID;
candidate.getNetworkSelectionStatus().setCandidate(candidateScanResult);
when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
anyBoolean(), anyBoolean())).thenReturn(candidate);
// Set screen to on
mWifiConnectivityManager.handleScreenStateChanged(true);
// Set WiFi to disconnected state
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
verify(mWifiStateMachine, times(0)).startConnectToNetwork(
CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
}
/*
* Wifi in connected state. Drop the roaming attempt if the recommended
* network configuration has a BSSID specified but the scan result BSSID doesn't
* match it.
*
* Expected behavior: WifiConnectivityManager doesn't invoke
* WifiStateMachine.startRoamToNetwork().
*/
@Test
public void dropRoamingAttemptIfConfigSpecifiedBssidDifferentFromScanResultBssid() {
// Mock the currently connected network which has the same networkID and
// SSID as the one to be selected.
WifiConfiguration currentNetwork = generateWifiConfig(
0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(currentNetwork);
// Set up the candidate configuration such that it has a BSSID specified.
WifiConfiguration candidate = generateWifiConfig(
0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
candidate.BSSID = CANDIDATE_BSSID; // config specified
ScanResult candidateScanResult = new ScanResult();
candidateScanResult.SSID = CANDIDATE_SSID;
// Set up the scan result BSSID to be different from the config specified one.
candidateScanResult.BSSID = INVALID_SCAN_RESULT_BSSID;
candidate.getNetworkSelectionStatus().setCandidate(candidateScanResult);
when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
anyBoolean(), anyBoolean())).thenReturn(candidate);
// Set WiFi to connected state
mWifiConnectivityManager.handleConnectionStateChanged(
WifiConnectivityManager.WIFI_STATE_CONNECTED);
// Set screen to on
mWifiConnectivityManager.handleScreenStateChanged(true);
verify(mWifiStateMachine, times(0)).startRoamToNetwork(anyInt(), anyObject());
}
/**
* Dump local log buffer.
*
* Expected behavior: Logs dumped from WifiConnectivityManager.dump()
* contain the message we put in mLocalLog.
*/
@Test
public void dumpLocalLog() {
final String localLogMessage = "This is a message from the test";
mLocalLog.log(localLogMessage);
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
mWifiConnectivityManager.dump(new FileDescriptor(), pw, new String[]{});
assertTrue(sw.toString().contains(localLogMessage));
}
/**
* Dump ONA controller.
*
* Expected behavior: {@link WifiNotificationController#dump(FileDescriptor, PrintWriter,
* String[])} is invoked.
*/
@Test
public void dumpNotificationController() {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
mWifiConnectivityManager.dump(new FileDescriptor(), pw, new String[]{});
verify(mWifiNotificationController).dump(any(), any(), any());
}
}