blob: 7d227883b1fe248b43aa1d69a9f480c9a5c7fce0 [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.settingslib.wifi;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkInfo;
import android.net.NetworkKey;
import android.net.NetworkScoreManager;
import android.net.RssiCurve;
import android.net.ScoredNetwork;
import android.net.WifiKey;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiNetworkScoreCache;
import android.net.wifi.WifiSsid;
import android.net.wifi.hotspot2.OsuProvider;
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
import android.provider.Settings;
import android.util.ArraySet;
import android.util.Pair;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.settingslib.utils.ThreadUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
// TODO(sghuman): Change these to robolectric tests b/35766684.
@SmallTest
@RunWith(AndroidJUnit4.class)
public class WifiTrackerTest {
private static final String TAG = "WifiTrackerTest";
private static final int LATCH_TIMEOUT = 4000;
private static final String SSID_1 = "ssid1";
private static final String BSSID_1 = "00:00:00:00:00:00";
private static final NetworkKey NETWORK_KEY_1 =
new NetworkKey(new WifiKey('"' + SSID_1 + '"', BSSID_1));
private static final int RSSI_1 = -30;
private static final byte SCORE_1 = 10;
private static final int BADGE_1 = AccessPoint.Speed.MODERATE;
private static final String FQDN_1 = "fqdn1";
private static final String PROVIDER_FRIENDLY_NAME_1 = "providerFriendlyName1";
private static final String SSID_2 = "ssid2";
private static final String BSSID_2 = "AA:AA:AA:AA:AA:AA";
private static final NetworkKey NETWORK_KEY_2 =
new NetworkKey(new WifiKey('"' + SSID_2 + '"', BSSID_2));
private static final int RSSI_2 = -30;
private static final byte SCORE_2 = 15;
private static final int BADGE_2 = AccessPoint.Speed.FAST;
private static final String FQDN_2 = "fqdn2";
private static final String PROVIDER_FRIENDLY_NAME_2 = "providerFriendlyName2";
private static final String SSID_3 = "ssid3";
private static final String BSSID_3 = "CC:00:00:00:00:00";
private static final int RSSI_3 = -40;
// TODO(b/65594609): Convert mutable Data objects to instance variables / builder pattern
private static final int NETWORK_ID_1 = 123;
private static final int CONNECTED_RSSI = -50;
private static final WifiInfo CONNECTED_AP_1_INFO = new WifiInfo();
static {
CONNECTED_AP_1_INFO.setSSID(WifiSsid.createFromAsciiEncoded(SSID_1));
CONNECTED_AP_1_INFO.setBSSID(BSSID_1);
CONNECTED_AP_1_INFO.setNetworkId(NETWORK_ID_1);
CONNECTED_AP_1_INFO.setRssi(CONNECTED_RSSI);
}
private static final WifiConfiguration CONFIGURATION_1 = new WifiConfiguration();
static {
CONFIGURATION_1.SSID = SSID_1;
CONFIGURATION_1.BSSID = BSSID_1;
CONFIGURATION_1.networkId = NETWORK_ID_1;
}
private static final int NETWORK_ID_2 = 2;
private static final WifiConfiguration CONFIGURATION_2 = new WifiConfiguration();
static {
CONFIGURATION_2.SSID = SSID_2;
CONFIGURATION_2.BSSID = BSSID_2;
CONFIGURATION_2.networkId = NETWORK_ID_2;
}
@Captor ArgumentCaptor<WifiNetworkScoreCache> mScoreCacheCaptor;
@Mock private ConnectivityManager mockConnectivityManager;
@Mock private NetworkScoreManager mockNetworkScoreManager;
@Mock private RssiCurve mockCurve1;
@Mock private RssiCurve mockCurve2;
@Mock private RssiCurve mockBadgeCurve1;
@Mock private RssiCurve mockBadgeCurve2;
@Mock private WifiManager mockWifiManager;
@Mock private WifiTracker.WifiListener mockWifiListener;
private final List<NetworkKey> mRequestedKeys = new ArrayList<>();
private Context mContext;
private CountDownLatch mAccessPointsChangedLatch;
private CountDownLatch mRequestScoresLatch;
private Handler mScannerHandler;
private HandlerThread mWorkerThread;
private int mOriginalScoringUiSettingValue;
@SuppressWarnings("VisibleForTests")
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = InstrumentationRegistry.getTargetContext();
mWorkerThread = new HandlerThread("TestHandlerWorkerThread");
mWorkerThread.start();
// Make sure the scanner doesn't try to run on the testing thread.
HandlerThread scannerThread = new HandlerThread("ScannerWorkerThread");
scannerThread.start();
mScannerHandler = new Handler(scannerThread.getLooper());
when(mockWifiManager.isWifiEnabled()).thenReturn(true);
when(mockWifiManager.getScanResults())
.thenReturn(Arrays.asList(buildScanResult1(), buildScanResult2()));
when(mockWifiManager.getConfiguredNetworks())
.thenReturn(Arrays.asList(CONFIGURATION_1, CONFIGURATION_2));
when(mockCurve1.lookupScore(RSSI_1)).thenReturn(SCORE_1);
when(mockCurve2.lookupScore(RSSI_2)).thenReturn(SCORE_2);
when(mockBadgeCurve1.lookupScore(RSSI_1)).thenReturn((byte) BADGE_1);
when(mockBadgeCurve2.lookupScore(RSSI_2)).thenReturn((byte) BADGE_2);
doNothing()
.when(mockNetworkScoreManager)
.registerNetworkScoreCache(
anyInt(),
mScoreCacheCaptor.capture(),
Matchers.anyInt());
// Capture requested keys and count down latch if present
doAnswer(
new Answer<Boolean>() {
@Override
public Boolean answer(InvocationOnMock input) {
if (mRequestScoresLatch != null) {
mRequestScoresLatch.countDown();
}
NetworkKey[] keys = (NetworkKey[]) input.getArguments()[0];
for (NetworkKey key : keys) {
mRequestedKeys.add(key);
}
return true;
}
}).when(mockNetworkScoreManager).requestScores(Matchers.<NetworkKey[]>any());
// We use a latch to detect callbacks as Tracker initialization state often invokes
// callbacks
doAnswer(invocation -> {
if (mAccessPointsChangedLatch != null) {
mAccessPointsChangedLatch.countDown();
}
return null;
}).when(mockWifiListener).onAccessPointsChanged();
// Turn on Scoring UI features
mOriginalScoringUiSettingValue = Settings.Global.getInt(
InstrumentationRegistry.getTargetContext().getContentResolver(),
Settings.Global.NETWORK_SCORING_UI_ENABLED,
0 /* disabled */);
Settings.Global.putInt(
InstrumentationRegistry.getTargetContext().getContentResolver(),
Settings.Global.NETWORK_SCORING_UI_ENABLED,
1 /* enabled */);
}
@After
public void cleanUp() {
Settings.Global.putInt(
InstrumentationRegistry.getTargetContext().getContentResolver(),
Settings.Global.NETWORK_SCORING_UI_ENABLED,
mOriginalScoringUiSettingValue);
}
private static ScanResult buildScanResult1() {
return new ScanResult(
WifiSsid.createFromAsciiEncoded(SSID_1),
BSSID_1,
0, // hessid
0, //anqpDomainId
null, // osuProviders
"", // capabilities
RSSI_1,
0, // frequency
SystemClock.elapsedRealtime() * 1000 /* microsecond timestamp */);
}
private static ScanResult buildScanResult2() {
return new ScanResult(
WifiSsid.createFromAsciiEncoded(SSID_2),
BSSID_2,
0, // hessid
0, //anqpDomainId
null, // osuProviders
"", // capabilities
RSSI_2,
0, // frequency
SystemClock.elapsedRealtime() * 1000 /* microsecond timestamp */);
}
private static ScanResult buildStaleScanResult() {
return new ScanResult(
WifiSsid.createFromAsciiEncoded(SSID_3),
BSSID_3,
0, // hessid
0, //anqpDomainId
null, // osuProviders
"", // capabilities
RSSI_3,
0, // frequency
0 /* microsecond timestamp */);
}
private static WifiConfiguration buildPasspointConfiguration(String fqdn, String friendlyName) {
WifiConfiguration config = spy(new WifiConfiguration());
config.FQDN = fqdn;
config.providerFriendlyName = friendlyName;
when(config.isPasspoint()).thenReturn(true);
return config;
}
private List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>>
createPasspointMatchingWifiConfigsWithDuplicates() {
List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> matchingList =
new ArrayList<>();
Map<Integer, List<ScanResult>> mapping = new HashMap<>();
mapping.put(WifiManager.PASSPOINT_HOME_NETWORK, Arrays.asList(buildScanResult1()));
WifiConfiguration passpointConfig1 =
buildPasspointConfiguration(FQDN_1, PROVIDER_FRIENDLY_NAME_1);
WifiConfiguration passpointConfig2 =
buildPasspointConfiguration(FQDN_2, PROVIDER_FRIENDLY_NAME_2);
matchingList.add(new Pair(passpointConfig1, mapping));
matchingList.add(new Pair(passpointConfig1, mapping));
matchingList.add(new Pair(passpointConfig2, mapping));
matchingList.add(new Pair(passpointConfig2, mapping));
return matchingList;
}
private List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>>
createPasspointMatchingWifiConfigWithScanResults(
List<ScanResult> homeList, List<ScanResult> roamingList) {
List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> matchingList =
new ArrayList<>();
Map<Integer, List<ScanResult>> mapping = new HashMap<>();
if (homeList != null) {
mapping.put(WifiManager.PASSPOINT_HOME_NETWORK, homeList);
}
if (roamingList != null) {
mapping.put(WifiManager.PASSPOINT_ROAMING_NETWORK, roamingList);
}
matchingList.add(new Pair(buildPasspointConfiguration(FQDN_1, PROVIDER_FRIENDLY_NAME_1),
mapping));
return matchingList;
}
private static OsuProvider buildOsuProvider(String friendlyName) {
Map<String, String> friendlyNames = new HashMap<>();
friendlyNames.put("en", friendlyName);
return new OsuProvider(null, friendlyNames, null, null, null, null, null);
}
private WifiTracker createTrackerWithImmediateBroadcastsAndInjectInitialScanResults(
Intent ... intents)
throws InterruptedException {
WifiTracker tracker = createMockedWifiTracker();
startTracking(tracker);
for (Intent intent : intents) {
tracker.mReceiver.onReceive(mContext, intent);
}
sendScanResults(tracker);
return tracker;
}
private WifiTracker createMockedWifiTracker() {
final WifiTracker wifiTracker = new WifiTracker(
mContext,
mockWifiListener,
mockWifiManager,
mockConnectivityManager,
mockNetworkScoreManager,
new IntentFilter()); // empty filter to ignore system broadcasts
wifiTracker.setWorkThread(mWorkerThread);
return wifiTracker;
}
private void startTracking(WifiTracker tracker) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
mScannerHandler.post(() -> {
tracker.onStart();
latch.countDown();
});
assertTrue("Latch timed out", latch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
}
private void sendScanResults(WifiTracker tracker) throws InterruptedException {
Intent i = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
tracker.mReceiver.onReceive(mContext, i);
}
private void sendUpdatedScores() throws InterruptedException {
Bundle attr1 = new Bundle();
attr1.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, mockBadgeCurve1);
ScoredNetwork sc1 =
new ScoredNetwork(
NETWORK_KEY_1,
mockCurve1,
false /* meteredHint */,
attr1);
Bundle attr2 = new Bundle();
attr2.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, mockBadgeCurve2);
ScoredNetwork sc2 =
new ScoredNetwork(
NETWORK_KEY_2,
mockCurve2,
true /* meteredHint */,
attr2);
WifiNetworkScoreCache scoreCache = mScoreCacheCaptor.getValue();
scoreCache.updateScores(Arrays.asList(sc1, sc2));
}
private WifiTracker createTrackerWithScanResultsAndAccessPoint1Connected()
throws InterruptedException {
when(mockWifiManager.getConnectionInfo()).thenReturn(CONNECTED_AP_1_INFO);
WifiConfiguration configuration = new WifiConfiguration();
configuration.SSID = SSID_1;
configuration.BSSID = BSSID_1;
configuration.networkId = NETWORK_ID_1;
NetworkInfo networkInfo = new NetworkInfo(
ConnectivityManager.TYPE_WIFI, 0, "Type Wifi", "subtype");
networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "connected", "test");
Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo);
WifiTracker tracker =
createTrackerWithImmediateBroadcastsAndInjectInitialScanResults(intent);
assertThat(tracker.isConnected()).isTrue();
return tracker;
}
private void waitForHandlersToProcessCurrentlyEnqueuedMessages(WifiTracker tracker)
throws InterruptedException {
CountDownLatch workerLatch = new CountDownLatch(1);
tracker.mWorkHandler.post(() -> workerLatch.countDown());
assertTrue("Latch timed out while waiting for WorkerHandler",
workerLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
}
private void switchToNetwork2(WifiTracker tracker) throws InterruptedException {
NetworkInfo networkInfo = new NetworkInfo(
ConnectivityManager.TYPE_WIFI, 0, "Type Wifi", "subtype");
networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTING, "connecting", "test");
WifiInfo info = new WifiInfo();
info.setSSID(WifiSsid.createFromAsciiEncoded(SSID_2));
info.setBSSID(BSSID_2);
info.setRssi(CONNECTED_RSSI);
info.setNetworkId(NETWORK_ID_2);
when(mockWifiManager.getConnectionInfo()).thenReturn(info);
Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo);
tracker.mReceiver.onReceive(mContext, intent);
}
@Test
public void startAndStopTrackingShouldRegisterAndUnregisterScoreCache()
throws InterruptedException {
WifiTracker tracker = createMockedWifiTracker();
// Test register
startTracking(tracker);
verify(mockNetworkScoreManager)
.registerNetworkScoreCache(
Matchers.anyInt(),
mScoreCacheCaptor.capture(),
Matchers.anyInt());
WifiNetworkScoreCache scoreCache = mScoreCacheCaptor.getValue();
CountDownLatch latch = new CountDownLatch(1);
doAnswer(
(invocation) -> {
latch.countDown();
return null;
}).when(mockNetworkScoreManager)
.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, scoreCache);
// Test unregister
tracker.onStop();
assertTrue("Latch timed out", latch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
verify(mockNetworkScoreManager)
.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, scoreCache);
}
@Test
public void testGetNumSavedNetworks() throws InterruptedException {
WifiConfiguration validConfig = new WifiConfiguration();
validConfig.SSID = SSID_1;
validConfig.BSSID = BSSID_1;
WifiConfiguration selfAddedNoAssociation = new WifiConfiguration();
selfAddedNoAssociation.ephemeral = true;
selfAddedNoAssociation.selfAdded = true;
selfAddedNoAssociation.numAssociation = 0;
selfAddedNoAssociation.SSID = SSID_2;
selfAddedNoAssociation.BSSID = BSSID_2;
when(mockWifiManager.getConfiguredNetworks())
.thenReturn(Arrays.asList(validConfig, selfAddedNoAssociation));
WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
assertEquals(1, tracker.getNumSavedNetworks());
}
@Test
public void startTrackingShouldSetConnectedAccessPointAsActive() throws InterruptedException {
WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected();
List<AccessPoint> aps = tracker.getAccessPoints();
assertThat(aps).hasSize(2);
assertThat(aps.get(0).isActive()).isTrue();
}
@Test
public void startTrackingAfterStopTracking_shouldRequestNewScores()
throws InterruptedException {
// Start the tracker and inject the initial scan results and then stop tracking
WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
tracker.onStop();
mRequestedKeys.clear();
mRequestScoresLatch = new CountDownLatch(1);
startTracking(tracker);
assertTrue("Latch timed out",
mRequestScoresLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
assertTrue(mRequestedKeys.contains(NETWORK_KEY_1));
assertTrue(mRequestedKeys.contains(NETWORK_KEY_2));
}
@Test
public void stopTracking_shouldNotClearExistingScores()
throws InterruptedException {
// Start the tracker and inject the initial scan results and then stop tracking
WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
updateScoresAndWaitForCacheListenerToProcess(tracker);
tracker.onStop();
assertThat(mScoreCacheCaptor.getValue().getScoredNetwork(NETWORK_KEY_1)).isNotNull();
}
@Test
public void scoreCacheUpdateScoresShouldTriggerOnAccessPointsChanged()
throws InterruptedException {
WifiTracker tracker = createMockedWifiTracker();
startTracking(tracker);
sendScanResults(tracker);
updateScoresAndWaitForCacheListenerToProcess(tracker);
}
private void updateScoresAndWaitForCacheListenerToProcess(WifiTracker tracker)
throws InterruptedException {
// Scores are updated via the cache listener hence we need to wait for the work handler
// to finish before proceeding.
sendUpdatedScores();
// Ensure the work handler has processed the scores inside the cache listener of WifiTracker
waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
}
@Test
public void scoreCacheUpdateScoresShouldChangeSortOrder() throws InterruptedException {
WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
List<AccessPoint> aps = tracker.getAccessPoints();
assertTrue(aps.size() == 2);
assertEquals(aps.get(0).getSsidStr(), SSID_1);
assertEquals(aps.get(1).getSsidStr(), SSID_2);
updateScoresAndWaitForCacheListenerToProcess(tracker);
aps = tracker.getAccessPoints();
assertTrue(aps.size() == 2);
assertEquals(aps.get(0).getSsidStr(), SSID_2);
assertEquals(aps.get(1).getSsidStr(), SSID_1);
}
@Test
public void scoreCacheUpdateScoresShouldNotChangeSortOrderWhenSortingDisabled()
throws InterruptedException {
Settings.Global.putInt(
InstrumentationRegistry.getTargetContext().getContentResolver(),
Settings.Global.NETWORK_SCORING_UI_ENABLED,
0 /* disabled */);
WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
List<AccessPoint> aps = tracker.getAccessPoints();
assertTrue(aps.size() == 2);
assertEquals(aps.get(0).getSsidStr(), SSID_1);
assertEquals(aps.get(1).getSsidStr(), SSID_2);
updateScoresAndWaitForCacheListenerToProcess(tracker);
aps = tracker.getAccessPoints();
assertTrue(aps.size() == 2);
assertEquals(aps.get(0).getSsidStr(), SSID_1);
assertEquals(aps.get(1).getSsidStr(), SSID_2);
}
@Test
public void scoreCacheUpdateScoresShouldInsertSpeedIntoAccessPoint()
throws InterruptedException {
WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
updateScoresAndWaitForCacheListenerToProcess(tracker);
List<AccessPoint> aps = tracker.getAccessPoints();
for (AccessPoint ap : aps) {
if (ap.getSsidStr().equals(SSID_1)) {
assertEquals(BADGE_1, ap.getSpeed());
} else if (ap.getSsidStr().equals(SSID_2)) {
assertEquals(BADGE_2, ap.getSpeed());
}
}
}
@Test
public void scoreCacheUpdateMeteredShouldUpdateAccessPointMetering()
throws InterruptedException {
WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
updateScoresAndWaitForCacheListenerToProcess(tracker);
List<AccessPoint> aps = tracker.getAccessPoints();
for (AccessPoint ap : aps) {
if (ap.getSsidStr().equals(SSID_1)) {
assertFalse(ap.isMetered());
} else if (ap.getSsidStr().equals(SSID_2)) {
assertTrue(ap.isMetered());
}
}
}
@Test
public void noSpeedsShouldBeInsertedIntoAccessPointWhenScoringUiDisabled()
throws InterruptedException {
Settings.Global.putInt(
InstrumentationRegistry.getTargetContext().getContentResolver(),
Settings.Global.NETWORK_SCORING_UI_ENABLED,
0 /* disabled */);
WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
updateScoresAndWaitForCacheListenerToProcess(tracker);
List<AccessPoint> aps = tracker.getAccessPoints();
for (AccessPoint ap : aps) {
if (ap.getSsidStr().equals(SSID_1)) {
assertEquals(AccessPoint.Speed.NONE, ap.getSpeed());
} else if (ap.getSsidStr().equals(SSID_2)) {
assertEquals(AccessPoint.Speed.NONE, ap.getSpeed());
}
}
}
@Test
public void scoresShouldBeRequestedForNewScanResultOnly() throws InterruptedException {
// Scores can be requested together or serially depending on how the scan results are
// processed.
mRequestScoresLatch = new CountDownLatch(1);
WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
assertTrue(mRequestScoresLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
mRequestedKeys.clear();
String ssid = "ssid3";
String bssid = "00:00:00:00:00:00";
ScanResult newResult = new ScanResult(
WifiSsid.createFromAsciiEncoded(ssid),
bssid,
0, // hessid
0, //anqpDomainId
null, // osuProviders
"", // capabilities
RSSI_1,
0, // frequency
SystemClock.elapsedRealtime() * 1000);
when(mockWifiManager.getScanResults())
.thenReturn(Arrays.asList(buildScanResult1(), buildScanResult2(), newResult));
mRequestScoresLatch = new CountDownLatch(1);
sendScanResults(tracker);
assertTrue(mRequestScoresLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
assertEquals(1, mRequestedKeys.size());
assertTrue(mRequestedKeys.contains(new NetworkKey(new WifiKey('"' + ssid + '"', bssid))));
}
@Test
public void scoreCacheAndListenerShouldBeUnregisteredWhenStopTrackingIsCalled() throws Exception
{
WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
WifiNetworkScoreCache cache = mScoreCacheCaptor.getValue();
tracker.onStop();
verify(mockNetworkScoreManager).unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, cache);
// Verify listener is unregistered so updating a score does not throw an error by posting
// a message to the dead work handler
mWorkerThread.quit();
sendUpdatedScores();
}
/**
* Verify that tracking a Passpoint AP on a device with Passpoint disabled doesn't cause
* any crash.
*
* @throws Exception
*/
@Test
public void trackPasspointApWithPasspointDisabled() throws Exception {
// TODO(sghuman): Delete this test and replace with a passpoint test
WifiTracker tracker = createMockedWifiTracker();
// Add a Passpoint AP to the scan results.
List<ScanResult> results = new ArrayList<>();
ScanResult passpointAp = new ScanResult(
WifiSsid.createFromAsciiEncoded(SSID_1),
BSSID_1,
0, // hessid
0, //anqpDomainId
null, // osuProviders
"", // capabilities
RSSI_1,
0, // frequency
SystemClock.elapsedRealtime() * 1000 /* microsecond timestamp */);
passpointAp.setFlag(ScanResult.FLAG_PASSPOINT_NETWORK);
results.add(passpointAp);
// Update access point and verify UnsupportedOperationException is being caught for
// call to WifiManager#getMatchingWifiConfig.
when(mockWifiManager.getConfiguredNetworks())
.thenReturn(new ArrayList<WifiConfiguration>());
when(mockWifiManager.getScanResults()).thenReturn(results);
startTracking(tracker);
}
@Test
public void rssiChangeBroadcastShouldUpdateConnectedAp() throws Exception {
WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected();
assertThat(tracker.getAccessPoints().get(0).isActive()).isTrue();
int newRssi = CONNECTED_RSSI + 10;
WifiInfo info = new WifiInfo(CONNECTED_AP_1_INFO);
info.setRssi(newRssi);
// Once the new info has been fetched, we need to wait for the access points to be copied
mAccessPointsChangedLatch = new CountDownLatch(1);
doAnswer(invocation -> info).when(mockWifiManager).getConnectionInfo();
tracker.mReceiver.onReceive(mContext, new Intent(WifiManager.RSSI_CHANGED_ACTION));
assertTrue("onAccessPointsChanged never called",
mAccessPointsChangedLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
assertThat(tracker.getAccessPoints().get(0).getRssi()).isEqualTo(newRssi);
}
@Test
public void onStartShouldSynchronouslyFetchLatestInformation() throws Exception {
Network mockNetwork = mock(Network.class);
when(mockWifiManager.getCurrentNetwork()).thenReturn(mockNetwork);
when(mockWifiManager.getConnectionInfo()).thenReturn(CONNECTED_AP_1_INFO);
NetworkInfo networkInfo = new NetworkInfo(
ConnectivityManager.TYPE_WIFI, 0, "Type Wifi", "subtype");
networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "connected", "test");
when(mockConnectivityManager.getNetworkInfo(any(Network.class))).thenReturn(networkInfo);
WifiTracker tracker = createMockedWifiTracker();
startTracking(tracker);
verify(mockWifiManager).getConnectionInfo();
verify(mockWifiManager, times(1)).getConfiguredNetworks();
verify(mockConnectivityManager).getNetworkInfo(any(Network.class));
// mStaleAccessPoints is true
verify(mockWifiListener, never()).onAccessPointsChanged();
assertThat(tracker.getAccessPoints().size()).isEqualTo(2);
assertThat(tracker.getAccessPoints().get(0).isActive()).isTrue();
}
@Test
public void onStartShouldDisplayConnectedAccessPointWhenThereAreNoScanResults()
throws Exception {
Network mockNetwork = mock(Network.class);
when(mockWifiManager.getCurrentNetwork()).thenReturn(mockNetwork);
when(mockWifiManager.getConnectionInfo()).thenReturn(CONNECTED_AP_1_INFO);
NetworkInfo networkInfo = new NetworkInfo(
ConnectivityManager.TYPE_WIFI, 0, "Type Wifi", "subtype");
networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "connected", "test");
when(mockConnectivityManager.getNetworkInfo(any(Network.class))).thenReturn(networkInfo);
// Don't return any scan results
when(mockWifiManager.getScanResults()).thenReturn(new ArrayList<>());
WifiTracker tracker = createMockedWifiTracker();
startTracking(tracker);
verify(mockWifiManager).getConnectionInfo();
verify(mockWifiManager, times(1)).getConfiguredNetworks();
verify(mockConnectivityManager).getNetworkInfo(any(Network.class));
// mStaleAccessPoints is true
verify(mockWifiListener, never()).onAccessPointsChanged();
assertThat(tracker.getAccessPoints()).hasSize(1);
assertThat(tracker.getAccessPoints().get(0).isActive()).isTrue();
}
@Test
public void stopTrackingShouldRemoveAllPendingWork() throws Exception {
WifiTracker tracker = createMockedWifiTracker();
startTracking(tracker);
CountDownLatch ready = new CountDownLatch(1);
CountDownLatch latch = new CountDownLatch(1);
CountDownLatch lock = new CountDownLatch(1);
tracker.mWorkHandler.post(() -> {
try {
ready.countDown();
lock.await();
latch.countDown();
} catch (InterruptedException e) {
fail("Interrupted Exception while awaiting lock release: " + e);
}
});
// Enqueue messages
final AtomicBoolean executed = new AtomicBoolean(false);
tracker.mWorkHandler.post(() -> executed.set(true));
try {
ready.await(); // Make sure we have entered the first message handler
} catch (InterruptedException e) {}
tracker.onStop();
lock.countDown();
assertTrue("Latch timed out", latch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
// In case the method was already executing
assertThat(tracker.mWorkHandler.hasMessagesOrCallbacks()).isFalse();
assertThat(executed.get()).isFalse();
}
@Test
public void stopTrackingShouldPreventCallbacksFromOngoingWork() throws Exception {
WifiTracker tracker = createMockedWifiTracker();
startTracking(tracker);
final CountDownLatch ready = new CountDownLatch(1);
final CountDownLatch latch = new CountDownLatch(1);
final CountDownLatch lock = new CountDownLatch(1);
tracker.mWorkHandler.post(() -> {
try {
ready.countDown();
lock.await();
tracker.mReceiver.onReceive(
mContext, new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
latch.countDown();
} catch (InterruptedException e) {
fail("Interrupted Exception while awaiting lock release: " + e);
}
});
ready.await(); // Make sure we have entered the first message handler
tracker.onStop();
lock.countDown();
assertTrue("Latch timed out", latch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
// Wait for main thread
final CountDownLatch latch2 = new CountDownLatch(1);
ThreadUtils.postOnMainThread(latch2::countDown);
latch2.await();
verify(mockWifiListener, never()).onWifiStateChanged(anyInt());
}
@Test
public void stopTrackingShouldSetStaleBitWhichPreventsCallbacksUntilNextScanResult()
throws Exception {
WifiTracker tracker = createMockedWifiTracker();
startTracking(tracker);
tracker.onStop();
startTracking(tracker);
tracker.mReceiver.onReceive(mContext, new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
tracker.mReceiver.onReceive(
mContext, new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION));
tracker.mReceiver.onReceive(
mContext, new Intent(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION));
verify(mockWifiListener, never()).onAccessPointsChanged();
sendScanResults(tracker); // verifies onAccessPointsChanged is invoked
}
@Test
public void startTrackingShouldNotSendAnyCallbacksUntilScanResultsAreProcessed()
throws Exception {
WifiTracker tracker = createMockedWifiTracker();
startTracking(tracker);
tracker.mReceiver.onReceive(mContext, new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
tracker.mReceiver.onReceive(
mContext, new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION));
tracker.mReceiver.onReceive(
mContext, new Intent(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION));
verify(mockWifiListener, never()).onAccessPointsChanged();
sendScanResults(tracker); // verifies onAccessPointsChanged is invoked
}
@Test
public void disablingWifiShouldClearExistingAccessPoints() throws Exception {
WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected();
when(mockWifiManager.isWifiEnabled()).thenReturn(false);
mAccessPointsChangedLatch = new CountDownLatch(1);
tracker.mReceiver.onReceive(mContext, new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
assertThat(mAccessPointsChangedLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS)).isTrue();
assertThat(tracker.getAccessPoints()).isEmpty();
}
@Test
public void onConnectedChangedCallback_shouldNotBeInvokedWhenNoStateChange() throws Exception {
WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected();
verify(mockWifiListener, times(1)).onConnectedChanged();
NetworkInfo networkInfo = new NetworkInfo(
ConnectivityManager.TYPE_WIFI, 0, "Type Wifi", "subtype");
networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "connected", "test");
Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo);
tracker.mReceiver.onReceive(mContext, intent);
verify(mockWifiListener, times(1)).onConnectedChanged();
}
@Test
public void onConnectedChangedCallback_shouldBeInvokedWhenStateChanges() throws Exception {
WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected();
verify(mockWifiListener, times(1)).onConnectedChanged();
NetworkInfo networkInfo = new NetworkInfo(
ConnectivityManager.TYPE_WIFI, 0, "Type Wifi", "subtype");
networkInfo.setDetailedState(
NetworkInfo.DetailedState.DISCONNECTED, "disconnected", "test");
Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo);
tracker.mReceiver.onReceive(mContext, intent);
assertThat(tracker.isConnected()).isFalse();
verify(mockWifiListener, times(2)).onConnectedChanged();
}
@Test
public void updateNetworkInfoWithNewConnectedNetwork_switchesNetworks() throws Exception {
WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected();
switchToNetwork2(tracker);
List<AccessPoint> aps = tracker.getAccessPoints();
assertThat(aps.get(0).getSsidStr()).isEqualTo(SSID_2);
assertThat(aps.get(0).isReachable()).isTrue();
assertThat(aps.get(1).isReachable()).isTrue();
}
@Test
public void onStart_updateScanResults_evictOldScanResult() {
when(mockWifiManager.getScanResults()).thenReturn(
Arrays.asList(buildScanResult1(), buildScanResult2(), buildStaleScanResult()));
WifiTracker tracker = createMockedWifiTracker();
tracker.forceUpdate();
// Only has scanResult1 and scanResult2
assertThat(tracker.getAccessPoints()).hasSize(2);
assertThat(tracker.getAccessPoints().get(0).getBssid()).isEqualTo(BSSID_1);
assertThat(tracker.getAccessPoints().get(1).getBssid()).isEqualTo(BSSID_2);
}
/**
* Verifies that updatePasspointAccessPoints will only return AccessPoints whose
* isPasspoint() evaluates as true.
*/
@Test
public void updatePasspointAccessPoints_returnedAccessPointsArePasspoint() {
WifiTracker tracker = createMockedWifiTracker();
List<AccessPoint> passpointAccessPoints = tracker.updatePasspointAccessPoints(
createPasspointMatchingWifiConfigsWithDuplicates(), new ArrayList<>());
assertTrue(passpointAccessPoints.size() != 0);
for (AccessPoint ap : passpointAccessPoints) {
assertTrue(ap.isPasspoint());
}
}
/**
* Verifies that updatePasspointAccessPoints will return the same amount of AccessPoints as
* unique WifiConfigurations, even if duplicate FQDNs exist.
*/
@Test
public void updatePasspointAccessPoints_ignoresDuplicateFQDNs() {
WifiTracker tracker = createMockedWifiTracker();
// Process matching list of four configs with two duplicate FQDNs.
List<AccessPoint> passpointAccessPoints = tracker.updatePasspointAccessPoints(
createPasspointMatchingWifiConfigsWithDuplicates(), new ArrayList<>());
// Should have 2 APs with unique FQDNs, ignoring the 2 duplicate FQDNs.
assertThat(passpointAccessPoints).hasSize(2);
Set<String> fqdns = new ArraySet<>(Arrays.asList(FQDN_1, FQDN_2));
assertTrue(fqdns.remove(passpointAccessPoints.get(0).getConfig().FQDN));
assertTrue(fqdns.remove(passpointAccessPoints.get(1).getConfig().FQDN));
}
/**
* Verifies that updatePasspointAccessPoints will return matching cached APs and update their
* scan results instead of creating new APs.
*/
@Test
public void updatePasspointAccessPoints_usesCachedAccessPoints() {
WifiTracker tracker = createMockedWifiTracker();
ScanResult result = buildScanResult1();
List<AccessPoint> passpointAccessPointsFirstUpdate = tracker.updatePasspointAccessPoints(
createPasspointMatchingWifiConfigWithScanResults(Arrays.asList(result),
null), new ArrayList<>());
List<AccessPoint> cachedAccessPoints = new ArrayList<>(passpointAccessPointsFirstUpdate);
int prevRssi = result.level;
int newRssi = prevRssi + 10;
result.level = newRssi;
List<AccessPoint> passpointAccessPointsSecondUpdate = tracker.updatePasspointAccessPoints(
createPasspointMatchingWifiConfigWithScanResults(Arrays.asList(result),
null), cachedAccessPoints);
// Verify second update AP is the same object as the first update AP
assertThat(passpointAccessPointsFirstUpdate.get(0))
.isSameAs(passpointAccessPointsSecondUpdate.get(0));
// Verify second update AP has the average of the first and second update RSSIs
assertThat(passpointAccessPointsSecondUpdate.get(0).getRssi())
.isEqualTo((prevRssi + newRssi) / 2);
}
/**
* Verifies that updateOsuAccessPoints will only return AccessPoints whose
* isOsuProvider() evaluates as true.
*/
@Test
public void updateOsuAccessPoints_returnedAccessPointsAreOsuProviders() {
WifiTracker tracker = createMockedWifiTracker();
Map<OsuProvider, List<ScanResult>> providersAndScans = new HashMap<>();
providersAndScans.put(
buildOsuProvider(PROVIDER_FRIENDLY_NAME_1), Arrays.asList(buildScanResult1()));
providersAndScans.put(
buildOsuProvider(PROVIDER_FRIENDLY_NAME_2), Arrays.asList(buildScanResult2()));
List<AccessPoint> osuAccessPoints = tracker.updateOsuAccessPoints(
providersAndScans, new ArrayList<>());
assertThat(osuAccessPoints).hasSize(2);
for (AccessPoint ap: osuAccessPoints) {
assertThat(ap.isOsuProvider()).isTrue();
}
}
/**
* Verifies that updateOsuAccessPoints will not return Osu AccessPoints for already provisioned
* networks
*/
@Test
public void updateOsuAccessPoints_doesNotReturnAlreadyProvisionedOsuAccessPoints() {
WifiTracker tracker = createMockedWifiTracker();
// Start with two Osu Providers
Map<OsuProvider, List<ScanResult>> providersAndScans = new HashMap<>();
providersAndScans.put(
buildOsuProvider(PROVIDER_FRIENDLY_NAME_1), Arrays.asList(buildScanResult1()));
providersAndScans.put(
buildOsuProvider(PROVIDER_FRIENDLY_NAME_2), Arrays.asList(buildScanResult2()));
// First update
List<AccessPoint> osuAccessPoints = tracker.updateOsuAccessPoints(
providersAndScans, new ArrayList<>());
// Make sure both Osu Providers' APs are returned
assertThat(osuAccessPoints).hasSize(2);
List<String> friendlyNames = Arrays.asList(
osuAccessPoints.get(0).getTitle(), osuAccessPoints.get(1).getTitle());
assertThat(friendlyNames)
.containsExactly(PROVIDER_FRIENDLY_NAME_1, PROVIDER_FRIENDLY_NAME_2);
// Simulate Osu Provider 1 being provisioned
Map<OsuProvider, PasspointConfiguration> matchingPasspointConfigForOsuProvider =
new HashMap<>();
matchingPasspointConfigForOsuProvider.put(buildOsuProvider(PROVIDER_FRIENDLY_NAME_1), null);
when(mockWifiManager.getMatchingPasspointConfigsForOsuProviders(any())).thenReturn(
matchingPasspointConfigForOsuProvider);
// Second update
osuAccessPoints = tracker.updateOsuAccessPoints(
providersAndScans, new ArrayList<>());
// Returned AP should only be for Osu Provider 2
assertThat(osuAccessPoints).hasSize(1);
assertThat(osuAccessPoints.get(0).getTitle()).isEqualTo(PROVIDER_FRIENDLY_NAME_2);
}
/**
* Verifies that updateOsuAccessPoints will return matching cached APs and update their
* scan results instead of creating new APs.
*/
@Test
public void updateOsuAccessPoints_usesCachedAccessPoints() {
WifiTracker tracker = createMockedWifiTracker();
ScanResult result = buildScanResult1();
Map<OsuProvider, List<ScanResult>> providersAndScans = new HashMap<>();
providersAndScans.put(
buildOsuProvider(PROVIDER_FRIENDLY_NAME_1), Arrays.asList(result));
List<AccessPoint> osuAccessPointsFirstUpdate = tracker.updateOsuAccessPoints(
providersAndScans, new ArrayList<>());
List<AccessPoint> cachedAccessPoints = new ArrayList<>(osuAccessPointsFirstUpdate);
// New RSSI for second update
int prevRssi = result.level;
int newRssi = prevRssi + 10;
result.level = newRssi;
List<AccessPoint> osuAccessPointsSecondUpdate = tracker.updateOsuAccessPoints(
providersAndScans, cachedAccessPoints);
// Verify second update AP is the same object as the first update AP
assertTrue(osuAccessPointsFirstUpdate.get(0)
== osuAccessPointsSecondUpdate.get(0));
// Verify second update AP has the average of the first and second update RSSIs
assertThat(osuAccessPointsSecondUpdate.get(0).getRssi())
.isEqualTo((prevRssi + newRssi) / 2);
}
}