blob: 7cce648f0dfad6a3f22a37181f89abc146ead0d0 [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.atMost;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.Intent;
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.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.SystemClock;
import android.provider.Settings;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.filters.FlakyTest;
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.BitSet;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
// 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 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;
// 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 mMainThread;
private HandlerThread mWorkerThread;
private Looper mWorkerLooper;
private Looper mMainLooper;
private int mOriginalScoringUiSettingValue;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = InstrumentationRegistry.getTargetContext();
mWorkerThread = new HandlerThread("TestHandlerWorkerThread");
mWorkerThread.start();
mWorkerLooper = mWorkerThread.getLooper();
mMainThread = new HandlerThread("TestHandlerThread");
mMainThread.start();
mMainLooper = mMainThread.getLooper();
// 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());
doAnswer(
new Answer<Void>() {
@Override
public Void answer (InvocationOnMock invocation) throws Throwable {
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 WifiTracker createTrackerWithImmediateBroadcastsAndInjectInitialScanResults(
Intent ... intents)
throws InterruptedException {
WifiTracker tracker = createMockedWifiTracker();
startTracking(tracker);
for (Intent intent : intents) {
tracker.mReceiver.onReceive(mContext, intent);
}
sendScanResultsAndProcess(tracker);
waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
return tracker;
}
private WifiTracker createMockedWifiTracker() {
WifiTracker tracker =
new WifiTracker(
mContext,
mockWifiListener,
mWorkerLooper,
true,
true,
true,
mockWifiManager,
mockConnectivityManager,
mockNetworkScoreManager,
mMainLooper
);
return tracker;
}
private void startTracking(WifiTracker tracker) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
mScannerHandler.post(new Runnable() {
@Override
public void run() {
tracker.startTracking();
latch.countDown();
}
});
assertTrue("Latch timed out", latch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
}
private void sendScanResultsAndProcess(WifiTracker tracker) throws InterruptedException {
mAccessPointsChangedLatch = new CountDownLatch(1);
Intent i = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
tracker.mReceiver.onReceive(mContext, i);
assertTrue("Latch timed out",
mAccessPointsChangedLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
}
private void updateScores() {
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));
CountDownLatch mainLatch = new CountDownLatch(1);
tracker.mMainHandler.post(() -> {
mainLatch.countDown();
});
assertTrue("Latch timed out while waiting for MainHandler",
mainLatch.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);
waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
}
@Test
public void testAccessPointListenerSetWhenLookingUpUsingScanResults() {
ScanResult scanResult = new ScanResult();
scanResult.level = 123;
scanResult.BSSID = "bssid-" + 111;
scanResult.timestamp = SystemClock.elapsedRealtime() * 1000;
scanResult.capabilities = "";
WifiTracker tracker = new WifiTracker(
InstrumentationRegistry.getTargetContext(), null, mWorkerLooper, true, true);
AccessPoint result = tracker.getCachedOrCreate(scanResult, new ArrayList<AccessPoint>());
assertTrue(result.mAccessPointListener != null);
}
@Test
public void testAccessPointListenerSetWhenLookingUpUsingWifiConfiguration() {
WifiConfiguration configuration = new WifiConfiguration();
configuration.SSID = "test123";
configuration.BSSID="bssid";
configuration.networkId = 123;
configuration.allowedKeyManagement = new BitSet();
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
WifiTracker tracker = new WifiTracker(
InstrumentationRegistry.getTargetContext(), null, mWorkerLooper, true, true);
AccessPoint result = tracker.getCachedOrCreate(configuration, new ArrayList<AccessPoint>());
assertTrue(result.mAccessPointListener != null);
}
@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.stopTracking();
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.stopTracking();
mRequestedKeys.clear();
mRequestScoresLatch = new CountDownLatch(1);
startTracking(tracker);
tracker.forceUpdate();
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();
updateScoresAndWaitForAccessPointsChangedCallback(tracker);
tracker.stopTracking();
assertThat(mScoreCacheCaptor.getValue().getScoredNetwork(NETWORK_KEY_1)).isNotNull();
}
@Test
public void scoreCacheUpdateScoresShouldTriggerOnAccessPointsChanged()
throws InterruptedException {
WifiTracker tracker = createMockedWifiTracker();
startTracking(tracker);
sendScanResultsAndProcess(tracker);
updateScoresAndWaitForAccessPointsChangedCallback(tracker);
}
private void updateScoresAndWaitForAccessPointsChangedCallback(WifiTracker tracker)
throws InterruptedException {
// Updating scores can happen together or one after the other, so the latch countdown is set
// to 2.
mAccessPointsChangedLatch = new CountDownLatch(1);
updateScores();
assertTrue("onAccessPointChanged was not called after updating scores",
mAccessPointsChangedLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
}
@FlakyTest
@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);
updateScoresAndWaitForAccessPointsChangedCallback(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);
updateScoresAndWaitForAccessPointsChangedCallback(tracker);
aps = tracker.getAccessPoints();
assertTrue(aps.size() == 2);
assertEquals(aps.get(0).getSsidStr(), SSID_1);
assertEquals(aps.get(1).getSsidStr(), SSID_2);
}
@FlakyTest
@Test
public void scoreCacheUpdateScoresShouldInsertSpeedIntoAccessPoint()
throws InterruptedException {
WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
updateScoresAndWaitForAccessPointsChangedCallback(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();
updateScoresAndWaitForAccessPointsChangedCallback(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();
updateScoresAndWaitForAccessPointsChangedCallback(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(2);
WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
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);
sendScanResultsAndProcess(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.stopTracking();
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();
updateScores();
}
/**
* 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 {
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);
doThrow(new UnsupportedOperationException())
.when(mockWifiManager).getMatchingWifiConfig(any(ScanResult.class));
tracker.forceUpdate();
verify(mockWifiManager).getMatchingWifiConfig(any(ScanResult.class));
}
@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);
CountDownLatch latch = new CountDownLatch(1);
// Once the new info has been fetched, we need to wait for the access points to be copied
doAnswer(invocation -> {
latch.countDown();
mAccessPointsChangedLatch = new CountDownLatch(1);
return info;
}).when(mockWifiManager).getConnectionInfo();
tracker.mReceiver.onReceive(mContext, new Intent(WifiManager.RSSI_CHANGED_ACTION));
assertTrue("New connection info never retrieved",
latch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
assertTrue("onAccessPointsChanged never called",
mAccessPointsChangedLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
assertThat(tracker.getAccessPoints().get(0).getRssi()).isEqualTo(newRssi);
}
@Test
public void forceUpdateShouldSynchronouslyFetchLatestInformation() 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();
tracker.forceUpdate();
verify(mockWifiManager).getConnectionInfo();
verify(mockWifiManager, times(2)).getConfiguredNetworks();
verify(mockConnectivityManager).getNetworkInfo(any(Network.class));
verify(mockWifiListener, never()).onAccessPointsChanged(); // mStaleAccessPoints is true
assertThat(tracker.getAccessPoints().size()).isEqualTo(2);
assertThat(tracker.getAccessPoints().get(0).isActive()).isTrue();
}
@Test
public void stopTrackingShouldRemoveWifiListenerCallbacks() throws Exception {
WifiTracker tracker = createMockedWifiTracker();
startTracking(tracker);
CountDownLatch latch = new CountDownLatch(1);
CountDownLatch lock = new CountDownLatch(1);
tracker.mMainHandler.post(() -> {
try {
lock.await();
latch.countDown();
} catch (InterruptedException e) {
fail("Interrupted Exception while awaiting lock release: " + e);
}
});
// Enqueue messages
tracker.mMainHandler.sendEmptyMessage(
WifiTracker.MainHandler.MSG_ACCESS_POINT_CHANGED);
tracker.mMainHandler.sendEmptyMessage(
WifiTracker.MainHandler.MSG_CONNECTED_CHANGED);
tracker.mMainHandler.sendEmptyMessage(
WifiTracker.MainHandler.MSG_WIFI_STATE_CHANGED);
tracker.stopTracking();
verify(mockWifiListener, atMost(1)).onAccessPointsChanged();
verify(mockWifiListener, atMost(1)).onConnectedChanged();
verify(mockWifiListener, atMost(1)).onWifiStateChanged(anyInt());
lock.countDown();
assertTrue("Latch timed out", latch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
assertThat(tracker.mMainHandler.hasMessages(
WifiTracker.MainHandler.MSG_ACCESS_POINT_CHANGED)).isFalse();
assertThat(tracker.mMainHandler.hasMessages(
WifiTracker.MainHandler.MSG_CONNECTED_CHANGED)).isFalse();
assertThat(tracker.mMainHandler.hasMessages(
WifiTracker.MainHandler.MSG_WIFI_STATE_CHANGED)).isFalse();
verifyNoMoreInteractions(mockWifiListener);
}
@Test
public void stopTrackingShouldSetStaleBitWhichPreventsCallbacksUntilNextScanResult()
throws Exception {
WifiTracker tracker = createMockedWifiTracker();
startTracking(tracker);
waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
tracker.stopTracking();
waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
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));
waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
verify(mockWifiListener, never()).onAccessPointsChanged();
sendScanResultsAndProcess(tracker); // verifies onAccessPointsChanged is invoked
}
@Test
public void startTrackingShouldNotSendAnyCallbacksUntilScanResultsAreProcessed()
throws Exception {
WifiTracker tracker = createMockedWifiTracker();
startTracking(tracker);
waitForHandlersToProcessCurrentlyEnqueuedMessages(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));
waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
verify(mockWifiListener, never()).onAccessPointsChanged();
sendScanResultsAndProcess(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));
mAccessPointsChangedLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS);
waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
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);
waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
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);
waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
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();
}
}