blob: 94e95b14a67253a5151eeb8f7a5bfaf33a5828bf [file] [log] [blame]
/*
* Copyright (C) 2020 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 android.net.wifi.cts;
import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.Manifest.permission.READ_WIFI_CREDENTIAL;
import static android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE;
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
import static android.net.wifi.WifiUsabilityStatsEntry.ContentionTimeStats;
import static android.net.wifi.WifiUsabilityStatsEntry.PROBE_STATUS_FAILURE;
import static android.net.wifi.WifiUsabilityStatsEntry.PROBE_STATUS_NO_PROBE;
import static android.net.wifi.WifiUsabilityStatsEntry.PROBE_STATUS_SUCCESS;
import static android.net.wifi.WifiUsabilityStatsEntry.PROBE_STATUS_UNKNOWN;
import static android.net.wifi.WifiUsabilityStatsEntry.RadioStats;
import static android.net.wifi.WifiUsabilityStatsEntry.RateStats;
import static android.net.wifi.WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_BE;
import static android.net.wifi.WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_BK;
import static android.net.wifi.WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_VI;
import static android.net.wifi.WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_VO;
import static android.os.Process.myUid;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
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.junit.Assume.assumeTrue;
import android.annotation.NonNull;
import android.app.UiAutomation;
import android.content.Context;
import android.location.LocationManager;
import android.net.ConnectivityManager;
import android.net.DhcpOption;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConnectedSessionInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiNetworkSpecifier;
import android.net.wifi.WifiNetworkSuggestion;
import android.net.wifi.WifiSsid;
import android.net.wifi.WifiUsabilityStatsEntry;
import android.os.Build;
import android.platform.test.annotations.AppModeFull;
import android.support.test.uiautomator.UiDevice;
import android.telephony.TelephonyManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SdkSuppress;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.ApiLevelUtil;
import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.PropertyUtil;
import com.android.compatibility.common.util.ShellIdentityUtils;
import com.google.common.collect.Range;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Tests for wifi connected network scorer interface and usability stats.
*/
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
@LargeTest
@RunWith(AndroidJUnit4.class)
public class ConnectedNetworkScorerTest extends WifiJUnit4TestBase {
private Context mContext;
private WifiManager mWifiManager;
private ConnectivityManager mConnectivityManager;
private UiDevice mUiDevice;
private TestHelper mTestHelper;
private TelephonyManager mTelephonyManager;
private boolean mWasVerboseLoggingEnabled;
private static final int WIFI_CONNECT_TIMEOUT_MILLIS = 30_000;
private static final int TIMEOUT = 12_000;
private static final int WAIT_DURATION = 5_000;
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getInstrumentation().getContext();
// skip the test if WiFi is not supported
assumeTrue(WifiFeature.isWifiSupported(mContext));
mWifiManager = mContext.getSystemService(WifiManager.class);
assertThat(mWifiManager).isNotNull();
// Location mode must be enabled, otherwise the connection info will be redacted.
assertThat(Objects.requireNonNull(mContext.getSystemService(LocationManager.class))
.isLocationEnabled()).isTrue();
mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
// turn on verbose logging for tests
mWasVerboseLoggingEnabled = ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.isVerboseLoggingEnabled());
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.setVerboseLoggingEnabled(true));
// enable Wifi
if (!mWifiManager.isWifiEnabled()) {
ShellIdentityUtils.invokeWithShellPermissions(() -> mWifiManager.setWifiEnabled(true));
}
PollingCheck.check("Wifi not enabled", TIMEOUT, () -> mWifiManager.isWifiEnabled());
// turn screen on
mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
mTestHelper = new TestHelper(mContext, mUiDevice);
mTestHelper.turnScreenOn();
// Clear any existing app state before each test.
if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
}
// check we have >= 1 saved network
List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.getConfiguredNetworks());
assertWithMessage("Need at least one saved network").that(savedNetworks).isNotEmpty();
// ensure Wifi is connected
ShellIdentityUtils.invokeWithShellPermissions(() -> mWifiManager.reconnect());
PollingCheck.check(
"Wifi not connected",
WIFI_CONNECT_TIMEOUT_MILLIS,
() -> mWifiManager.getConnectionInfo().getNetworkId() != -1);
mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
}
@After
public void tearDown() throws Exception {
if (!WifiFeature.isWifiSupported(mContext)) return;
if (!mWifiManager.isWifiEnabled()) {
ShellIdentityUtils.invokeWithShellPermissions(() -> mWifiManager.setWifiEnabled(true));
}
mTestHelper.turnScreenOff();
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.setVerboseLoggingEnabled(mWasVerboseLoggingEnabled));
}
private static class TestUsabilityStatsListener implements
WifiManager.OnWifiUsabilityStatsListener {
private final CountDownLatch mCountDownLatch;
public int seqNum;
public boolean isSameBssidAndFre;
public WifiUsabilityStatsEntry statsEntry;
TestUsabilityStatsListener(CountDownLatch countDownLatch) {
mCountDownLatch = countDownLatch;
}
@Override
public void onWifiUsabilityStats(int seqNum, boolean isSameBssidAndFreq,
WifiUsabilityStatsEntry statsEntry) {
this.seqNum = seqNum;
this.isSameBssidAndFre = isSameBssidAndFreq;
this.statsEntry = statsEntry;
mCountDownLatch.countDown();
}
}
/**
* Tests the {@link android.net.wifi.WifiUsabilityStatsEntry} retrieved from
* {@link WifiManager.OnWifiUsabilityStatsListener}.
*/
@Test
public void testWifiUsabilityStatsEntry() throws Exception {
// Usability stats collection only supported by vendor version Q and above.
if (!PropertyUtil.isVendorApiLevelAtLeast(Build.VERSION_CODES.Q)) {
return;
}
CountDownLatch countDownLatch = new CountDownLatch(1);
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
TestUsabilityStatsListener usabilityStatsListener =
new TestUsabilityStatsListener(countDownLatch);
try {
uiAutomation.adoptShellPermissionIdentity();
mWifiManager.addOnWifiUsabilityStatsListener(
Executors.newSingleThreadExecutor(), usabilityStatsListener);
// Wait for new usability stats (while connected & screen on this is triggered
// by platform periodically).
assertThat(countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue();
assertThat(usabilityStatsListener.statsEntry).isNotNull();
WifiUsabilityStatsEntry statsEntry = usabilityStatsListener.statsEntry;
assertThat(statsEntry.getTimeStampMillis()).isGreaterThan(0L);
assertThat(statsEntry.getRssi()).isLessThan(0);
assertThat(statsEntry.getLinkSpeedMbps()).isAtLeast(0);
assertThat(statsEntry.getTotalTxSuccess()).isAtLeast(0L);
assertThat(statsEntry.getTotalTxRetries()).isAtLeast(0L);
assertThat(statsEntry.getTotalTxBad()).isAtLeast(0L);
assertThat(statsEntry.getTotalRxSuccess()).isAtLeast(0L);
if (mWifiManager.isEnhancedPowerReportingSupported()) {
assertThat(statsEntry.getTotalRadioOnTimeMillis()).isAtLeast(0L);
assertThat(statsEntry.getTotalRadioTxTimeMillis()).isAtLeast(0L);
assertThat(statsEntry.getTotalRadioRxTimeMillis()).isAtLeast(0L);
assertThat(statsEntry.getTotalScanTimeMillis()).isAtLeast(0L);
assertThat(statsEntry.getTotalNanScanTimeMillis()).isAtLeast(0L);
assertThat(statsEntry.getTotalBackgroundScanTimeMillis()).isAtLeast(0L);
assertThat(statsEntry.getTotalRoamScanTimeMillis()).isAtLeast(0L);
assertThat(statsEntry.getTotalPnoScanTimeMillis()).isAtLeast(0L);
assertThat(statsEntry.getTotalHotspot2ScanTimeMillis()).isAtLeast(0L);
assertThat(statsEntry.getTotalCcaBusyFreqTimeMillis()).isAtLeast(0L);
assertThat(statsEntry.getTotalRadioOnTimeMillis()).isAtLeast(0L);
assertThat(statsEntry.getTotalRadioOnFreqTimeMillis()).isAtLeast(0L);
assertThat(statsEntry.getTotalBeaconRx()).isAtLeast(0L);
assertThat(statsEntry.getProbeStatusSinceLastUpdate())
.isAnyOf(PROBE_STATUS_SUCCESS,
PROBE_STATUS_FAILURE,
PROBE_STATUS_NO_PROBE,
PROBE_STATUS_UNKNOWN);
// -1 is default value for some of these fields if they're not available.
assertThat(statsEntry.getProbeElapsedTimeSinceLastUpdateMillis()).isAtLeast(-1);
assertThat(statsEntry.getProbeMcsRateSinceLastUpdate()).isAtLeast(-1);
assertThat(statsEntry.getRxLinkSpeedMbps()).isAtLeast(-1);
if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S)) {
try {
assertThat(statsEntry.getTimeSliceDutyCycleInPercent())
.isIn(Range.closed(0, 100));
} catch (NoSuchElementException e) {
// pass - Device does not support the field.
}
assertThat(statsEntry.getContentionTimeStats(
WME_ACCESS_CATEGORY_BE).getContentionTimeMinMicros()).isAtLeast(0);
assertThat(statsEntry.getContentionTimeStats(
WME_ACCESS_CATEGORY_BE).getContentionTimeMaxMicros()).isAtLeast(0);
assertThat(statsEntry.getContentionTimeStats(
WME_ACCESS_CATEGORY_BE).getContentionTimeAvgMicros()).isAtLeast(0);
assertThat(statsEntry.getContentionTimeStats(
WME_ACCESS_CATEGORY_BE).getContentionNumSamples()).isAtLeast(0);
assertThat(statsEntry.getContentionTimeStats(
WME_ACCESS_CATEGORY_BK).getContentionTimeMinMicros()).isAtLeast(0);
assertThat(statsEntry.getContentionTimeStats(
WME_ACCESS_CATEGORY_BK).getContentionTimeMaxMicros()).isAtLeast(0);
assertThat(statsEntry.getContentionTimeStats(
WME_ACCESS_CATEGORY_BK).getContentionTimeAvgMicros()).isAtLeast(0);
assertThat(statsEntry.getContentionTimeStats(
WME_ACCESS_CATEGORY_BK).getContentionNumSamples()).isAtLeast(0);
assertThat(statsEntry.getContentionTimeStats(
WME_ACCESS_CATEGORY_VI).getContentionTimeMinMicros()).isAtLeast(0);
assertThat(statsEntry.getContentionTimeStats(
WME_ACCESS_CATEGORY_VI).getContentionTimeMaxMicros()).isAtLeast(0);
assertThat(statsEntry.getContentionTimeStats(
WME_ACCESS_CATEGORY_VI).getContentionTimeAvgMicros()).isAtLeast(0);
assertThat(statsEntry.getContentionTimeStats(
WME_ACCESS_CATEGORY_VI).getContentionNumSamples()).isAtLeast(0);
assertThat(statsEntry.getContentionTimeStats(
WME_ACCESS_CATEGORY_VO).getContentionTimeMinMicros()).isAtLeast(0);
assertThat(statsEntry.getContentionTimeStats(
WME_ACCESS_CATEGORY_VO).getContentionTimeMaxMicros()).isAtLeast(0);
assertThat(statsEntry.getContentionTimeStats(
WME_ACCESS_CATEGORY_VO).getContentionTimeAvgMicros()).isAtLeast(0);
assertThat(statsEntry.getContentionTimeStats(
WME_ACCESS_CATEGORY_VO).getContentionNumSamples()).isAtLeast(0);
// This is to add CTS test for the constructor function.
ContentionTimeStats contentionStats = new ContentionTimeStats(2, 1, 4, 10);
assertEquals(2, contentionStats.getContentionTimeMinMicros());
assertEquals(1, contentionStats.getContentionTimeMaxMicros());
assertEquals(4, contentionStats.getContentionTimeAvgMicros());
assertEquals(10, contentionStats.getContentionNumSamples());
// Note that -1 is also a possible returned value for utilization ratio.
assertThat(statsEntry.getChannelUtilizationRatio()).isIn(Range.closed(-1, 255));
if (mTelephonyManager != null) {
boolean isCellularDataAvailable =
mTelephonyManager.getDataState() == TelephonyManager.DATA_CONNECTED;
assertEquals(isCellularDataAvailable, statsEntry.isCellularDataAvailable());
} else {
assertFalse(statsEntry.isCellularDataAvailable());
}
statsEntry.isWifiScoringEnabled();
statsEntry.isThroughputSufficient();
RateStats rateStats = new RateStats(WifiUsabilityStatsEntry.WIFI_PREAMBLE_VHT,
WifiUsabilityStatsEntry.WIFI_SPATIAL_STREAMS_TWO,
WifiUsabilityStatsEntry.WIFI_BANDWIDTH_40_MHZ,
2, 20, 100, 200, 5, 10);
assertThat(statsEntry.getRateStats()).isNotNull();
if(statsEntry.getRateStats().size() > 0) {
assertThat(statsEntry.getRateStats().get(0).getPreamble()).isAtLeast(0);
assertThat(statsEntry.getRateStats().get(0).getNumberOfSpatialStreams())
.isAtLeast(1);
assertThat(statsEntry.getRateStats().get(0).getBandwidthInMhz())
.isAtLeast(0);
assertThat(statsEntry.getRateStats().get(0).getRateMcsIdx()).isAtLeast(0);
assertThat(statsEntry.getRateStats().get(0).getBitRateInKbps())
.isAtLeast(0);
assertThat(statsEntry.getRateStats().get(0).getTxMpdu()).isAtLeast(0);
assertThat(statsEntry.getRateStats().get(0).getRxMpdu()).isAtLeast(0);
assertThat(statsEntry.getRateStats().get(0).getMpduLost()).isAtLeast(0);
assertThat(statsEntry.getRateStats().get(0).getRetries()).isAtLeast(0);
}
RadioStats radioStat = new RadioStats(0, 10, 11, 12, 13, 14, 15, 16, 17, 18);
assertThat(statsEntry.getWifiLinkLayerRadioStats()).isNotNull();
int numRadios = statsEntry.getWifiLinkLayerRadioStats().size();
for (int i = 0; i < numRadios; i++) {
RadioStats radioStats = statsEntry.getWifiLinkLayerRadioStats().get(i);
assertThat(radioStats.getRadioId()).isAtLeast(0);
assertThat(radioStats.getTotalRadioOnTimeMillis()).isAtLeast(0);
assertThat(radioStats.getTotalRadioTxTimeMillis()).isAtLeast(0);
assertThat(radioStats.getTotalRadioRxTimeMillis()).isAtLeast(0);
assertThat(radioStats.getTotalScanTimeMillis()).isAtLeast(0);
assertThat(radioStats.getTotalNanScanTimeMillis()).isAtLeast(0);
assertThat(radioStats.getTotalBackgroundScanTimeMillis()).isAtLeast(0);
assertThat(radioStats.getTotalRoamScanTimeMillis()).isAtLeast(0);
assertThat(radioStats.getTotalPnoScanTimeMillis()).isAtLeast(0);
assertThat(radioStats.getTotalHotspot2ScanTimeMillis()).isAtLeast(0);
}
}
// no longer populated, return default value.
assertThat(statsEntry.getCellularDataNetworkType())
.isAnyOf(TelephonyManager.NETWORK_TYPE_UNKNOWN,
TelephonyManager.NETWORK_TYPE_GPRS,
TelephonyManager.NETWORK_TYPE_EDGE,
TelephonyManager.NETWORK_TYPE_UMTS,
TelephonyManager.NETWORK_TYPE_CDMA,
TelephonyManager.NETWORK_TYPE_EVDO_0,
TelephonyManager.NETWORK_TYPE_EVDO_A,
TelephonyManager.NETWORK_TYPE_1xRTT,
TelephonyManager.NETWORK_TYPE_HSDPA,
TelephonyManager.NETWORK_TYPE_HSUPA,
TelephonyManager.NETWORK_TYPE_HSPA,
TelephonyManager.NETWORK_TYPE_IDEN,
TelephonyManager.NETWORK_TYPE_EVDO_B,
TelephonyManager.NETWORK_TYPE_LTE,
TelephonyManager.NETWORK_TYPE_EHRPD,
TelephonyManager.NETWORK_TYPE_HSPAP,
TelephonyManager.NETWORK_TYPE_GSM,
TelephonyManager.NETWORK_TYPE_TD_SCDMA,
TelephonyManager.NETWORK_TYPE_IWLAN,
TelephonyManager.NETWORK_TYPE_NR);
assertThat(statsEntry.getCellularSignalStrengthDbm()).isAtMost(0);
assertThat(statsEntry.getCellularSignalStrengthDb()).isAtMost(0);
assertThat(statsEntry.isSameRegisteredCell()).isFalse();
}
} finally {
mWifiManager.removeOnWifiUsabilityStatsListener(usabilityStatsListener);
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests the {@link android.net.wifi.WifiManager#updateWifiUsabilityScore(int, int, int)}
*/
@Test
public void testUpdateWifiUsabilityScore() throws Exception {
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
uiAutomation.adoptShellPermissionIdentity();
// update scoring with dummy values.
mWifiManager.updateWifiUsabilityScore(0, 50, 50);
} finally {
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests the {@link android.net.wifi.WifiManager#setWifiScoringEnabled(boolean)}
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
@Test
public void testSetWifiScoringEnabled() throws Exception {
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
uiAutomation.adoptShellPermissionIdentity();
assertTrue(mWifiManager.setWifiScoringEnabled(true));
} finally {
uiAutomation.dropShellPermissionIdentity();
}
}
private static abstract class TestConnectedNetworkScorer implements
WifiManager.WifiConnectedNetworkScorer {
protected CountDownLatch mCountDownLatch;
public Integer startSessionId;
public Integer stopSessionId;
public WifiManager.ScoreUpdateObserver scoreUpdateObserver;
public boolean isUserSelected;
TestConnectedNetworkScorer(CountDownLatch countDownLatch) {
mCountDownLatch = countDownLatch;
}
@Override
public void onStop(int sessionId) {
synchronized (mCountDownLatch) {
this.stopSessionId = sessionId;
mCountDownLatch.countDown();
}
}
@Override
public void onSetScoreUpdateObserver(WifiManager.ScoreUpdateObserver observerImpl) {
synchronized (mCountDownLatch) {
this.scoreUpdateObserver = observerImpl;
}
}
public void resetCountDownLatch(CountDownLatch countDownLatch) {
synchronized (mCountDownLatch) {
mCountDownLatch = countDownLatch;
}
}
}
private static class TestConnectedNetworkScorerWithSessionId extends
TestConnectedNetworkScorer {
TestConnectedNetworkScorerWithSessionId(CountDownLatch countDownLatch) {
super(countDownLatch);
isUserSelected = false;
}
@Override
public void onStart(int sessionId) {
synchronized (mCountDownLatch) {
this.startSessionId = sessionId;
mCountDownLatch.countDown();
}
}
}
private static class TestConnectedNetworkScorerWithSessionInfo extends
TestConnectedNetworkScorer {
TestConnectedNetworkScorerWithSessionInfo(CountDownLatch countDownLatch) {
super(countDownLatch);
}
@Override
public void onStart(WifiConnectedSessionInfo sessionInfo) {
synchronized (mCountDownLatch) {
this.startSessionId = sessionInfo.getSessionId();
this.isUserSelected = sessionInfo.isUserSelected();
// Build a WifiConnectedSessionInfo object
WifiConnectedSessionInfo.Builder sessionBuilder =
new WifiConnectedSessionInfo.Builder(startSessionId.intValue())
.setUserSelected(isUserSelected);
sessionBuilder.build();
mCountDownLatch.countDown();
}
}
}
/**
* Tests the
* {@link android.net.wifi.WifiConnectedNetworkScorer#onStart(WifiConnectedSessionInfo)}.
*/
@Test
public void testConnectedNetworkScorerWithSessionInfo() throws Exception {
CountDownLatch countDownLatchScorer = new CountDownLatch(1);
TestConnectedNetworkScorerWithSessionInfo connectedNetworkScorer =
new TestConnectedNetworkScorerWithSessionInfo(countDownLatchScorer);
testSetWifiConnectedNetworkScorer(connectedNetworkScorer, countDownLatchScorer);
}
/**
* Tests the {@link android.net.wifi.WifiConnectedNetworkScorer#onStart(int)}.
*/
@Test
public void testConnectedNetworkScorerWithSessionId() throws Exception {
CountDownLatch countDownLatchScorer = new CountDownLatch(1);
TestConnectedNetworkScorerWithSessionId connectedNetworkScorer =
new TestConnectedNetworkScorerWithSessionId(countDownLatchScorer);
testSetWifiConnectedNetworkScorer(connectedNetworkScorer, countDownLatchScorer);
}
/**
* Note: We could write more interesting test cases (if the device has a mobile connection), but
* that would make the test flaky. The default network/route selection on the device is not just
* controlled by the wifi scorer input, but also based on params which are controlled by
* other parts of the platform (likely in connectivity service) and hence will behave
* differently on OEM devices.
*/
private void testSetWifiConnectedNetworkScorer(
TestConnectedNetworkScorer connectedNetworkScorer,
CountDownLatch countDownLatchScorer) throws Exception {
CountDownLatch countDownLatchUsabilityStats = new CountDownLatch(1);
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
TestUsabilityStatsListener usabilityStatsListener =
new TestUsabilityStatsListener(countDownLatchUsabilityStats);
boolean disconnected = false;
try {
uiAutomation.adoptShellPermissionIdentity();
// Clear any external scorer already active on the device.
mWifiManager.clearWifiConnectedNetworkScorer();
Thread.sleep(500);
mWifiManager.setWifiConnectedNetworkScorer(
Executors.newSingleThreadExecutor(), connectedNetworkScorer);
// Since we're already connected, wait for onStart to be invoked.
assertThat(countDownLatchScorer.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue();
assertThat(connectedNetworkScorer.startSessionId).isAtLeast(0);
assertThat(connectedNetworkScorer.isUserSelected).isEqualTo(false);
assertThat(connectedNetworkScorer.scoreUpdateObserver).isNotNull();
WifiManager.ScoreUpdateObserver scoreUpdateObserver =
connectedNetworkScorer.scoreUpdateObserver;
// Now trigger a dummy score update.
scoreUpdateObserver.notifyScoreUpdate(connectedNetworkScorer.startSessionId, 50);
// Register the usability listener
mWifiManager.addOnWifiUsabilityStatsListener(
Executors.newSingleThreadExecutor(), usabilityStatsListener);
// Trigger a usability stats update.
scoreUpdateObserver.triggerUpdateOfWifiUsabilityStats(
connectedNetworkScorer.startSessionId);
// Ensure that we got the stats update callback.
assertThat(countDownLatchUsabilityStats.await(TIMEOUT, TimeUnit.MILLISECONDS))
.isTrue();
assertThat(usabilityStatsListener.seqNum).isAtLeast(0);
// Reset the scorer countdown latch for onStop
countDownLatchScorer = new CountDownLatch(1);
connectedNetworkScorer.resetCountDownLatch(countDownLatchScorer);
if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S)) {
// Notify status change and request a NUD check
scoreUpdateObserver.notifyStatusUpdate(
connectedNetworkScorer.startSessionId, false);
scoreUpdateObserver.requestNudOperation(connectedNetworkScorer.startSessionId);
// Blocklist current AP with invalid session Id
scoreUpdateObserver.blocklistCurrentBssid(-1);
}
// Now disconnect from the network.
mWifiManager.disconnect();
// Wait for it to be disconnected.
PollingCheck.check(
"Wifi not disconnected",
TIMEOUT,
() -> mWifiManager.getConnectionInfo().getNetworkId() == -1);
disconnected = true;
// Wait for stop to be invoked and ensure that the session id matches.
assertThat(countDownLatchScorer.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue();
assertThat(connectedNetworkScorer.stopSessionId)
.isEqualTo(connectedNetworkScorer.startSessionId);
// Verify that onStart() and onStop() set internal variables correctly.
connectedNetworkScorer.onStart(
new WifiConnectedSessionInfo.Builder(100)
.setUserSelected(false)
.build());
assertEquals(100, connectedNetworkScorer.startSessionId.intValue());
assertEquals(false, connectedNetworkScorer.isUserSelected);
connectedNetworkScorer.onStop(200);
assertEquals(200, connectedNetworkScorer.stopSessionId.intValue());
} finally {
mWifiManager.removeOnWifiUsabilityStatsListener(usabilityStatsListener);
mWifiManager.clearWifiConnectedNetworkScorer();
if (disconnected) {
mWifiManager.reconnect();
// Wait for it to be reconnected.
PollingCheck.check(
"Wifi not reconnected",
WIFI_CONNECT_TIMEOUT_MILLIS,
() -> mWifiManager.getConnectionInfo().getNetworkId() != -1);
}
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests the {@link android.net.wifi.WifiConnectedNetworkScorer} interface.
*
* Verifies that the external scorer works even after wifi restart.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
@Test
public void testSetWifiConnectedNetworkScorerOnSubsystemRestart() throws Exception {
CountDownLatch countDownLatchScorer = new CountDownLatch(1);
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
TestConnectedNetworkScorerWithSessionInfo connectedNetworkScorer =
new TestConnectedNetworkScorerWithSessionInfo(countDownLatchScorer);
try {
uiAutomation.adoptShellPermissionIdentity();
// Clear any external scorer already active on the device.
mWifiManager.clearWifiConnectedNetworkScorer();
Thread.sleep(500);
mWifiManager.setWifiConnectedNetworkScorer(
Executors.newSingleThreadExecutor(), connectedNetworkScorer);
// Since we're already connected, wait for onStart to be invoked.
assertThat(countDownLatchScorer.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue();
int prevSessionId = connectedNetworkScorer.startSessionId;
WifiManager.ScoreUpdateObserver prevScoreUpdateObserver =
connectedNetworkScorer.scoreUpdateObserver;
// Expect one stop followed by one start after the restart
// Ensure that we got an onStop() for the previous connection when restart is invoked.
countDownLatchScorer = new CountDownLatch(1);
connectedNetworkScorer.resetCountDownLatch(countDownLatchScorer);
// Restart wifi subsystem.
mWifiManager.restartWifiSubsystem();
// wait for scorer to stop session due to network disconnection.
assertThat(countDownLatchScorer.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue();
assertThat(connectedNetworkScorer.stopSessionId).isEqualTo(prevSessionId);
// Wait for the device to connect back.
PollingCheck.check(
"Wifi not connected",
WIFI_CONNECT_TIMEOUT_MILLIS * 2,
() -> mWifiManager.getConnectionInfo().getNetworkId() != -1);
// Followed by a new onStart() after the connection.
// Note: There is a 5 second delay between stop/start when restartWifiSubsystem() is
// invoked, so this should not be racy.
countDownLatchScorer = new CountDownLatch(1);
connectedNetworkScorer.resetCountDownLatch(countDownLatchScorer);
assertThat(countDownLatchScorer.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue();
assertThat(connectedNetworkScorer.startSessionId).isNotEqualTo(prevSessionId);
// Ensure that we did not get a new score update observer.
assertThat(connectedNetworkScorer.scoreUpdateObserver).isSameInstanceAs(
prevScoreUpdateObserver);
} finally {
mWifiManager.clearWifiConnectedNetworkScorer();
uiAutomation.dropShellPermissionIdentity();
}
}
private interface ConnectionInitiator {
/**
* Trigger connection (using suggestion or specifier) to the provided network.
*/
ConnectivityManager.NetworkCallback initiateConnection(
@NonNull WifiConfiguration testNetwork,
@NonNull ScheduledExecutorService executorService) throws Exception;
}
private void setWifiConnectedNetworkScorerAndInitiateConnectToSpecifierOrRestrictedSuggestion(
@NonNull ConnectionInitiator connectionInitiator) throws Exception {
CountDownLatch countDownLatchScorer = new CountDownLatch(1);
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
TestConnectedNetworkScorerWithSessionInfo connectedNetworkScorer =
new TestConnectedNetworkScorerWithSessionInfo(countDownLatchScorer);
ConnectivityManager.NetworkCallback networkCallback = null;
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
List<WifiConfiguration> savedNetworks = null;
try {
uiAutomation.adoptShellPermissionIdentity(
NETWORK_SETTINGS, WIFI_UPDATE_USABILITY_STATS_SCORE, CONNECTIVITY_INTERNAL,
READ_WIFI_CREDENTIAL);
// Clear any external scorer already active on the device.
mWifiManager.clearWifiConnectedNetworkScorer();
Thread.sleep(500);
savedNetworks = mWifiManager.getPrivilegedConfiguredNetworks();
WifiConfiguration testNetwork =
TestHelper.findMatchingSavedNetworksWithBssid(mWifiManager, savedNetworks)
.get(0);
// Disconnect & disable auto-join on the saved network to prevent auto-connect from
// interfering with the test.
for (WifiConfiguration savedNetwork : savedNetworks) {
mWifiManager.disableNetwork(savedNetwork.networkId);
}
// Wait for Wifi to be disconnected.
PollingCheck.check(
"Wifi not disconnected",
20000,
() -> mWifiManager.getConnectionInfo().getNetworkId() == -1);
assertThat(testNetwork).isNotNull();
// Register the external scorer.
mWifiManager.setWifiConnectedNetworkScorer(
Executors.newSingleThreadExecutor(), connectedNetworkScorer);
// Now connect using the provided connection initiator
networkCallback = connectionInitiator.initiateConnection(testNetwork, executorService);
// We should not receive the start
assertThat(countDownLatchScorer.await(WAIT_DURATION, TimeUnit.MILLISECONDS)).isFalse();
assertThat(connectedNetworkScorer.startSessionId).isNull();
// Now disconnect from the network.
mConnectivityManager.unregisterNetworkCallback(networkCallback);
networkCallback = null;
// We should not receive the stop either
countDownLatchScorer = new CountDownLatch(1);
connectedNetworkScorer.resetCountDownLatch(countDownLatchScorer);
assertThat(countDownLatchScorer.await(WAIT_DURATION, TimeUnit.MILLISECONDS)).isFalse();
assertThat(connectedNetworkScorer.stopSessionId).isNull();
} finally {
executorService.shutdownNow();
mWifiManager.clearWifiConnectedNetworkScorer();
if (networkCallback != null) {
mConnectivityManager.unregisterNetworkCallback(networkCallback);
}
// Re-enable the networks after the test.
if (savedNetworks != null) {
for (WifiConfiguration savedNetwork : savedNetworks) {
mWifiManager.enableNetwork(savedNetwork.networkId, false);
}
}
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests the {@link android.net.wifi.WifiConnectedNetworkScorer} interface.
*
* Verifies that the external scorer is not notified for local only connections.
*/
@Test
public void testSetWifiConnectedNetworkScorerForSpecifierConnection() throws Exception {
setWifiConnectedNetworkScorerAndInitiateConnectToSpecifierOrRestrictedSuggestion(
(testNetwork, executorService) -> {
// Connect using wifi network specifier.
WifiNetworkSpecifier specifier =
TestHelper.createSpecifierBuilderWithCredentialFromSavedNetwork(
testNetwork)
.build();
return mTestHelper.testConnectionFlowWithSpecifierWithShellIdentity(
testNetwork, specifier, false);
}
);
}
private void testSetWifiConnectedNetworkScorerForRestrictedSuggestionConnection(
Set<Integer> restrictedNetworkCapabilities) throws Exception {
setWifiConnectedNetworkScorerAndInitiateConnectToSpecifierOrRestrictedSuggestion(
(testNetwork, executorService) -> {
// Connect using wifi network suggestion.
WifiNetworkSuggestion.Builder suggestionBuilder =
TestHelper
.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
testNetwork);
if (restrictedNetworkCapabilities.contains(NET_CAPABILITY_OEM_PAID)) {
suggestionBuilder.setOemPaid(true);
}
if (restrictedNetworkCapabilities.contains(NET_CAPABILITY_OEM_PRIVATE)) {
suggestionBuilder.setOemPrivate(true);
}
return mTestHelper.testConnectionFlowWithSuggestionWithShellIdentity(
testNetwork, suggestionBuilder.build(), executorService,
restrictedNetworkCapabilities, false/* restrictedNetwork */);
}
);
}
/**
* Tests the {@link android.net.wifi.WifiConnectedNetworkScorer} interface.
*
* Verifies that the external scorer is not notified for oem paid suggestion connections.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
@Test
public void testSetWifiConnectedNetworkScorerForOemPaidSuggestionConnection() throws Exception {
testSetWifiConnectedNetworkScorerForRestrictedSuggestionConnection(
Set.of(NET_CAPABILITY_OEM_PAID));
}
/**
* Tests the {@link android.net.wifi.WifiConnectedNetworkScorer} interface.
*
* Verifies that the external scorer is not notified for oem private suggestion connections.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
@Test
public void testSetWifiConnectedNetworkScorerForOemPrivateSuggestionConnection()
throws Exception {
testSetWifiConnectedNetworkScorerForRestrictedSuggestionConnection(
Set.of(NET_CAPABILITY_OEM_PRIVATE));
}
/**
* Tests the
* {@link android.net.wifi.WifiManager#addCustomDhcpOptions(Object, Object, List)} and
* {@link android.net.wifi.WifiManager#removeCustomDhcpOptions(Object, Object)}.
*
* Verifies that these APIs can be invoked successfully with permissions.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
@Test
public void testAddAndRemoveCustomDhcpOptions() throws Exception {
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
uiAutomation.adoptShellPermissionIdentity();
WifiSsid ssid = WifiSsid.fromBytes(new byte[]{0x12, 0x34, 0x56});
byte[] oui = new byte[]{0x00, 0x01, 0x02};
List<DhcpOption> options = new ArrayList<DhcpOption>();
mWifiManager.addCustomDhcpOptions(ssid, oui, options);
mWifiManager.removeCustomDhcpOptions(ssid, oui);
} finally {
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests the
* {@link android.net.wifi.WifiManager#addCustomDhcpOptions(Object, Object, List)}.
*
* Verifies that SecurityException is thrown when permissions are missing.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
@Test
public void testAddCustomDhcpOptionsOnMissingPermissions() throws Exception {
try {
WifiSsid ssid = WifiSsid.fromBytes(new byte[]{0x12, 0x34, 0x56});
byte[] oui = new byte[]{0x00, 0x01, 0x02};
List<DhcpOption> options = new ArrayList<DhcpOption>();
mWifiManager.addCustomDhcpOptions(ssid, oui, options);
fail("Expected SecurityException");
} catch (SecurityException e) {
// expected
}
}
}