blob: 0a83f248876d5eca346d12048b758b103bdda2f8 [file] [log] [blame]
/*
* Copyright (C) 2009 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.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.wifi.WifiAvailableChannel.OP_MODE_SAP;
import static android.net.wifi.WifiAvailableChannel.OP_MODE_STA;
import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
import static android.net.wifi.WifiManager.COEX_RESTRICTION_SOFTAP;
import static android.net.wifi.WifiManager.COEX_RESTRICTION_WIFI_AWARE;
import static android.net.wifi.WifiManager.COEX_RESTRICTION_WIFI_DIRECT;
import static android.net.wifi.WifiScanner.WIFI_BAND_24_GHZ;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThrows;
import android.annotation.NonNull;
import android.app.UiAutomation;
import android.app.admin.DevicePolicyManager;
import android.app.admin.WifiSsidPolicy;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.ConnectivityManager;
import android.net.MacAddress;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.net.TetheringManager;
import android.net.Uri;
import android.net.wifi.CoexUnsafeChannel;
import android.net.wifi.ScanResult;
import android.net.wifi.SoftApCapability;
import android.net.wifi.SoftApConfiguration;
import android.net.wifi.SoftApInfo;
import android.net.wifi.WifiAvailableChannel;
import android.net.wifi.WifiClient;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiEnterpriseConfig;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.SubsystemRestartTrackingCallback;
import android.net.wifi.WifiManager.WifiLock;
import android.net.wifi.WifiNetworkConnectionStatistics;
import android.net.wifi.WifiNetworkSuggestion;
import android.net.wifi.WifiScanner;
import android.net.wifi.WifiSsid;
import android.net.wifi.hotspot2.ConfigParser;
import android.net.wifi.hotspot2.OsuProvider;
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.net.wifi.hotspot2.ProvisioningCallback;
import android.net.wifi.hotspot2.pps.Credential;
import android.net.wifi.hotspot2.pps.HomeSp;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.HandlerThread;
import android.os.Process;
import android.os.SystemClock;
import android.os.UserHandle;
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.AsbSecurityTest;
import android.provider.Settings;
import android.support.test.uiautomator.UiDevice;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;
import androidx.test.filters.SdkSuppress;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.ApiLevelUtil;
import com.android.compatibility.common.util.FeatureUtil;
import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.PropertyUtil;
import com.android.compatibility.common.util.ShellIdentityUtils;
import com.android.compatibility.common.util.SystemUtil;
import com.android.compatibility.common.util.ThrowingRunnable;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.MacAddressUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
public class WifiManagerTest extends WifiJUnit3TestBase {
private static class MySync {
int expectedState = STATE_NULL;
}
private WifiManager mWifiManager;
private ConnectivityManager mConnectivityManager;
private TetheringManager mTetheringManager;
private WifiLock mWifiLock;
private static MySync mMySync;
private List<ScanResult> mScanResults = null;
private NetworkInfo mNetworkInfo =
new NetworkInfo(ConnectivityManager.TYPE_WIFI, TelephonyManager.NETWORK_TYPE_UNKNOWN,
"wifi", "unknown");
private final Object mLock = new Object();
private UiDevice mUiDevice;
private boolean mWasVerboseLoggingEnabled;
private boolean mWasScanThrottleEnabled;
private SoftApConfiguration mOriginalSoftApConfig = null;
// Please refer to WifiManager
private static final int MIN_RSSI = -100;
private static final int MAX_RSSI = -55;
private static final int STATE_NULL = 0;
private static final int STATE_WIFI_CHANGING = 1;
private static final int STATE_WIFI_ENABLED = 2;
private static final int STATE_WIFI_DISABLED = 3;
private static final int STATE_SCANNING = 4;
private static final int STATE_SCAN_DONE = 5;
private static final String TAG = "WifiManagerTest";
private static final String SSID1 = "\"WifiManagerTest\"";
// A full single scan duration is typically about 6-7 seconds, but
// depending on devices it takes more time (9-11 seconds). For a
// safety margin, the test waits for 15 seconds.
private static final int SCAN_TEST_WAIT_DURATION_MS = 15_000;
private static final int TEST_WAIT_DURATION_MS = 10_000;
private static final int WIFI_CONNECT_TIMEOUT_MILLIS = 30_000;
private static final int WIFI_PNO_CONNECT_TIMEOUT_MILLIS = 90_000;
private static final int WAIT_MSEC = 60;
private static final int DURATION_SCREEN_TOGGLE = 2000;
private static final int DURATION_SETTINGS_TOGGLE = 1_000;
private static final int DURATION_SOFTAP_START_MS = 6_000;
private static final int WIFI_SCAN_TEST_CACHE_DELAY_MILLIS = 3 * 60 * 1000;
private static final int ENFORCED_NUM_NETWORK_SUGGESTIONS_PER_APP = 50;
private static final String TEST_PAC_URL = "http://www.example.com/proxy.pac";
private static final String MANAGED_PROVISIONING_PACKAGE_NAME
= "com.android.managedprovisioning";
private static final String TEST_SSID_UNQUOTED = "testSsid1";
private static final String TEST_IP_ADDRESS = "192.168.5.5";
private static final String TEST_MAC_ADDRESS = "aa:bb:cc:dd:ee:ff";
private static final MacAddress TEST_MAC = MacAddress.fromString(TEST_MAC_ADDRESS);
private static final String TEST_PASSPHRASE = "passphrase";
private static final String PASSPOINT_INSTALLATION_FILE_WITH_CA_CERT =
"assets/ValidPasspointProfile.base64";
private static final String TYPE_WIFI_CONFIG = "application/x-wifi-config";
private static final String TEST_PSK_CAP = "[RSN-PSK-CCMP]";
private static final String TEST_BSSID = "00:01:02:03:04:05";
private static final String TEST_COUNTRY_CODE = "JP";
private static final String TEST_DOM_SUBJECT_MATCH = "domSubjectMatch";
private static final int TEST_SUB_ID = 2;
private static final int EID_VSA = 221; // Copied from ScanResult.InformationElement
private static final List<ScanResult.InformationElement> TEST_VENDOR_ELEMENTS =
new ArrayList<>(Arrays.asList(
new ScanResult.InformationElement(221, 0, new byte[]{ 1, 2, 3, 4 }),
new ScanResult.InformationElement(
221,
0,
new byte[]{ (byte) 170, (byte) 187, (byte) 204, (byte) 221 })
));
private IntentFilter mIntentFilter;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
synchronized (mMySync) {
if (intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false)) {
mScanResults = mWifiManager.getScanResults();
} else {
mScanResults = null;
}
mMySync.expectedState = STATE_SCAN_DONE;
mMySync.notifyAll();
}
} else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
int newState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
WifiManager.WIFI_STATE_UNKNOWN);
synchronized (mMySync) {
if (newState == WifiManager.WIFI_STATE_ENABLED) {
Log.d(TAG, "*** New WiFi state is ENABLED ***");
mMySync.expectedState = STATE_WIFI_ENABLED;
mMySync.notifyAll();
} else if (newState == WifiManager.WIFI_STATE_DISABLED) {
Log.d(TAG, "*** New WiFi state is DISABLED ***");
mMySync.expectedState = STATE_WIFI_DISABLED;
mMySync.notifyAll();
}
}
} else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
synchronized (mMySync) {
mNetworkInfo =
(NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
if (mNetworkInfo.getState() == NetworkInfo.State.CONNECTED)
mMySync.notifyAll();
}
}
}
};
// Initialize with an invalid status value (0)
private int mProvisioningStatus = 0;
// Initialize with an invalid status value (0)
private int mProvisioningFailureStatus = 0;
private boolean mProvisioningComplete = false;
private ProvisioningCallback mProvisioningCallback = new ProvisioningCallback() {
@Override
public void onProvisioningFailure(int status) {
synchronized (mLock) {
mProvisioningFailureStatus = status;
mLock.notify();
}
}
@Override
public void onProvisioningStatus(int status) {
synchronized (mLock) {
mProvisioningStatus = status;
mLock.notify();
}
}
@Override
public void onProvisioningComplete() {
mProvisioningComplete = true;
}
};
private int mSubsystemRestartStatus = 0; // 0: nada, 1: restarting, 2: restarted
private SubsystemRestartTrackingCallback mSubsystemRestartTrackingCallback =
new SubsystemRestartTrackingCallback() {
@Override
public void onSubsystemRestarting() {
synchronized (mLock) {
mSubsystemRestartStatus = 1;
mLock.notify();
}
}
@Override
public void onSubsystemRestarted() {
synchronized (mLock) {
mSubsystemRestartStatus = 2;
mLock.notify();
}
}
};
private static final String TEST_SSID = "TEST SSID";
private static final String TEST_FRIENDLY_NAME = "Friendly Name";
private static final Map<String, String> TEST_FRIENDLY_NAMES =
new HashMap<String, String>() {
{
put("en", TEST_FRIENDLY_NAME);
put("kr", TEST_FRIENDLY_NAME + 2);
put("jp", TEST_FRIENDLY_NAME + 3);
}
};
private static final String TEST_SERVICE_DESCRIPTION = "Dummy Service";
private static final Uri TEST_SERVER_URI = Uri.parse("https://test.com");
private static final String TEST_NAI = "test.access.com";
private static final List<Integer> TEST_METHOD_LIST =
Arrays.asList(1 /* METHOD_SOAP_XML_SPP */);
private final HandlerThread mHandlerThread = new HandlerThread("WifiManagerTest");
protected final Executor mExecutor;
{
mHandlerThread.start();
mExecutor = new HandlerExecutor(new Handler(mHandlerThread.getLooper()));
}
@Override
protected void setUp() throws Exception {
super.setUp();
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
mMySync = new MySync();
mIntentFilter = new IntentFilter();
mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
mIntentFilter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
mIntentFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
mIntentFilter.addAction(WifiManager.ACTION_PICK_WIFI_NETWORK);
mContext.registerReceiver(mReceiver, mIntentFilter);
mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
mConnectivityManager = getContext().getSystemService(ConnectivityManager.class);
mTetheringManager = getContext().getSystemService(TetheringManager.class);
assertNotNull(mWifiManager);
assertNotNull(mTetheringManager);
// turn on verbose logging for tests
mWasVerboseLoggingEnabled = ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.isVerboseLoggingEnabled());
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.setVerboseLoggingEnabled(true));
// Disable scan throttling for tests.
mWasScanThrottleEnabled = ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.isScanThrottleEnabled());
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.setScanThrottleEnabled(false));
mWifiLock = mWifiManager.createWifiLock(TAG);
mWifiLock.acquire();
// enable Wifi
if (!mWifiManager.isWifiEnabled()) setWifiEnabled(true);
PollingCheck.check("Wifi not enabled", TEST_WAIT_DURATION_MS,
() -> mWifiManager.isWifiEnabled());
mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
turnScreenOnNoDelay();
synchronized (mMySync) {
mMySync.expectedState = STATE_NULL;
}
List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
mWifiManager::getConfiguredNetworks);
assertFalse("Need at least one saved network", savedNetworks.isEmpty());
// Get original config for restore
mOriginalSoftApConfig = ShellIdentityUtils.invokeWithShellPermissions(
mWifiManager::getSoftApConfiguration);
}
@Override
protected void tearDown() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
super.tearDown();
return;
}
if (!mWifiManager.isWifiEnabled())
setWifiEnabled(true);
mWifiLock.release();
mContext.unregisterReceiver(mReceiver);
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.setScanThrottleEnabled(mWasScanThrottleEnabled));
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.setVerboseLoggingEnabled(mWasVerboseLoggingEnabled));
// restore original softap config
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.setSoftApConfiguration(mOriginalSoftApConfig));
Thread.sleep(TEST_WAIT_DURATION_MS);
super.tearDown();
}
private void setWifiEnabled(boolean enable) throws Exception {
synchronized (mMySync) {
if (mWifiManager.isWifiEnabled() != enable) {
// the new state is different, we expect it to change
mMySync.expectedState = STATE_WIFI_CHANGING;
} else {
mMySync.expectedState = (enable ? STATE_WIFI_ENABLED : STATE_WIFI_DISABLED);
}
// now trigger the change using shell commands.
SystemUtil.runShellCommand("svc wifi " + (enable ? "enable" : "disable"));
waitForExpectedWifiState(enable);
}
}
private void waitForExpectedWifiState(boolean enabled) throws InterruptedException {
synchronized (mMySync) {
long timeout = System.currentTimeMillis() + TEST_WAIT_DURATION_MS;
int expected = (enabled ? STATE_WIFI_ENABLED : STATE_WIFI_DISABLED);
while (System.currentTimeMillis() < timeout
&& mMySync.expectedState != expected) {
mMySync.wait(WAIT_MSEC);
}
}
}
// Get the current scan status from sticky broadcast.
private boolean isScanCurrentlyAvailable() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(WifiManager.ACTION_WIFI_SCAN_AVAILABILITY_CHANGED);
Intent intent = mContext.registerReceiver(null, intentFilter);
assertNotNull(intent);
if (intent.getAction().equals(WifiManager.ACTION_WIFI_SCAN_AVAILABILITY_CHANGED)) {
return intent.getBooleanExtra(WifiManager.EXTRA_SCAN_AVAILABLE, false);
}
return false;
}
private void startScan() throws Exception {
synchronized (mMySync) {
mMySync.expectedState = STATE_SCANNING;
mScanResults = null;
assertTrue(mWifiManager.startScan());
long timeout = System.currentTimeMillis() + SCAN_TEST_WAIT_DURATION_MS;
while (System.currentTimeMillis() < timeout && mMySync.expectedState == STATE_SCANNING)
mMySync.wait(WAIT_MSEC);
}
}
private void waitForNetworkInfoState(NetworkInfo.State state, int timeoutMillis)
throws Exception {
synchronized (mMySync) {
if (mNetworkInfo.getState() == state) return;
long timeout = System.currentTimeMillis() + timeoutMillis;
while (System.currentTimeMillis() < timeout
&& mNetworkInfo.getState() != state)
mMySync.wait(WAIT_MSEC);
assertEquals(state, mNetworkInfo.getState());
}
}
private void waitForConnection(int timeoutMillis) throws Exception {
waitForNetworkInfoState(NetworkInfo.State.CONNECTED, timeoutMillis);
}
private void waitForConnection() throws Exception {
waitForNetworkInfoState(NetworkInfo.State.CONNECTED, WIFI_CONNECT_TIMEOUT_MILLIS);
}
private void waitForDisconnection() throws Exception {
waitForNetworkInfoState(NetworkInfo.State.DISCONNECTED, TEST_WAIT_DURATION_MS);
}
private void ensureNotNetworkInfoState(NetworkInfo.State state) throws Exception {
synchronized (mMySync) {
long timeout = System.currentTimeMillis() + TEST_WAIT_DURATION_MS + WAIT_MSEC;
while (System.currentTimeMillis() < timeout) {
assertNotEquals(state, mNetworkInfo.getState());
mMySync.wait(WAIT_MSEC);
}
}
}
private void ensureNotConnected() throws Exception {
ensureNotNetworkInfoState(NetworkInfo.State.CONNECTED);
}
private void ensureNotDisconnected() throws Exception {
ensureNotNetworkInfoState(NetworkInfo.State.DISCONNECTED);
}
private boolean existSSID(String ssid) {
for (final WifiConfiguration w : mWifiManager.getConfiguredNetworks()) {
if (w.SSID.equals(ssid))
return true;
}
return false;
}
private int findConfiguredNetworks(String SSID, List<WifiConfiguration> networks) {
for (final WifiConfiguration w : networks) {
if (w.SSID.equals(SSID))
return networks.indexOf(w);
}
return -1;
}
/**
* Test creation of WifiManager Lock.
*/
public void testWifiManagerLock() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
final String TAG = "Test";
assertNotNull(mWifiManager.createWifiLock(TAG));
assertNotNull(mWifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, TAG));
}
/**
* Test wifi scanning when Wifi is off and location scanning is turned on.
*/
public void testWifiManagerScanWhenWifiOffLocationTurnedOn() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
if (!hasLocationFeature()) {
Log.d(TAG, "Skipping test as location is not supported");
return;
}
if (!isLocationEnabled()) {
fail("Please enable location for this test - since Marshmallow WiFi scan results are"
+ " empty when location is disabled!");
}
runWithScanning(() -> {
setWifiEnabled(false);
Thread.sleep(TEST_WAIT_DURATION_MS);
startScan();
if (mWifiManager.isScanAlwaysAvailable() && isScanCurrentlyAvailable()) {
// Make sure at least one AP is found.
assertNotNull("mScanResult should not be null!", mScanResults);
assertFalse("empty scan results!", mScanResults.isEmpty());
} else {
// Make sure no scan results are available.
assertNull("mScanResult should be null!", mScanResults);
}
final String TAG = "Test";
assertNotNull(mWifiManager.createWifiLock(TAG));
assertNotNull(mWifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, TAG));
}, true /* run with enabled*/);
}
/**
* Restart WiFi subsystem - verify that privileged call fails.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
public void testRestartWifiSubsystemShouldFailNoPermission() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
try {
mWifiManager.restartWifiSubsystem();
fail("The restartWifiSubsystem should not succeed - privileged call");
} catch (SecurityException e) {
// expected
}
}
/**
* Restart WiFi subsystem and verify transition through states.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
public void testRestartWifiSubsystem() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
mSubsystemRestartStatus = 0; // 0: uninitialized
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
uiAutomation.adoptShellPermissionIdentity();
mWifiManager.registerSubsystemRestartTrackingCallback(mExecutor,
mSubsystemRestartTrackingCallback);
synchronized (mLock) {
mWifiManager.restartWifiSubsystem();
mLock.wait(TEST_WAIT_DURATION_MS);
}
assertEquals(mSubsystemRestartStatus, 1); // 1: restarting
waitForExpectedWifiState(false);
assertFalse(mWifiManager.isWifiEnabled());
synchronized (mLock) {
mLock.wait(TEST_WAIT_DURATION_MS);
assertEquals(mSubsystemRestartStatus, 2); // 2: restarted
}
waitForExpectedWifiState(true);
assertTrue(mWifiManager.isWifiEnabled());
} finally {
// cleanup
mWifiManager.unregisterSubsystemRestartTrackingCallback(
mSubsystemRestartTrackingCallback);
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* test point of wifiManager properties:
* 1.enable properties
* 2.DhcpInfo properties
* 3.wifi state
* 4.ConnectionInfo
*/
public void testWifiManagerProperties() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
setWifiEnabled(true);
assertTrue(mWifiManager.isWifiEnabled());
assertNotNull(mWifiManager.getDhcpInfo());
assertEquals(WifiManager.WIFI_STATE_ENABLED, mWifiManager.getWifiState());
mWifiManager.getConnectionInfo();
setWifiEnabled(false);
assertFalse(mWifiManager.isWifiEnabled());
}
/**
* Test WiFi scan timestamp - fails when WiFi scan timestamps are inconsistent with
* {@link SystemClock#elapsedRealtime()} on device.<p>
* To run this test in cts-tradefed:
* run cts --class android.net.wifi.cts.WifiManagerTest --method testWifiScanTimestamp
*/
@VirtualDeviceNotSupported
public void testWifiScanTimestamp() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
Log.d(TAG, "Skipping test as WiFi is not supported");
return;
}
if (!hasLocationFeature()) {
Log.d(TAG, "Skipping test as location is not supported");
return;
}
if (!isLocationEnabled()) {
fail("Please enable location for this test - since Marshmallow WiFi scan results are"
+ " empty when location is disabled!");
}
if (!mWifiManager.isWifiEnabled()) {
setWifiEnabled(true);
}
// Make sure the scan timestamps are consistent with the device timestamp within the range
// of WIFI_SCAN_TEST_CACHE_DELAY_MILLIS.
startScan();
// Make sure at least one AP is found.
assertTrue("mScanResult should not be null. This may be due to a scan timeout",
mScanResults != null);
assertFalse("empty scan results!", mScanResults.isEmpty());
long nowMillis = SystemClock.elapsedRealtime();
// Keep track of how many APs are fresh in one scan.
int numFreshAps = 0;
for (ScanResult result : mScanResults) {
long scanTimeMillis = TimeUnit.MICROSECONDS.toMillis(result.timestamp);
if (Math.abs(nowMillis - scanTimeMillis) < WIFI_SCAN_TEST_CACHE_DELAY_MILLIS) {
numFreshAps++;
}
}
// At least half of the APs in the scan should be fresh.
int numTotalAps = mScanResults.size();
String msg = "Stale AP count: " + (numTotalAps - numFreshAps) + ", fresh AP count: "
+ numFreshAps;
assertTrue(msg, numFreshAps * 2 >= mScanResults.size());
}
public void testConvertBetweenChannelFrequencyMhz() throws Exception {
int[] testFrequency_2G = {2412, 2437, 2462, 2484};
int[] testFrequency_5G = {5180, 5220, 5540, 5745};
int[] testFrequency_6G = {5955, 6435, 6535, 7115};
int[] testFrequency_60G = {58320, 64800};
SparseArray<int[]> testData = new SparseArray<>() {{
put(ScanResult.WIFI_BAND_24_GHZ, testFrequency_2G);
put(ScanResult.WIFI_BAND_5_GHZ, testFrequency_5G);
put(ScanResult.WIFI_BAND_6_GHZ, testFrequency_6G);
put(ScanResult.WIFI_BAND_60_GHZ, testFrequency_60G);
}};
for (int i = 0; i < testData.size(); i++) {
for (int frequency : testData.valueAt(i)) {
assertEquals(frequency, ScanResult.convertChannelToFrequencyMhzIfSupported(
ScanResult.convertFrequencyMhzToChannelIfSupported(frequency), testData.keyAt(i)));
}
}
}
// Return true if location is enabled.
private boolean isLocationEnabled() {
return Settings.Secure.getInt(getContext().getContentResolver(),
Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF) !=
Settings.Secure.LOCATION_MODE_OFF;
}
// Returns true if the device has location feature.
private boolean hasLocationFeature() {
return getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION);
}
private boolean hasAutomotiveFeature() {
return getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
}
private boolean hasWifiDirect() {
return getContext().getPackageManager().hasSystemFeature(
PackageManager.FEATURE_WIFI_DIRECT);
}
private boolean hasWifiAware() {
return getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
}
public void testSignal() {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
final int numLevels = 9;
int expectLevel = 0;
assertEquals(expectLevel, WifiManager.calculateSignalLevel(MIN_RSSI, numLevels));
assertEquals(numLevels - 1, WifiManager.calculateSignalLevel(MAX_RSSI, numLevels));
expectLevel = 4;
assertEquals(expectLevel, WifiManager.calculateSignalLevel((MIN_RSSI + MAX_RSSI) / 2,
numLevels));
int rssiA = 4;
int rssiB = 5;
assertTrue(WifiManager.compareSignalLevel(rssiA, rssiB) < 0);
rssiB = 4;
assertTrue(WifiManager.compareSignalLevel(rssiA, rssiB) == 0);
rssiA = 5;
rssiB = 4;
assertTrue(WifiManager.compareSignalLevel(rssiA, rssiB) > 0);
}
/**
* Test that {@link WifiManager#calculateSignalLevel(int)} returns a value in the range
* [0, {@link WifiManager#getMaxSignalLevel()}], and its value is monotonically increasing as
* the RSSI increases.
*/
public void testCalculateSignalLevel() {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
int maxSignalLevel = mWifiManager.getMaxSignalLevel();
int prevSignalLevel = 0;
for (int rssi = -150; rssi <= 50; rssi++) {
int signalLevel = mWifiManager.calculateSignalLevel(rssi);
// between [0, maxSignalLevel]
assertWithMessage("For RSSI=%s", rssi).that(signalLevel).isAtLeast(0);
assertWithMessage("For RSSI=%s", rssi).that(signalLevel).isAtMost(maxSignalLevel);
// calculateSignalLevel(rssi) <= calculateSignalLevel(rssi + 1)
assertWithMessage("For RSSI=%s", rssi).that(signalLevel).isAtLeast(prevSignalLevel);
prevSignalLevel = signalLevel;
}
}
public class TestWifiVerboseLoggingStatusChangedListener implements
WifiManager.WifiVerboseLoggingStatusChangedListener {
public int numCalls;
public boolean status;
@Override
public void onWifiVerboseLoggingStatusChanged(boolean enabled) {
numCalls++;
status = enabled;
}
}
public class TestSoftApCallback implements WifiManager.SoftApCallback {
Object softApLock;
int currentState;
int currentFailureReason;
List<SoftApInfo> apInfoList = new ArrayList<>();
SoftApInfo apInfoOnSingleApMode;
Map<SoftApInfo, List<WifiClient>> apInfoClients = new HashMap<>();
List<WifiClient> currentClientList;
SoftApCapability currentSoftApCapability;
MacAddress lastBlockedClientMacAddress;
int lastBlockedClientReason;
boolean onStateChangedCalled = false;
boolean onSoftApCapabilityChangedCalled = false;
boolean onConnectedClientCalled = false;
boolean onConnectedClientChangedWithInfoCalled = false;
boolean onBlockedClientConnectingCalled = false;
int onSoftapInfoChangedCalledCount = 0;
int onSoftapInfoChangedWithListCalledCount = 0;
TestSoftApCallback(Object lock) {
softApLock = lock;
}
public boolean getOnStateChangedCalled() {
synchronized(softApLock) {
return onStateChangedCalled;
}
}
public int getOnSoftapInfoChangedCalledCount() {
synchronized(softApLock) {
return onSoftapInfoChangedCalledCount;
}
}
public int getOnSoftApInfoChangedWithListCalledCount() {
synchronized(softApLock) {
return onSoftapInfoChangedWithListCalledCount;
}
}
public boolean getOnSoftApCapabilityChangedCalled() {
synchronized(softApLock) {
return onSoftApCapabilityChangedCalled;
}
}
public boolean getOnConnectedClientChangedWithInfoCalled() {
synchronized(softApLock) {
return onConnectedClientChangedWithInfoCalled;
}
}
public boolean getOnConnectedClientCalled() {
synchronized(softApLock) {
return onConnectedClientCalled;
}
}
public boolean getOnBlockedClientConnectingCalled() {
synchronized(softApLock) {
return onBlockedClientConnectingCalled;
}
}
public int getCurrentState() {
synchronized(softApLock) {
return currentState;
}
}
public int getCurrentStateFailureReason() {
synchronized(softApLock) {
return currentFailureReason;
}
}
public List<WifiClient> getCurrentClientList() {
synchronized(softApLock) {
return new ArrayList<>(currentClientList);
}
}
public SoftApInfo getCurrentSoftApInfo() {
synchronized(softApLock) {
return apInfoOnSingleApMode;
}
}
public List<SoftApInfo> getCurrentSoftApInfoList() {
synchronized(softApLock) {
return new ArrayList<>(apInfoList);
}
}
public SoftApCapability getCurrentSoftApCapability() {
synchronized(softApLock) {
return currentSoftApCapability;
}
}
public MacAddress getLastBlockedClientMacAddress() {
synchronized(softApLock) {
return lastBlockedClientMacAddress;
}
}
public int getLastBlockedClientReason() {
synchronized(softApLock) {
return lastBlockedClientReason;
}
}
@Override
public void onStateChanged(int state, int failureReason) {
synchronized(softApLock) {
currentState = state;
currentFailureReason = failureReason;
onStateChangedCalled = true;
}
}
@Override
public void onConnectedClientsChanged(List<WifiClient> clients) {
synchronized(softApLock) {
currentClientList = new ArrayList<>(clients);
onConnectedClientCalled = true;
}
}
@Override
public void onConnectedClientsChanged(SoftApInfo info, List<WifiClient> clients) {
synchronized(softApLock) {
apInfoClients.put(info, clients);
onConnectedClientChangedWithInfoCalled = true;
}
}
@Override
public void onInfoChanged(List<SoftApInfo> infoList) {
synchronized(softApLock) {
apInfoList = new ArrayList<>(infoList);
onSoftapInfoChangedWithListCalledCount++;
}
}
@Override
public void onInfoChanged(SoftApInfo softApInfo) {
synchronized(softApLock) {
apInfoOnSingleApMode = softApInfo;
onSoftapInfoChangedCalledCount++;
}
}
@Override
public void onCapabilityChanged(SoftApCapability softApCapability) {
synchronized(softApLock) {
currentSoftApCapability = softApCapability;
onSoftApCapabilityChangedCalled = true;
}
}
@Override
public void onBlockedClientConnecting(WifiClient client, int blockedReason) {
synchronized(softApLock) {
lastBlockedClientMacAddress = client.getMacAddress();
lastBlockedClientReason = blockedReason;
onBlockedClientConnectingCalled = true;
}
}
}
private static class TestLocalOnlyHotspotCallback extends WifiManager.LocalOnlyHotspotCallback {
Object hotspotLock;
WifiManager.LocalOnlyHotspotReservation reservation = null;
boolean onStartedCalled = false;
boolean onStoppedCalled = false;
boolean onFailedCalled = false;
int failureReason = -1;
TestLocalOnlyHotspotCallback(Object lock) {
hotspotLock = lock;
}
@Override
public void onStarted(WifiManager.LocalOnlyHotspotReservation r) {
synchronized (hotspotLock) {
reservation = r;
onStartedCalled = true;
hotspotLock.notify();
}
}
@Override
public void onStopped() {
synchronized (hotspotLock) {
onStoppedCalled = true;
hotspotLock.notify();
}
}
@Override
public void onFailed(int reason) {
synchronized (hotspotLock) {
onFailedCalled = true;
failureReason = reason;
hotspotLock.notify();
}
}
}
private List<Integer> getSupportedSoftApBand(SoftApCapability capability) {
List<Integer> supportedApBands = new ArrayList<>();
if (mWifiManager.is24GHzBandSupported() &&
capability.areFeaturesSupported(
SoftApCapability.SOFTAP_FEATURE_BAND_24G_SUPPORTED)) {
supportedApBands.add(SoftApConfiguration.BAND_2GHZ);
}
if (mWifiManager.is5GHzBandSupported() &&
capability.areFeaturesSupported(
SoftApCapability.SOFTAP_FEATURE_BAND_5G_SUPPORTED)) {
supportedApBands.add(SoftApConfiguration.BAND_5GHZ);
}
if (mWifiManager.is6GHzBandSupported() &&
capability.areFeaturesSupported(
SoftApCapability.SOFTAP_FEATURE_BAND_6G_SUPPORTED)) {
supportedApBands.add(SoftApConfiguration.BAND_6GHZ);
}
if (mWifiManager.is60GHzBandSupported() &&
capability.areFeaturesSupported(
SoftApCapability.SOFTAP_FEATURE_BAND_60G_SUPPORTED)) {
supportedApBands.add(SoftApConfiguration.BAND_60GHZ);
}
return supportedApBands;
}
private TestLocalOnlyHotspotCallback startLocalOnlyHotspot() {
// Location mode must be enabled for this test
if (!isLocationEnabled()) {
fail("Please enable location for this test");
}
TestExecutor executor = new TestExecutor();
TestSoftApCallback lohsSoftApCallback = new TestSoftApCallback(mLock);
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
List<Integer> supportedSoftApBands = new ArrayList<>();
try {
uiAutomation.adoptShellPermissionIdentity();
verifyLohsRegisterSoftApCallback(executor, lohsSoftApCallback);
supportedSoftApBands = getSupportedSoftApBand(
lohsSoftApCallback.getCurrentSoftApCapability());
} catch (Exception ex) {
} finally {
// clean up
unregisterLocalOnlyHotspotSoftApCallback(lohsSoftApCallback);
uiAutomation.dropShellPermissionIdentity();
}
TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(mLock);
synchronized (mLock) {
try {
mWifiManager.startLocalOnlyHotspot(callback, null);
// now wait for callback
mLock.wait(TEST_WAIT_DURATION_MS);
} catch (InterruptedException e) {
}
// check if we got the callback
assertTrue(callback.onStartedCalled);
SoftApConfiguration softApConfig = callback.reservation.getSoftApConfiguration();
assertNotNull(softApConfig);
int securityType = softApConfig.getSecurityType();
if (securityType == SoftApConfiguration.SECURITY_TYPE_OPEN
|| securityType == SoftApConfiguration.SECURITY_TYPE_WPA2_PSK
|| securityType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION) {
assertNotNull(softApConfig.toWifiConfiguration());
} else {
assertNull(softApConfig.toWifiConfiguration());
}
if (!hasAutomotiveFeature()) {
assertEquals(supportedSoftApBands.size() > 0 ? supportedSoftApBands.get(0)
: SoftApConfiguration.BAND_2GHZ,
callback.reservation.getSoftApConfiguration().getBand());
}
assertFalse(callback.onFailedCalled);
assertFalse(callback.onStoppedCalled);
}
return callback;
}
private void stopLocalOnlyHotspot(TestLocalOnlyHotspotCallback callback, boolean wifiEnabled) {
synchronized (mMySync) {
// we are expecting a new state
mMySync.expectedState = STATE_WIFI_CHANGING;
// now shut down LocalOnlyHotspot
callback.reservation.close();
try {
waitForExpectedWifiState(wifiEnabled);
} catch (InterruptedException e) {}
}
}
/**
* Verify that calls to startLocalOnlyHotspot succeed with proper permissions.
*
* Note: Location mode must be enabled for this test.
*/
public void testStartLocalOnlyHotspotSuccess() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// check that softap mode is supported by the device
if (!mWifiManager.isPortableHotspotSupported()) {
return;
}
boolean wifiEnabled = mWifiManager.isWifiEnabled();
TestLocalOnlyHotspotCallback callback = startLocalOnlyHotspot();
// add sleep to avoid calling stopLocalOnlyHotspot before TetherController initialization.
// TODO: remove this sleep as soon as b/124330089 is fixed.
Log.d(TAG, "Sleeping for 2 seconds");
Thread.sleep(2000);
stopLocalOnlyHotspot(callback, wifiEnabled);
// wifi should either stay on, or come back on
assertEquals(wifiEnabled, mWifiManager.isWifiEnabled());
}
/**
* Verify calls to deprecated API's all fail for non-settings apps targeting >= Q SDK.
*/
public void testDeprecatedApis() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
setWifiEnabled(true);
waitForConnection(); // ensures that there is at-least 1 saved network on the device.
WifiConfiguration wifiConfiguration = new WifiConfiguration();
wifiConfiguration.SSID = SSID1;
wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
assertEquals(INVALID_NETWORK_ID,
mWifiManager.addNetwork(wifiConfiguration));
assertEquals(INVALID_NETWORK_ID,
mWifiManager.updateNetwork(wifiConfiguration));
assertFalse(mWifiManager.enableNetwork(0, true));
assertFalse(mWifiManager.disableNetwork(0));
assertFalse(mWifiManager.removeNetwork(0));
assertFalse(mWifiManager.disconnect());
assertFalse(mWifiManager.reconnect());
assertFalse(mWifiManager.reassociate());
assertTrue(mWifiManager.getConfiguredNetworks().isEmpty());
boolean wifiEnabled = mWifiManager.isWifiEnabled();
// now we should fail to toggle wifi state.
assertFalse(mWifiManager.setWifiEnabled(!wifiEnabled));
Thread.sleep(TEST_WAIT_DURATION_MS);
assertEquals(wifiEnabled, mWifiManager.isWifiEnabled());
}
/**
* Test the WifiManager APIs that return whether a feature is supported.
*/
public void testGetSupportedFeatures() {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
if (!WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
// Skip the test if wifi module version is older than S.
return;
}
mWifiManager.isMakeBeforeBreakWifiSwitchingSupported();
mWifiManager.isStaBridgedApConcurrencySupported();
}
/**
* Verify non DO apps cannot call removeNonCallerConfiguredNetworks.
*/
public void testRemoveNonCallerConfiguredNetworksNotAllowed() {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
if (!WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
// Skip the test if wifi module version is older than S.
return;
}
try {
mWifiManager.removeNonCallerConfiguredNetworks();
fail("Expected security exception for non DO app");
} catch (SecurityException e) {
}
}
/**
* Verify setting the scan schedule.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
public void testSetScreenOnScanSchedule() {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
List<WifiManager.ScreenOnScanSchedule> schedules = new ArrayList<>();
schedules.add(new WifiManager.ScreenOnScanSchedule(Duration.ofSeconds(20),
WifiScanner.SCAN_TYPE_HIGH_ACCURACY));
schedules.add(new WifiManager.ScreenOnScanSchedule(Duration.ofSeconds(40),
WifiScanner.SCAN_TYPE_LOW_LATENCY));
assertEquals(20, schedules.get(0).getScanInterval().toSeconds());
assertEquals(40, schedules.get(1).getScanInterval().toSeconds());
assertEquals(WifiScanner.SCAN_TYPE_HIGH_ACCURACY, schedules.get(0).getScanType());
assertEquals(WifiScanner.SCAN_TYPE_LOW_LATENCY, schedules.get(1).getScanType());
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.setScreenOnScanSchedule(schedules));
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.setScreenOnScanSchedule(null));
// Creating an invalid ScanSchedule should throw an exception
assertThrows(IllegalArgumentException.class, () -> new WifiManager.ScreenOnScanSchedule(
null, WifiScanner.SCAN_TYPE_HIGH_ACCURACY));
}
/**
* Verify a normal app cannot set the scan schedule.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
public void testSetScreenOnScanScheduleNoPermission() {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
assertThrows(SecurityException.class, () -> mWifiManager.setScreenOnScanSchedule(null));
}
/**
* Test coverage for the constructor of AddNetworkResult.
*/
public void testAddNetworkResultCreation() {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
if (!WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
// Skip the test if wifi module version is older than S.
return;
}
int statusCode = WifiManager.AddNetworkResult.STATUS_NO_PERMISSION;
int networkId = 5;
WifiManager.AddNetworkResult result = new WifiManager.AddNetworkResult(
statusCode, networkId);
assertEquals("statusCode should match", statusCode, result.statusCode);
assertEquals("networkId should match", networkId, result.networkId);
}
/**
* Verify {@link WifiManager#setSsidsAllowlist(Set)} can be called with sufficient
* privilege.
*/
public void testGetAndSetSsidsAllowlist() {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
Set<WifiSsid> ssids = new ArraySet<>();
ssids.add(WifiSsid.fromBytes("TEST_SSID_1".getBytes(StandardCharsets.UTF_8)));
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.setSsidsAllowlist(ssids));
ShellIdentityUtils.invokeWithShellPermissions(
() -> assertEquals("Ssids should match", ssids,
mWifiManager.getSsidsAllowlist()));
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.setSsidsAllowlist(Collections.EMPTY_SET));
ShellIdentityUtils.invokeWithShellPermissions(
() -> assertEquals("Should equal to empty set",
Collections.EMPTY_SET,
mWifiManager.getSsidsAllowlist()));
try {
mWifiManager.setSsidsAllowlist(Collections.EMPTY_SET);
fail("Expected SecurityException when called without permission");
} catch (SecurityException e) {
// expect the exception
}
}
class TestPnoScanResultsCallback implements WifiManager.PnoScanResultsCallback {
public CountDownLatch latch = new CountDownLatch(1);
private boolean mRegisterSuccess;
private int mRegisterFailedReason = -1;
private int mRemovedReason = -1;
private List<ScanResult> mScanResults;
@Override
public void onScanResultsAvailable(List<ScanResult> scanResults) {
latch.countDown();
mScanResults = scanResults;
}
@Override
public void onRegisterSuccess() {
latch.countDown();
mRegisterSuccess = true;
}
@Override
public void onRegisterFailed(int reason) {
latch.countDown();
mRegisterFailedReason = reason;
}
@Override
public void onRemoved(int reason) {
latch.countDown();
mRemovedReason = reason;
}
public boolean isRegisterSuccess() {
return mRegisterSuccess;
}
public int getRemovedReason() {
return mRemovedReason;
}
public int getRegisterFailedReason() {
return mRegisterFailedReason;
}
public List<ScanResult> getScanResults() {
return mScanResults;
}
}
/**
* Verify {@link WifiManager#setExternalPnoScanRequest(List, int[], Executor,
* WifiManager.PnoScanResultsCallback)} can be called with proper permissions.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
public void testSetExternalPnoScanRequestSuccess() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
TestPnoScanResultsCallback callback = new TestPnoScanResultsCallback();
List<WifiSsid> ssids = new ArrayList<>();
ssids.add(WifiSsid.fromBytes("TEST_SSID_1".getBytes(StandardCharsets.UTF_8)));
int[] frequencies = new int[] {2412, 5180, 5805};
assertFalse("Callback should be initialized unregistered", callback.isRegisterSuccess());
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.setExternalPnoScanRequest(
ssids, frequencies, Executors.newSingleThreadExecutor(), callback));
callback.latch.await(TEST_WAIT_DURATION_MS, TimeUnit.MILLISECONDS);
if (mWifiManager.isPreferredNetworkOffloadSupported()) {
assertTrue("Expect register success or failed due to resource busy",
callback.isRegisterSuccess()
|| callback.getRegisterFailedReason() == WifiManager.PnoScanResultsCallback
.REGISTER_PNO_CALLBACK_RESOURCE_BUSY);
} else {
assertEquals("Expect register fail due to not supported.",
WifiManager.PnoScanResultsCallback.REGISTER_PNO_CALLBACK_PNO_NOT_SUPPORTED,
callback.getRegisterFailedReason());
}
mWifiManager.clearExternalPnoScanRequest();
}
/**
* Verify {@link WifiManager#setExternalPnoScanRequest(List, int[], Executor,
* WifiManager.PnoScanResultsCallback)} can be called with null frequency.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
public void testSetExternalPnoScanRequestSuccessNullFrequency() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
TestPnoScanResultsCallback callback = new TestPnoScanResultsCallback();
List<WifiSsid> ssids = new ArrayList<>();
ssids.add(WifiSsid.fromBytes("TEST_SSID_1".getBytes(StandardCharsets.UTF_8)));
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.setExternalPnoScanRequest(
ssids, null, Executors.newSingleThreadExecutor(), callback));
mWifiManager.clearExternalPnoScanRequest();
}
/**
* Verify {@link WifiManager#setExternalPnoScanRequest(List, int[], Executor,
* WifiManager.PnoScanResultsCallback)} throws an Exception if called with too many SSIDs.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
public void testSetExternalPnoScanRequestTooManySsidsException() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
TestPnoScanResultsCallback callback = new TestPnoScanResultsCallback();
List<WifiSsid> ssids = new ArrayList<>();
ssids.add(WifiSsid.fromBytes("TEST_SSID_1".getBytes(StandardCharsets.UTF_8)));
ssids.add(WifiSsid.fromBytes("TEST_SSID_2".getBytes(StandardCharsets.UTF_8)));
ssids.add(WifiSsid.fromBytes("TEST_SSID_3".getBytes(StandardCharsets.UTF_8)));
assertFalse("Callback should be initialized unregistered", callback.isRegisterSuccess());
assertThrows(IllegalArgumentException.class, () -> {
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.setExternalPnoScanRequest(
ssids, null, Executors.newSingleThreadExecutor(), callback));
});
}
/**
* Verify {@link WifiManager#setExternalPnoScanRequest(List, int[], Executor,
* WifiManager.PnoScanResultsCallback)} throws an Exception if called with too many frequencies.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
public void testSetExternalPnoScanRequestTooManyFrequenciesException() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
TestPnoScanResultsCallback callback = new TestPnoScanResultsCallback();
List<WifiSsid> ssids = new ArrayList<>();
ssids.add(WifiSsid.fromBytes("TEST_SSID_1".getBytes(StandardCharsets.UTF_8)));
int[] frequencies = new int[] {2412, 2417, 2422, 2427, 2432, 2437, 2447, 2452, 2457, 2462,
5180, 5200, 5220, 5240, 5745, 5765, 5785, 5805};
assertFalse("Callback should be initialized unregistered", callback.isRegisterSuccess());
assertThrows(IllegalArgumentException.class, () -> {
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.setExternalPnoScanRequest(
ssids, frequencies, Executors.newSingleThreadExecutor(), callback));
});
}
/**
* Verify {@link WifiManager#setExternalPnoScanRequest(List, int[], Executor,
* WifiManager.PnoScanResultsCallback)} cannot be called without permission.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
public void testSetExternalPnoScanRequestNoPermission() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
TestExecutor executor = new TestExecutor();
TestPnoScanResultsCallback callback = new TestPnoScanResultsCallback();
List<WifiSsid> ssids = new ArrayList<>();
ssids.add(WifiSsid.fromBytes("TEST_SSID_1".getBytes(StandardCharsets.UTF_8)));
assertFalse("Callback should be initialized unregistered", callback.isRegisterSuccess());
assertThrows(SecurityException.class,
() -> mWifiManager.setExternalPnoScanRequest(ssids, null, executor, callback));
}
/**
* Verify the invalid and valid usages of {@code WifiManager#getLastCallerInfoForApi}.
*/
public void testGetLastCallerInfoForApi() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
AtomicReference<String> packageName = new AtomicReference<>();
AtomicBoolean enabled = new AtomicBoolean(false);
BiConsumer<String, Boolean> listener = new BiConsumer<String, Boolean>() {
@Override
public void accept(String caller, Boolean value) {
synchronized (mLock) {
packageName.set(caller);
enabled.set(value);
mLock.notify();
}
}
};
// Test invalid inputs trigger IllegalArgumentException
assertThrows("Invalid apiType should trigger exception", IllegalArgumentException.class,
() -> mWifiManager.getLastCallerInfoForApi(-1, mExecutor, listener));
assertThrows("null executor should trigger exception", IllegalArgumentException.class,
() -> mWifiManager.getLastCallerInfoForApi(WifiManager.API_SOFT_AP, null,
listener));
assertThrows("null listener should trigger exception", IllegalArgumentException.class,
() -> mWifiManager.getLastCallerInfoForApi(WifiManager.API_SOFT_AP, mExecutor,
null));
// Test caller with no permission triggers SecurityException.
assertThrows("No permission should trigger SecurityException", SecurityException.class,
() -> mWifiManager.getLastCallerInfoForApi(WifiManager.API_SOFT_AP,
mExecutor, listener));
String expectedPackage = "com.android.shell";
boolean isEnabledBefore = mWifiManager.isWifiEnabled();
// toggle wifi and verify getting last caller
setWifiEnabled(!isEnabledBefore);
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.getLastCallerInfoForApi(WifiManager.API_WIFI_ENABLED, mExecutor,
listener));
synchronized (mLock) {
mLock.wait(TEST_WAIT_DURATION_MS);
}
assertEquals("package does not match", expectedPackage, packageName.get());
assertEquals("enabled does not match", !isEnabledBefore, enabled.get());
// toggle wifi again and verify last caller
packageName.set(null);
setWifiEnabled(isEnabledBefore);
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.getLastCallerInfoForApi(WifiManager.API_WIFI_ENABLED, mExecutor,
listener));
synchronized (mLock) {
mLock.wait(TEST_WAIT_DURATION_MS);
}
assertEquals("package does not match", expectedPackage, packageName.get());
assertEquals("enabled does not match", isEnabledBefore, enabled.get());
}
/**
* Verify that {@link WifiManager#addNetworkPrivileged(WifiConfiguration)} throws a
* SecurityException when called by a normal app.
*/
public void testAddNetworkPrivilegedNotAllowedForNormalApps() {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
if (!WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
// Skip the test if wifi module version is older than S.
return;
}
try {
WifiConfiguration newOpenNetwork = new WifiConfiguration();
newOpenNetwork.SSID = "\"" + TEST_SSID_UNQUOTED + "\"";
mWifiManager.addNetworkPrivileged(newOpenNetwork);
fail("A normal app should not be able to call this API.");
} catch (SecurityException e) {
}
}
/**
* Verify {@link WifiManager#addNetworkPrivileged(WifiConfiguration)} throws an exception when
* null is the input.
*/
public void testAddNetworkPrivilegedBadInput() {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
if (!WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
// Skip the test if wifi module version is older than S.
return;
}
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
uiAutomation.adoptShellPermissionIdentity();
mWifiManager.addNetworkPrivileged(null);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
} finally {
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Verify {@link WifiManager#getPrivilegedConnectedNetwork()} returns the currently
* connected WifiConfiguration with randomized MAC address filtered out.
*/
public void testGetPrivilegedConnectedNetworkSuccess() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
setWifiEnabled(true);
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
uiAutomation.adoptShellPermissionIdentity();
mWifiManager.startScan();
waitForConnection(); // ensures that there is at-least 1 saved network on the device.
WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
int curNetworkId = wifiInfo.getNetworkId();
assertNotEquals("Should be connected to valid networkId", INVALID_NETWORK_ID,
curNetworkId);
WifiConfiguration curConfig = mWifiManager.getPrivilegedConnectedNetwork();
assertEquals("NetworkId should match", curNetworkId, curConfig.networkId);
assertEquals("SSID should match", wifiInfo.getSSID(), curConfig.SSID);
assertEquals("Randomized MAC should be filtered out", WifiInfo.DEFAULT_MAC_ADDRESS,
curConfig.getRandomizedMacAddress().toString());
} finally {
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Verify {@link WifiManager#addNetworkPrivileged(WifiConfiguration)} works properly when the
* calling app has permissions.
*/
public void testAddNetworkPrivilegedSuccess() {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
if (!WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
// Skip the test if wifi module version is older than S.
return;
}
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
WifiManager.AddNetworkResult result = null;
try {
uiAutomation.adoptShellPermissionIdentity();
WifiConfiguration newOpenNetwork = new WifiConfiguration();
newOpenNetwork.SSID = "\"" + TEST_SSID_UNQUOTED + "\"";
result = mWifiManager.addNetworkPrivileged(newOpenNetwork);
assertEquals(WifiManager.AddNetworkResult.STATUS_SUCCESS, result.statusCode);
assertTrue(result.networkId >= 0);
List<WifiConfiguration> configuredNetworks = mWifiManager.getConfiguredNetworks();
boolean found = false;
for (WifiConfiguration config : configuredNetworks) {
if (config.networkId == result.networkId
&& config.SSID.equals(newOpenNetwork.SSID)) {
found = true;
break;
}
}
assertTrue("addNetworkPrivileged returns success"
+ "but the network is not found in getConfiguredNetworks", found);
List<WifiConfiguration> privilegedConfiguredNetworks =
mWifiManager.getPrivilegedConfiguredNetworks();
found = false;
for (WifiConfiguration config : privilegedConfiguredNetworks) {
if (config.networkId == result.networkId
&& config.SSID.equals(newOpenNetwork.SSID)) {
found = true;
break;
}
}
assertTrue("addNetworkPrivileged returns success"
+ "but the network is not found in getPrivilegedConfiguredNetworks", found);
List<WifiConfiguration> callerConfiguredNetworks =
mWifiManager.getCallerConfiguredNetworks();
found = false;
for (WifiConfiguration config : callerConfiguredNetworks) {
if (config.networkId == result.networkId
&& config.SSID.equals(newOpenNetwork.SSID)) {
found = true;
break;
}
}
assertTrue("addNetworkPrivileged returns success"
+ "but the network is not found in getCallerConfiguredNetworks", found);
} finally {
if (null != result) {
mWifiManager.removeNetwork(result.networkId);
}
uiAutomation.dropShellPermissionIdentity();
}
}
private WifiConfiguration createConfig(
String ssid, int type) {
WifiConfiguration config = new WifiConfiguration();
config.SSID = "\"" + ssid + "\"";
config.setSecurityParams(type);
// set necessary fields for different types.
switch (type) {
case WifiConfiguration.SECURITY_TYPE_OPEN:
case WifiConfiguration.SECURITY_TYPE_OWE:
break;
case WifiConfiguration.SECURITY_TYPE_PSK:
case WifiConfiguration.SECURITY_TYPE_SAE:
config.preSharedKey = "\"1qaz@WSX\"";
break;
case WifiConfiguration.SECURITY_TYPE_EAP:
case WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE:
case WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT:
config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.SIM);
break;
}
return config;
}
private void assertConfigsAreFound(
List<WifiConfiguration> expectedConfigs,
List<WifiConfiguration> configs) {
for (WifiConfiguration expectedConfig: expectedConfigs) {
boolean found = false;
for (WifiConfiguration config : configs) {
if (config.networkId == expectedConfig.networkId
&& config.getKey().equals(expectedConfig.getKey())) {
found = true;
break;
}
}
assertTrue("the network " + expectedConfig.getKey() + " is not found", found);
}
}
/**
* Verify {@link WifiManager#addNetworkPrivileged(WifiConfiguration)} works
* with merging types properly when the calling app has permissions.
*/
public void testAddNetworkPrivilegedMergingTypeSuccess() {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
if (!WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
// Skip the test if wifi module version is older than S.
return;
}
List<WifiConfiguration> baseConfigs = new ArrayList<>();
baseConfigs.add(createConfig("test-open-owe-jdur", WifiConfiguration.SECURITY_TYPE_OPEN));
baseConfigs.add(createConfig("test-psk-sae-ijfe", WifiConfiguration.SECURITY_TYPE_PSK));
baseConfigs.add(createConfig("test-wpa2e-wpa3e-plki",
WifiConfiguration.SECURITY_TYPE_EAP));
List<WifiConfiguration> upgradeConfigs = new ArrayList<>();
upgradeConfigs.add(createConfig("test-open-owe-jdur", WifiConfiguration.SECURITY_TYPE_OWE));
upgradeConfigs.add(createConfig("test-psk-sae-ijfe", WifiConfiguration.SECURITY_TYPE_SAE));
upgradeConfigs.add(createConfig("test-wpa2e-wpa3e-plki",
WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE));
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
uiAutomation.adoptShellPermissionIdentity();
final int originalConfiguredNetworksNumber = mWifiManager.getConfiguredNetworks().size();
final int originalPrivilegedConfiguredNetworksNumber =
mWifiManager.getPrivilegedConfiguredNetworks().size();
final int originalCallerConfiguredNetworksNumber =
mWifiManager.getCallerConfiguredNetworks().size();
for (WifiConfiguration c: baseConfigs) {
WifiManager.AddNetworkResult result = mWifiManager.addNetworkPrivileged(c);
assertEquals(WifiManager.AddNetworkResult.STATUS_SUCCESS, result.statusCode);
assertTrue(result.networkId >= 0);
c.networkId = result.networkId;
}
for (WifiConfiguration c: upgradeConfigs) {
WifiManager.AddNetworkResult result = mWifiManager.addNetworkPrivileged(c);
assertEquals(WifiManager.AddNetworkResult.STATUS_SUCCESS, result.statusCode);
assertTrue(result.networkId >= 0);
c.networkId = result.networkId;
}
// open/owe, psk/sae, and wpa2e/wpa3e should be merged
// so they should have the same network ID.
for (int i = 0; i < baseConfigs.size(); i++) {
assertEquals(baseConfigs.get(i).networkId, upgradeConfigs.get(i).networkId);
}
int numAddedConfigs = baseConfigs.size();
List<WifiConfiguration> expectedConfigs = new ArrayList<>(baseConfigs);
if (SdkLevel.isAtLeastS()) {
// S devices and above will return one additional config per each security type
// added, so we include the number of both base and upgrade configs.
numAddedConfigs += upgradeConfigs.size();
expectedConfigs.addAll(upgradeConfigs);
}
List<WifiConfiguration> configuredNetworks = mWifiManager.getConfiguredNetworks();
assertEquals(originalConfiguredNetworksNumber + numAddedConfigs,
configuredNetworks.size());
assertConfigsAreFound(expectedConfigs, configuredNetworks);
List<WifiConfiguration> privilegedConfiguredNetworks =
mWifiManager.getPrivilegedConfiguredNetworks();
assertEquals(originalPrivilegedConfiguredNetworksNumber + numAddedConfigs,
privilegedConfiguredNetworks.size());
assertConfigsAreFound(expectedConfigs, privilegedConfiguredNetworks);
List<WifiConfiguration> callerConfiguredNetworks =
mWifiManager.getCallerConfiguredNetworks();
assertEquals(originalCallerConfiguredNetworksNumber + numAddedConfigs,
callerConfiguredNetworks.size());
assertConfigsAreFound(expectedConfigs, callerConfiguredNetworks);
} finally {
for (WifiConfiguration c: baseConfigs) {
if (c.networkId >= 0) {
mWifiManager.removeNetwork(c.networkId);
}
}
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Verify that applications can only have one registered LocalOnlyHotspot request at a time.
*
* Note: Location mode must be enabled for this test.
*/
public void testStartLocalOnlyHotspotSingleRequestByApps() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// check that softap mode is supported by the device
if (!mWifiManager.isPortableHotspotSupported()) {
return;
}
boolean caughtException = false;
boolean wifiEnabled = mWifiManager.isWifiEnabled();
TestLocalOnlyHotspotCallback callback = startLocalOnlyHotspot();
// now make a second request - this should fail.
TestLocalOnlyHotspotCallback callback2 = new TestLocalOnlyHotspotCallback(mLock);
try {
mWifiManager.startLocalOnlyHotspot(callback2, null);
} catch (IllegalStateException e) {
Log.d(TAG, "Caught the IllegalStateException we expected: called startLOHS twice");
caughtException = true;
}
if (!caughtException) {
// second start did not fail, should clean up the hotspot.
// add sleep to avoid calling stopLocalOnlyHotspot before TetherController initialization.
// TODO: remove this sleep as soon as b/124330089 is fixed.
Log.d(TAG, "Sleeping for 2 seconds");
Thread.sleep(2000);
stopLocalOnlyHotspot(callback2, wifiEnabled);
}
assertTrue(caughtException);
// add sleep to avoid calling stopLocalOnlyHotspot before TetherController initialization.
// TODO: remove this sleep as soon as b/124330089 is fixed.
Log.d(TAG, "Sleeping for 2 seconds");
Thread.sleep(2000);
stopLocalOnlyHotspot(callback, wifiEnabled);
}
private static class TestExecutor implements Executor {
private ConcurrentLinkedQueue<Runnable> tasks = new ConcurrentLinkedQueue<>();
@Override
public void execute(Runnable task) {
tasks.add(task);
}
private void runAll() {
Runnable task = tasks.poll();
while (task != null) {
task.run();
task = tasks.poll();
}
}
}
private SoftApConfiguration.Builder generateSoftApConfigBuilderWithSsid(String ssid) {
if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
return new SoftApConfiguration.Builder().setWifiSsid(
WifiSsid.fromBytes(ssid.getBytes(StandardCharsets.UTF_8)));
}
return new SoftApConfiguration.Builder().setSsid(ssid);
}
private void assertSsidEquals(SoftApConfiguration config, String expectedSsid) {
if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
assertEquals(WifiSsid.fromBytes(expectedSsid.getBytes(StandardCharsets.UTF_8)),
config.getWifiSsid());
} else {
assertEquals(expectedSsid, config.getSsid());
}
}
private void unregisterLocalOnlyHotspotSoftApCallback(TestSoftApCallback lohsSoftApCallback) {
if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
mWifiManager.unregisterLocalOnlyHotspotSoftApCallback(lohsSoftApCallback);
} else {
mWifiManager.unregisterSoftApCallback(lohsSoftApCallback);
}
}
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
public void testStartLocalOnlyHotspotWithSupportedBand() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// check that softap mode is supported by the device
if (!mWifiManager.isPortableHotspotSupported()) {
return;
}
TestExecutor executor = new TestExecutor();
TestSoftApCallback lohsSoftApCallback = new TestSoftApCallback(mLock);
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
boolean wifiEnabled = mWifiManager.isWifiEnabled();
try {
uiAutomation.adoptShellPermissionIdentity();
verifyLohsRegisterSoftApCallback(executor, lohsSoftApCallback);
SoftApConfiguration.Builder customConfigBuilder =
generateSoftApConfigBuilderWithSsid(TEST_SSID_UNQUOTED)
.setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
SparseIntArray testBandsAndChannels = getAvailableBandAndChannelForTesting(
lohsSoftApCallback.getCurrentSoftApCapability());
for (int i = 0; i < testBandsAndChannels.size(); i++) {
TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(mLock);
int testBand = testBandsAndChannels.keyAt(i);
customConfigBuilder.setBand(testBand);
mWifiManager.startLocalOnlyHotspot(customConfigBuilder.build(), executor, callback);
// now wait for callback
Thread.sleep(DURATION_SOFTAP_START_MS);
// Verify callback is run on the supplied executor
assertFalse(callback.onStartedCalled);
executor.runAll();
assertTrue(callback.onStartedCalled);
assertNotNull(callback.reservation);
SoftApConfiguration softApConfig = callback.reservation.getSoftApConfiguration();
assertEquals(
WifiSsid.fromBytes(TEST_SSID_UNQUOTED.getBytes(StandardCharsets.UTF_8)),
softApConfig.getWifiSsid());
assertEquals(TEST_PASSPHRASE, softApConfig.getPassphrase());
assertEquals(testBand, softApConfig.getBand());
assertTrue(lohsSoftApCallback.getCurrentSoftApInfo().getFrequency() > 0);
stopLocalOnlyHotspot(callback, wifiEnabled);
}
} finally {
// clean up
mWifiManager.unregisterSoftApCallback(lohsSoftApCallback);
uiAutomation.dropShellPermissionIdentity();
}
}
public void testStartLocalOnlyHotspotWithConfigBssid() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// check that softap mode is supported by the device
if (!mWifiManager.isPortableHotspotSupported()) {
return;
}
TestExecutor executor = new TestExecutor();
TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(mLock);
TestSoftApCallback lohsSoftApCallback = new TestSoftApCallback(mLock);
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
boolean wifiEnabled = mWifiManager.isWifiEnabled();
try {
uiAutomation.adoptShellPermissionIdentity();
verifyLohsRegisterSoftApCallback(executor, lohsSoftApCallback);
SoftApConfiguration.Builder customConfigBuilder =
generateSoftApConfigBuilderWithSsid(TEST_SSID_UNQUOTED)
.setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
boolean isSupportCustomizedMac = lohsSoftApCallback.getCurrentSoftApCapability()
.areFeaturesSupported(
SoftApCapability.SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION)
&& PropertyUtil.isVndkApiLevelNewerThan(Build.VERSION_CODES.S);
if (isSupportCustomizedMac) {
customConfigBuilder.setBssid(TEST_MAC).setMacRandomizationSetting(
SoftApConfiguration.RANDOMIZATION_NONE);
}
SoftApConfiguration customConfig = customConfigBuilder.build();
mWifiManager.startLocalOnlyHotspot(customConfig, executor, callback);
// now wait for callback
Thread.sleep(TEST_WAIT_DURATION_MS);
// Verify callback is run on the supplied executor
assertFalse(callback.onStartedCalled);
executor.runAll();
assertTrue(callback.onStartedCalled);
assertNotNull(callback.reservation);
SoftApConfiguration softApConfig = callback.reservation.getSoftApConfiguration();
assertNotNull(softApConfig);
if (isSupportCustomizedMac) {
assertEquals(TEST_MAC, softApConfig.getBssid());
}
assertSsidEquals(softApConfig, TEST_SSID_UNQUOTED);
assertEquals(TEST_PASSPHRASE, softApConfig.getPassphrase());
} finally {
// clean up
stopLocalOnlyHotspot(callback, wifiEnabled);
unregisterLocalOnlyHotspotSoftApCallback(lohsSoftApCallback);
uiAutomation.dropShellPermissionIdentity();
}
}
public void testStartLocalOnlyHotspotWithNullBssidConfig() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// check that softap mode is supported by the device
if (!mWifiManager.isPortableHotspotSupported()) {
return;
}
SoftApConfiguration customConfig =
generateSoftApConfigBuilderWithSsid(TEST_SSID_UNQUOTED)
.setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
.build();
TestExecutor executor = new TestExecutor();
TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(mLock);
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
boolean wifiEnabled = mWifiManager.isWifiEnabled();
try {
uiAutomation.adoptShellPermissionIdentity();
mWifiManager.startLocalOnlyHotspot(customConfig, executor, callback);
// now wait for callback
Thread.sleep(TEST_WAIT_DURATION_MS);
// Verify callback is run on the supplied executor
assertFalse(callback.onStartedCalled);
executor.runAll();
assertTrue(callback.onStartedCalled);
assertNotNull(callback.reservation);
SoftApConfiguration softApConfig = callback.reservation.getSoftApConfiguration();
assertNotNull(softApConfig);
assertSsidEquals(softApConfig, TEST_SSID_UNQUOTED);
assertEquals(TEST_PASSPHRASE, softApConfig.getPassphrase());
} finally {
// clean up
stopLocalOnlyHotspot(callback, wifiEnabled);
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Read the content of the given resource file into a String.
*
* @param filename String name of the file
* @return String
* @throws IOException
*/
private String loadResourceFile(String filename) throws IOException {
InputStream in = getClass().getClassLoader().getResourceAsStream(filename);
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder builder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
builder.append(line).append("\n");
}
return builder.toString();
}
/**
* Verify that changing the mac randomization setting of a Passpoint configuration.
*/
public void testMacRandomizationSettingPasspoint() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
String configStr = loadResourceFile(PASSPOINT_INSTALLATION_FILE_WITH_CA_CERT);
PasspointConfiguration config =
ConfigParser.parsePasspointConfig(TYPE_WIFI_CONFIG, configStr.getBytes());
String fqdn = config.getHomeSp().getFqdn();
String uniqueId = config.getUniqueId();
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
uiAutomation.adoptShellPermissionIdentity();
mWifiManager.addOrUpdatePasspointConfiguration(config);
PasspointConfiguration passpointConfig = getTargetPasspointConfiguration(
mWifiManager.getPasspointConfigurations(), uniqueId);
assertNotNull("The installed passpoint profile is missing", passpointConfig);
assertTrue("Mac randomization should be enabled for passpoint networks by default.",
passpointConfig.isMacRandomizationEnabled());
mWifiManager.setMacRandomizationSettingPasspointEnabled(fqdn, false);
passpointConfig = getTargetPasspointConfiguration(
mWifiManager.getPasspointConfigurations(), uniqueId);
assertNotNull("The installed passpoint profile is missing", passpointConfig);
assertFalse("Mac randomization should be disabled by the API call.",
passpointConfig.isMacRandomizationEnabled());
} finally {
// Clean up
mWifiManager.removePasspointConfiguration(fqdn);
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Verify that the {@link android.Manifest.permission#NETWORK_STACK} permission is never held by
* any package.
* <p>
* No apps should <em>ever</em> attempt to acquire this permission, since it would give those
* apps extremely broad access to connectivity functionality.
*/
public void testNetworkStackPermission() {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
final PackageManager pm = getContext().getPackageManager();
final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[] {
android.Manifest.permission.NETWORK_STACK
}, PackageManager.MATCH_UNINSTALLED_PACKAGES);
for (PackageInfo pi : holding) {
fail("The NETWORK_STACK permission must not be held by " + pi.packageName
+ " and must be revoked for security reasons");
}
}
/**
* Verify that the {@link android.Manifest.permission#NETWORK_SETTINGS} permission is
* never held by any package.
* <p>
* Only Settings, SysUi, NetworkStack and shell apps should <em>ever</em> attempt to acquire
* this permission, since it would give those apps extremely broad access to connectivity
* functionality. The permission is intended to be granted to only those apps with direct user
* access and no others.
*/
public void testNetworkSettingsPermission() {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
final PackageManager pm = getContext().getPackageManager();
final ArraySet<String> allowedPackages = new ArraySet();
final ArraySet<Integer> allowedUIDs = new ArraySet();
// explicitly add allowed UIDs
allowedUIDs.add(Process.SYSTEM_UID);
allowedUIDs.add(Process.SHELL_UID);
allowedUIDs.add(Process.PHONE_UID);
allowedUIDs.add(Process.NETWORK_STACK_UID);
allowedUIDs.add(Process.NFC_UID);
// only quick settings is allowed to bind to the BIND_QUICK_SETTINGS_TILE permission, using
// this fact to determined allowed package name for sysui. This is a signature permission,
// so allow any package with this permission.
final List<PackageInfo> sysuiPackages = pm.getPackagesHoldingPermissions(new String[] {
android.Manifest.permission.BIND_QUICK_SETTINGS_TILE
}, PackageManager.MATCH_UNINSTALLED_PACKAGES);
for (PackageInfo info : sysuiPackages) {
allowedPackages.add(info.packageName);
}
// the captive portal flow also currently holds the NETWORK_SETTINGS permission
final Intent intent = new Intent(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN);
final ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DISABLED_COMPONENTS);
if (ri != null) {
allowedPackages.add(ri.activityInfo.packageName);
}
final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[] {
android.Manifest.permission.NETWORK_SETTINGS
}, PackageManager.MATCH_UNINSTALLED_PACKAGES);
StringBuilder stringBuilder = new StringBuilder();
for (PackageInfo pi : holding) {
String packageName = pi.packageName;
// this is an explicitly allowed package
if (allowedPackages.contains(packageName)) continue;
// now check if the packages are from allowed UIDs
int uid = -1;
try {
uid = pm.getPackageUidAsUser(packageName, UserHandle.USER_SYSTEM);
} catch (PackageManager.NameNotFoundException e) {
continue;
}
if (!allowedUIDs.contains(uid)) {
stringBuilder.append("The NETWORK_SETTINGS permission must not be held by "
+ packageName + ":" + uid + " and must be revoked for security reasons\n");
}
}
if (stringBuilder.length() > 0) {
fail(stringBuilder.toString());
}
}
/**
* Verify that the {@link android.Manifest.permission#NETWORK_SETUP_WIZARD} permission is
* only held by the device setup wizard application.
* <p>
* Only the SetupWizard app should <em>ever</em> attempt to acquire this
* permission, since it would give those apps extremely broad access to connectivity
* functionality. The permission is intended to be granted to only the device setup wizard.
*/
public void testNetworkSetupWizardPermission() {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
final ArraySet<String> allowedPackages = new ArraySet();
final PackageManager pm = getContext().getPackageManager();
final Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_SETUP_WIZARD);
final ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DISABLED_COMPONENTS);
String validPkg = "";
if (ri != null) {
allowedPackages.add(ri.activityInfo.packageName);
validPkg = ri.activityInfo.packageName;
}
final Intent preIntent = new Intent("com.android.setupwizard.OEM_PRE_SETUP");
preIntent.addCategory(Intent.CATEGORY_DEFAULT);
final ResolveInfo preRi = pm
.resolveActivity(preIntent, PackageManager.MATCH_DISABLED_COMPONENTS);
String prePackageName = "";
if (null != preRi) {
prePackageName = preRi.activityInfo.packageName;
}
final Intent postIntent = new Intent("com.android.setupwizard.OEM_POST_SETUP");
postIntent.addCategory(Intent.CATEGORY_DEFAULT);
final ResolveInfo postRi = pm
.resolveActivity(postIntent, PackageManager.MATCH_DISABLED_COMPONENTS);
String postPackageName = "";
if (null != postRi) {
postPackageName = postRi.activityInfo.packageName;
}
if (!TextUtils.isEmpty(prePackageName) && !TextUtils.isEmpty(postPackageName)
&& prePackageName.equals(postPackageName)) {
allowedPackages.add(prePackageName);
}
final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[]{
android.Manifest.permission.NETWORK_SETUP_WIZARD
}, PackageManager.MATCH_UNINSTALLED_PACKAGES);
for (PackageInfo pi : holding) {
if (!allowedPackages.contains(pi.packageName)) {
fail("The NETWORK_SETUP_WIZARD permission must not be held by " + pi.packageName
+ " and must be revoked for security reasons [" + validPkg + "]");
}
}
}
/**
* Verify that the {@link android.Manifest.permission#NETWORK_MANAGED_PROVISIONING} permission
* is only held by the device managed provisioning application.
* <p>
* Only the ManagedProvisioning app should <em>ever</em> attempt to acquire this
* permission, since it would give those apps extremely broad access to connectivity
* functionality. The permission is intended to be granted to only the device managed
* provisioning.
*/
public void testNetworkManagedProvisioningPermission() {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
final PackageManager pm = getContext().getPackageManager();
// TODO(b/115980767): Using hardcoded package name. Need a better mechanism to find the
// managed provisioning app.
// Ensure that the package exists.
final Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setPackage(MANAGED_PROVISIONING_PACKAGE_NAME);
final ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DISABLED_COMPONENTS);
String validPkg = "";
if (ri != null) {
validPkg = ri.activityInfo.packageName;
}
String dpmHolderName = null;
if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
DevicePolicyManager dpm = getContext().getSystemService(DevicePolicyManager.class);
if (dpm != null) {
dpmHolderName = dpm.getDevicePolicyManagementRoleHolderPackage();
}
}
final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[] {
android.Manifest.permission.NETWORK_MANAGED_PROVISIONING
}, PackageManager.MATCH_UNINSTALLED_PACKAGES);
for (PackageInfo pi : holding) {
if (!Objects.equals(pi.packageName, validPkg)
&& !Objects.equals(pi.packageName, dpmHolderName)) {
fail("The NETWORK_MANAGED_PROVISIONING permission must not be held by "
+ pi.packageName + " and must be revoked for security reasons ["
+ validPkg + ", " + dpmHolderName + "]");
}
}
}
/**
* Verify that the {@link android.Manifest.permission#WIFI_SET_DEVICE_MOBILITY_STATE} permission
* is held by at most one application.
*/
public void testWifiSetDeviceMobilityStatePermission() {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
final PackageManager pm = getContext().getPackageManager();
final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[] {
android.Manifest.permission.WIFI_SET_DEVICE_MOBILITY_STATE
}, PackageManager.MATCH_UNINSTALLED_PACKAGES);
List<String> uniquePackageNames = holding
.stream()
.map(pi -> pi.packageName)
.distinct()
.collect(Collectors.toList());
if (uniquePackageNames.size() > 1) {
fail("The WIFI_SET_DEVICE_MOBILITY_STATE permission must not be held by more than one "
+ "application, but is held by " + uniquePackageNames.size() + " applications: "
+ String.join(", ", uniquePackageNames));
}
}
/**
* Verify that the {@link android.Manifest.permission#NETWORK_CARRIER_PROVISIONING} permission
* is held by at most one application.
*/
public void testNetworkCarrierProvisioningPermission() {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
final PackageManager pm = getContext().getPackageManager();
final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[] {
android.Manifest.permission.NETWORK_CARRIER_PROVISIONING
}, PackageManager.MATCH_UNINSTALLED_PACKAGES);
List<String> uniquePackageNames = holding
.stream()
.map(pi -> pi.packageName)
.distinct()
.collect(Collectors.toList());
if (uniquePackageNames.size() > 2) {
fail("The NETWORK_CARRIER_PROVISIONING permission must not be held by more than two "
+ "applications, but is held by " + uniquePackageNames.size() + " applications: "
+ String.join(", ", uniquePackageNames));
}
}
/**
* Verify that the {@link android.Manifest.permission#WIFI_UPDATE_USABILITY_STATS_SCORE}
* permission is held by at most one application.
*/
public void testUpdateWifiUsabilityStatsScorePermission() {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
final PackageManager pm = getContext().getPackageManager();
final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[] {
android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE
}, PackageManager.MATCH_UNINSTALLED_PACKAGES);
Set<String> uniqueNonSystemPackageNames = new HashSet<>();
for (PackageInfo pi : holding) {
String packageName = pi.packageName;
// Shell is allowed to hold this permission for testing.
int uid = -1;
try {
uid = pm.getPackageUidAsUser(packageName, UserHandle.USER_SYSTEM);
} catch (PackageManager.NameNotFoundException e) {
continue;
}
if (uid == Process.SHELL_UID) continue;
uniqueNonSystemPackageNames.add(packageName);
}
if (uniqueNonSystemPackageNames.size() > 1) {
fail("The WIFI_UPDATE_USABILITY_STATS_SCORE permission must not be held by more than "
+ "one application, but is held by " + uniqueNonSystemPackageNames.size()
+ " applications: " + String.join(", ", uniqueNonSystemPackageNames));
}
}
private void turnScreenOnNoDelay() throws Exception {
mUiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
mUiDevice.executeShellCommand("wm dismiss-keyguard");
}
private void turnScreenOn() throws Exception {
turnScreenOnNoDelay();
// Since the screen on/off intent is ordered, they will not be sent right now.
Thread.sleep(DURATION_SCREEN_TOGGLE);
}
private void turnScreenOffNoDelay() throws Exception {
mUiDevice.executeShellCommand("input keyevent KEYCODE_SLEEP");
}
private void turnScreenOff() throws Exception {
turnScreenOffNoDelay();
// Since the screen on/off intent is ordered, they will not be sent right now.
Thread.sleep(DURATION_SCREEN_TOGGLE);
}
private void assertWifiScanningIsOn() {
if (!mWifiManager.isScanAlwaysAvailable()) {
fail("Wi-Fi scanning should be on.");
}
}
private void runWithScanning(ThrowingRunnable r, boolean isEnabled) throws Exception {
boolean scanModeChangedForTest = false;
if (mWifiManager.isScanAlwaysAvailable() != isEnabled) {
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.setScanAlwaysAvailable(isEnabled));
scanModeChangedForTest = true;
}
try {
r.run();
} finally {
if (scanModeChangedForTest) {
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.setScanAlwaysAvailable(!isEnabled));
}
}
}
/**
* Verify that Wi-Fi scanning is not turned off when the screen turns off while wifi is disabled
* but location is on.
* @throws Exception
*/
public void testScreenOffDoesNotTurnOffWifiScanningWhenWifiDisabled() throws Exception {
if (FeatureUtil.isTV() || FeatureUtil.isAutomotive()) {
// TV and auto do not support the setting options of WIFI scanning and Bluetooth
// scanning
return;
}
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
if (!hasLocationFeature()) {
// skip the test if location is not supported
return;
}
if (!isLocationEnabled()) {
fail("Please enable location for this test - since Marshmallow WiFi scan results are"
+ " empty when location is disabled!");
}
runWithScanning(() -> {
setWifiEnabled(false);
turnScreenOn();
assertWifiScanningIsOn();
// Toggle screen and verify Wi-Fi scanning is still on.
turnScreenOff();
assertWifiScanningIsOn();
turnScreenOn();
assertWifiScanningIsOn();
}, true /* run with enabled*/);
}
/**
* Verify that Wi-Fi scanning is not turned off when the screen turns off while wifi is enabled.
* @throws Exception
*/
public void testScreenOffDoesNotTurnOffWifiScanningWhenWifiEnabled() throws Exception {
if (FeatureUtil.isTV() || FeatureUtil.isAutomotive()) {
// TV and auto do not support the setting options of WIFI scanning and Bluetooth
// scanning
return;
}
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
if (!hasLocationFeature()) {
// skip the test if location is not supported
return;
}
if (!isLocationEnabled()) {
fail("Please enable location for this test - since Marshmallow WiFi scan results are"
+ " empty when location is disabled!");
}
runWithScanning(() -> {
setWifiEnabled(true);
turnScreenOn();
assertWifiScanningIsOn();
// Toggle screen and verify Wi-Fi scanning is still on.
turnScreenOff();
assertWifiScanningIsOn();
turnScreenOn();
assertWifiScanningIsOn();
}, true /* run with enabled*/);
}
/**
* Verify that the platform supports a reasonable number of suggestions per app.
* @throws Exception
*/
public void testMaxNumberOfNetworkSuggestionsPerApp() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
assertTrue(mWifiManager.getMaxNumberOfNetworkSuggestionsPerApp()
> ENFORCED_NUM_NETWORK_SUGGESTIONS_PER_APP);
}
private void verifyRegisterSoftApCallback(TestExecutor executor, TestSoftApCallback callback)
throws Exception {
// Register callback to get SoftApCapability
mWifiManager.registerSoftApCallback(executor, callback);
PollingCheck.check(
"SoftAp register failed!", 5_000,
() -> {
executor.runAll();
// Verify callback is run on the supplied executor and called
return callback.getOnStateChangedCalled()
&& callback.getOnSoftapInfoChangedCalledCount() > 0
&& callback.getOnSoftApCapabilityChangedCalled()
&& callback.getOnConnectedClientCalled();
});
}
private void verifyLohsRegisterSoftApCallback(TestExecutor executor,
TestSoftApCallback callback) throws Exception {
// Register callback to get SoftApCapability
if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
mWifiManager.registerLocalOnlyHotspotSoftApCallback(executor, callback);
} else {
mWifiManager.registerSoftApCallback(executor, callback);
}
PollingCheck.check(
"SoftAp register failed!", 5_000,
() -> {
executor.runAll();
// Verify callback is run on the supplied executor and called
return callback.getOnStateChangedCalled() &&
callback.getOnSoftapInfoChangedCalledCount() > 0 &&
callback.getOnSoftApCapabilityChangedCalled() &&
callback.getOnConnectedClientCalled();
});
}
private void verifySetGetSoftApConfig(SoftApConfiguration targetConfig) {
mWifiManager.setSoftApConfiguration(targetConfig);
// Bssid set dodesn't support for tethered hotspot
SoftApConfiguration currentConfig = mWifiManager.getSoftApConfiguration();
compareSoftApConfiguration(targetConfig, currentConfig);
if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S)) {
assertTrue(currentConfig.isUserConfiguration());
}
assertNotNull(currentConfig.getPersistentRandomizedMacAddress());
if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
// Verify set/get with the deprecated set/getSsid()
SoftApConfiguration oldSsidConfig = new SoftApConfiguration.Builder(targetConfig)
.setWifiSsid(null)
.setSsid(targetConfig.getSsid()).build();
mWifiManager.setSoftApConfiguration(oldSsidConfig);
currentConfig = mWifiManager.getSoftApConfiguration();
compareSoftApConfiguration(oldSsidConfig, currentConfig);
}
}
private void compareSoftApConfiguration(SoftApConfiguration currentConfig,
SoftApConfiguration testSoftApConfig) {
if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
assertEquals(currentConfig.getWifiSsid(), testSoftApConfig.getWifiSsid());
}
assertEquals(currentConfig.getSsid(), testSoftApConfig.getSsid());
assertEquals(currentConfig.getBssid(), testSoftApConfig.getBssid());
assertEquals(currentConfig.getSecurityType(), testSoftApConfig.getSecurityType());
assertEquals(currentConfig.getPassphrase(), testSoftApConfig.getPassphrase());
assertEquals(currentConfig.isHiddenSsid(), testSoftApConfig.isHiddenSsid());
assertEquals(currentConfig.getBand(), testSoftApConfig.getBand());
assertEquals(currentConfig.getChannel(), testSoftApConfig.getChannel());
assertEquals(currentConfig.getMaxNumberOfClients(),
testSoftApConfig.getMaxNumberOfClients());
assertEquals(currentConfig.isAutoShutdownEnabled(),
testSoftApConfig.isAutoShutdownEnabled());
assertEquals(currentConfig.getShutdownTimeoutMillis(),
testSoftApConfig.getShutdownTimeoutMillis());
assertEquals(currentConfig.isClientControlByUserEnabled(),
testSoftApConfig.isClientControlByUserEnabled());
assertEquals(currentConfig.getAllowedClientList(),
testSoftApConfig.getAllowedClientList());
assertEquals(currentConfig.getBlockedClientList(),
testSoftApConfig.getBlockedClientList());
if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S)) {
assertEquals(currentConfig.getMacRandomizationSetting(),
testSoftApConfig.getMacRandomizationSetting());
assertEquals(currentConfig.getChannels().toString(),
testSoftApConfig.getChannels().toString());
assertEquals(currentConfig.isBridgedModeOpportunisticShutdownEnabled(),
testSoftApConfig.isBridgedModeOpportunisticShutdownEnabled());
assertEquals(currentConfig.isIeee80211axEnabled(),
testSoftApConfig.isIeee80211axEnabled());
if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
assertEquals(currentConfig.getBridgedModeOpportunisticShutdownTimeoutMillis(),
testSoftApConfig.getBridgedModeOpportunisticShutdownTimeoutMillis());
assertEquals(currentConfig.isIeee80211beEnabled(),
testSoftApConfig.isIeee80211beEnabled());
assertEquals(currentConfig.getVendorElements(),
testSoftApConfig.getVendorElements());
assertArrayEquals(
currentConfig.getAllowedAcsChannels(SoftApConfiguration.BAND_2GHZ),
testSoftApConfig.getAllowedAcsChannels(SoftApConfiguration.BAND_2GHZ));
assertArrayEquals(
currentConfig.getAllowedAcsChannels(SoftApConfiguration.BAND_5GHZ),
testSoftApConfig.getAllowedAcsChannels(SoftApConfiguration.BAND_5GHZ));
assertArrayEquals(
currentConfig.getAllowedAcsChannels(SoftApConfiguration.BAND_6GHZ),
testSoftApConfig.getAllowedAcsChannels(SoftApConfiguration.BAND_6GHZ));
assertEquals(currentConfig.getMaxChannelBandwidth(),
testSoftApConfig.getMaxChannelBandwidth());
}
}
}
private void turnOffWifiAndTetheredHotspotIfEnabled() throws Exception {
if (mWifiManager.isWifiEnabled()) {
Log.d(TAG, "Turn off WiFi");
mWifiManager.setWifiEnabled(false);
PollingCheck.check(
"Wifi turn off failed!", 2_000,
() -> mWifiManager.isWifiEnabled() == false);
}
if (mWifiManager.isWifiApEnabled()) {
mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
Log.d(TAG, "Turn off tethered Hotspot");
PollingCheck.check(
"SoftAp turn off failed!", 2_000,
() -> mWifiManager.isWifiApEnabled() == false);
}
}
private void verifyBridgedModeSoftApCallback(TestExecutor executor,
TestSoftApCallback callback, boolean shouldFallbackSingleApMode, boolean isEnabled)
throws Exception {
// Verify state and info callback value as expected
PollingCheck.check(
"SoftAp state and info on bridged AP mode are mismatch!!!"
+ " shouldFallbackSingleApMode = " + shouldFallbackSingleApMode
+ ", isEnabled = " + isEnabled, 10_000,
() -> {
executor.runAll();
int expectedState = isEnabled ? WifiManager.WIFI_AP_STATE_ENABLED
: WifiManager.WIFI_AP_STATE_DISABLED;
int expectedInfoSize = isEnabled
? (shouldFallbackSingleApMode ? 1 : 2) : 0;
return expectedState == callback.getCurrentState()
&& callback.getCurrentSoftApInfoList().size() == expectedInfoSize;
});
}
private boolean shouldFallbackToSingleAp(int[] bands, SoftApCapability capability) {
for (int band : bands) {
if (capability.getSupportedChannelList(band).length == 0) {
return true;
}
}
return false;
}
private SparseIntArray getAvailableBandAndChannelForTesting(SoftApCapability capability) {
final int[] bands = {SoftApConfiguration.BAND_2GHZ, SoftApConfiguration.BAND_5GHZ,
SoftApConfiguration.BAND_6GHZ, SoftApConfiguration.BAND_60GHZ};
SparseIntArray testBandsAndChannels = new SparseIntArray();
if (!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S)) {
testBandsAndChannels.put(SoftApConfiguration.BAND_2GHZ, 1);
return testBandsAndChannels;
}
for (int band : bands) {
int[] supportedList = capability.getSupportedChannelList(band);
if (supportedList.length != 0) {
testBandsAndChannels.put(band, supportedList[0]);
}
}
return testBandsAndChannels;
}
/**
* Test SoftApConfiguration#getPersistentRandomizedMacAddress(). There are two test cases in
* this test.
* 1. configure two different SoftApConfigurations (different SSID) and verify that randomized
* MAC address is different.
* 2. configure A then B then A (SSIDs) and verify that the 1st and 3rd MAC addresses are the
* same.
*/
public void testSoftApConfigurationGetPersistentRandomizedMacAddress() throws Exception {
SoftApConfiguration currentConfig = ShellIdentityUtils.invokeWithShellPermissions(
mWifiManager::getSoftApConfiguration);
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.setSoftApConfiguration(new SoftApConfiguration.Builder()
.setSsid(currentConfig.getSsid() + "test").build()));
SoftApConfiguration changedSsidConfig = ShellIdentityUtils.invokeWithShellPermissions(
mWifiManager::getSoftApConfiguration);
assertNotEquals(currentConfig.getPersistentRandomizedMacAddress(),
changedSsidConfig.getPersistentRandomizedMacAddress());
// set currentConfig
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.setSoftApConfiguration(currentConfig));
SoftApConfiguration changedSsidBackConfig = ShellIdentityUtils.invokeWithShellPermissions(
mWifiManager::getSoftApConfiguration);
assertEquals(currentConfig.getPersistentRandomizedMacAddress(),
changedSsidBackConfig.getPersistentRandomizedMacAddress());
}
/**
* Test bridged AP enable succeeful when device supports it.
* Also verify the callback info update correctly.
* @throws Exception
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
public void testTetheredBridgedAp() throws Exception {
// check that softap bridged mode is supported by the device
if (!mWifiManager.isBridgedApConcurrencySupported()) {
return;
}
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
TestExecutor executor = new TestExecutor();
TestSoftApCallback callback = new TestSoftApCallback(mLock);
try {
uiAutomation.adoptShellPermissionIdentity();
// Off/On Wifi to make sure that we get the supported channel
turnOffWifiAndTetheredHotspotIfEnabled();
mWifiManager.setWifiEnabled(true);
PollingCheck.check(
"Wifi turn on failed!", 2_000,
() -> mWifiManager.isWifiEnabled() == true);
turnOffWifiAndTetheredHotspotIfEnabled();
verifyRegisterSoftApCallback(executor, callback);
int[] testBands = {SoftApConfiguration.BAND_2GHZ,
SoftApConfiguration.BAND_5GHZ};
int[] expectedBands = {SoftApConfiguration.BAND_2GHZ,
SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ};
// Test bridged SoftApConfiguration set and get (setBands)
SoftApConfiguration testSoftApConfig =
generateSoftApConfigBuilderWithSsid(TEST_SSID_UNQUOTED)
.setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
.setBands(expectedBands)
.build();
boolean shouldFallbackToSingleAp = shouldFallbackToSingleAp(testBands,
callback.getCurrentSoftApCapability());
verifySetGetSoftApConfig(testSoftApConfig);
// start tethering which used to verify startTetheredHotspot
mTetheringManager.startTethering(ConnectivityManager.TETHERING_WIFI, executor,
new TetheringManager.StartTetheringCallback() {
@Override
public void onTetheringFailed(final int result) {
}
});
verifyBridgedModeSoftApCallback(executor, callback,
shouldFallbackToSingleAp, true /* enabled */);
// stop tethering which used to verify stopSoftAp
mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
verifyBridgedModeSoftApCallback(executor, callback,
shouldFallbackToSingleAp, false /* disabled */);
} finally {
mWifiManager.unregisterSoftApCallback(callback);
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Test bridged AP with forced channel config enable succeeful when device supports it.
* Also verify the callback info update correctly.
* @throws Exception
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
public void testTetheredBridgedApWifiForcedChannel() throws Exception {
// check that softap bridged mode is supported by the device
if (!mWifiManager.isBridgedApConcurrencySupported()) {
return;
}
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
TestExecutor executor = new TestExecutor();
TestSoftApCallback callback = new TestSoftApCallback(mLock);
try {
uiAutomation.adoptShellPermissionIdentity();
// Off/On Wifi to make sure that we get the supported channel
turnOffWifiAndTetheredHotspotIfEnabled();
mWifiManager.setWifiEnabled(true);
PollingCheck.check(
"Wifi turn on failed!", 2_000,
() -> mWifiManager.isWifiEnabled() == true);
turnOffWifiAndTetheredHotspotIfEnabled();
verifyRegisterSoftApCallback(executor, callback);
boolean shouldFallbackToSingleAp = shouldFallbackToSingleAp(
new int[] {SoftApConfiguration.BAND_2GHZ, SoftApConfiguration.BAND_5GHZ},
callback.getCurrentSoftApCapability());
// Test when there are supported channels in both of the bands.
if (!shouldFallbackToSingleAp) {
// Test bridged SoftApConfiguration set and get (setChannels)
SparseIntArray dual_channels = new SparseIntArray(2);
dual_channels.put(SoftApConfiguration.BAND_2GHZ,
callback.getCurrentSoftApCapability()
.getSupportedChannelList(SoftApConfiguration.BAND_2GHZ)[0]);
dual_channels.put(SoftApConfiguration.BAND_5GHZ,
callback.getCurrentSoftApCapability()
.getSupportedChannelList(SoftApConfiguration.BAND_5GHZ)[0]);
SoftApConfiguration testSoftApConfig =
generateSoftApConfigBuilderWithSsid(TEST_SSID_UNQUOTED)
.setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
.setChannels(dual_channels)
.build();
verifySetGetSoftApConfig(testSoftApConfig);
// start tethering which used to verify startTetheredHotspot
mTetheringManager.startTethering(ConnectivityManager.TETHERING_WIFI, executor,
new TetheringManager.StartTetheringCallback() {
@Override
public void onTetheringFailed(final int result) {
}
});
verifyBridgedModeSoftApCallback(executor, callback,
shouldFallbackToSingleAp, true /* enabled */);
// stop tethering which used to verify stopSoftAp
mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
verifyBridgedModeSoftApCallback(executor, callback,
shouldFallbackToSingleAp, false /* disabled */);
}
} finally {
mWifiManager.unregisterSoftApCallback(callback);
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Verify that the configuration from getSoftApConfiguration is same as the configuration which
* set by setSoftApConfiguration. And depends softap capability callback to test different
* configuration.
* @throws Exception
*/
@VirtualDeviceNotSupported
public void testSetGetSoftApConfigurationAndSoftApCapabilityCallback() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// check that softap mode is supported by the device
if (!mWifiManager.isPortableHotspotSupported()) {
return;
}
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
TestExecutor executor = new TestExecutor();
TestSoftApCallback callback = new TestSoftApCallback(mLock);
try {
uiAutomation.adoptShellPermissionIdentity();
turnOffWifiAndTetheredHotspotIfEnabled();
verifyRegisterSoftApCallback(executor, callback);
SoftApConfiguration.Builder softApConfigBuilder =
generateSoftApConfigBuilderWithSsid(TEST_SSID_UNQUOTED)
.setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
.setAutoShutdownEnabled(true)
.setShutdownTimeoutMillis(100000)
.setBand(getAvailableBandAndChannelForTesting(
callback.getCurrentSoftApCapability()).keyAt(0))
.setHiddenSsid(false);
if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
softApConfigBuilder.setBridgedModeOpportunisticShutdownTimeoutMillis(30_000);
softApConfigBuilder.setVendorElements(TEST_VENDOR_ELEMENTS);
softApConfigBuilder.setAllowedAcsChannels(
SoftApConfiguration.BAND_2GHZ, new int[] {1, 6, 11});
softApConfigBuilder.setAllowedAcsChannels(
SoftApConfiguration.BAND_5GHZ, new int[] {149});
softApConfigBuilder.setAllowedAcsChannels(
SoftApConfiguration.BAND_6GHZ, new int[] {});
softApConfigBuilder.setMaxChannelBandwidth(SoftApInfo.CHANNEL_WIDTH_80MHZ);
}
// Test SoftApConfiguration set and get
verifySetGetSoftApConfig(softApConfigBuilder.build());
boolean isSupportCustomizedMac = callback.getCurrentSoftApCapability()
.areFeaturesSupported(
SoftApCapability.SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION)
&& PropertyUtil.isVndkApiLevelNewerThan(Build.VERSION_CODES.S);
//Test MAC_ADDRESS_CUSTOMIZATION supported config
if (isSupportCustomizedMac) {
softApConfigBuilder.setBssid(TEST_MAC)
.setMacRandomizationSetting(SoftApConfiguration.RANDOMIZATION_NONE);
// Test SoftApConfiguration set and get
verifySetGetSoftApConfig(softApConfigBuilder.build());
}
// Test CLIENT_FORCE_DISCONNECT supported config.
if (callback.getCurrentSoftApCapability()
.areFeaturesSupported(
SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT)) {
softApConfigBuilder.setMaxNumberOfClients(10);
softApConfigBuilder.setClientControlByUserEnabled(true);
softApConfigBuilder.setBlockedClientList(new ArrayList<>());
softApConfigBuilder.setAllowedClientList(new ArrayList<>());
verifySetGetSoftApConfig(softApConfigBuilder.build());
}
// Test SAE config
if (callback.getCurrentSoftApCapability()
.areFeaturesSupported(SoftApCapability.SOFTAP_FEATURE_WPA3_SAE)) {
softApConfigBuilder
.setPassphrase(TEST_PASSPHRASE,
SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION);
verifySetGetSoftApConfig(softApConfigBuilder.build());
softApConfigBuilder
.setPassphrase(TEST_PASSPHRASE,
SoftApConfiguration.SECURITY_TYPE_WPA3_SAE);
verifySetGetSoftApConfig(softApConfigBuilder.build());
}
// Test 11 AX control config.
if (callback.getCurrentSoftApCapability()
.areFeaturesSupported(SoftApCapability.SOFTAP_FEATURE_IEEE80211_AX)) {
softApConfigBuilder.setIeee80211axEnabled(true);
verifySetGetSoftApConfig(softApConfigBuilder.build());
}
// Test 11 BE control config
if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
if (callback.getCurrentSoftApCapability()
.areFeaturesSupported(SoftApCapability.SOFTAP_FEATURE_IEEE80211_BE)) {
softApConfigBuilder.setIeee80211beEnabled(true);
verifySetGetSoftApConfig(softApConfigBuilder.build());
}
}
if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S)) {
softApConfigBuilder.setBridgedModeOpportunisticShutdownEnabled(false);
verifySetGetSoftApConfig(softApConfigBuilder.build());
}
} finally {
mWifiManager.unregisterSoftApCallback(callback);
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Verify that startTetheredHotspot with specific channel config.
* @throws Exception
*/
@VirtualDeviceNotSupported
public void testStartTetheredHotspotWithChannelConfigAndSoftApStateAndInfoCallback()
throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// check that softap mode is supported by the device
if (!mWifiManager.isPortableHotspotSupported()) {
return;
}
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
TestExecutor executor = new TestExecutor();
TestSoftApCallback callback = new TestSoftApCallback(mLock);
try {
uiAutomation.adoptShellPermissionIdentity();
// check that tethering is supported by the device
if (!mTetheringManager.isTetheringSupported()) {
return;
}
turnOffWifiAndTetheredHotspotIfEnabled();
verifyRegisterSoftApCallback(executor, callback);
SparseIntArray testBandsAndChannels = getAvailableBandAndChannelForTesting(
callback.getCurrentSoftApCapability());
if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S)) {
assertNotEquals(0, testBandsAndChannels.size());
}
boolean isSupportCustomizedMac = callback.getCurrentSoftApCapability()
.areFeaturesSupported(
SoftApCapability.SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION)
&& PropertyUtil.isVndkApiLevelNewerThan(Build.VERSION_CODES.S);
SoftApConfiguration.Builder testSoftApConfigBuilder =
generateSoftApConfigBuilderWithSsid(TEST_SSID_UNQUOTED)
.setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
.setChannel(testBandsAndChannels.valueAt(0), testBandsAndChannels.keyAt(0));
if (isSupportCustomizedMac) {
testSoftApConfigBuilder.setBssid(TEST_MAC)
.setMacRandomizationSetting(SoftApConfiguration.RANDOMIZATION_NONE);
}
SoftApConfiguration testSoftApConfig = testSoftApConfigBuilder.build();
mWifiManager.setSoftApConfiguration(testSoftApConfig);
// start tethering which used to verify startTetheredHotspot
mTetheringManager.startTethering(ConnectivityManager.TETHERING_WIFI, executor,
new TetheringManager.StartTetheringCallback() {
@Override
public void onTetheringFailed(final int result) {
}
});
// Verify state and info callback value as expected
PollingCheck.check(
"SoftAp channel and state mismatch!!!", 10_000,
() -> {
executor.runAll();
int sapChannel = ScanResult.convertFrequencyMhzToChannelIfSupported(
callback.getCurrentSoftApInfo().getFrequency());
boolean isInfoCallbackSupported =
callback.getOnSoftapInfoChangedCalledCount() > 1;
if (isInfoCallbackSupported) {
return WifiManager.WIFI_AP_STATE_ENABLED == callback.getCurrentState()
&& testBandsAndChannels.valueAt(0) == sapChannel;
}
return WifiManager.WIFI_AP_STATE_ENABLED == callback.getCurrentState();
});
// After Soft Ap enabled, check SoftAp info if it supported
if (isSupportCustomizedMac && callback.getOnSoftapInfoChangedCalledCount() > 1) {
assertEquals(callback.getCurrentSoftApInfo().getBssid(), TEST_MAC);
}
if (PropertyUtil.isVndkApiLevelNewerThan(Build.VERSION_CODES.S)
&& callback.getOnSoftapInfoChangedCalledCount() > 1) {
assertNotEquals(callback.getCurrentSoftApInfo().getWifiStandard(),
ScanResult.WIFI_STANDARD_UNKNOWN);
}
if (callback.getOnSoftapInfoChangedCalledCount() > 1) {
assertTrue(callback.getCurrentSoftApInfo().getAutoShutdownTimeoutMillis() > 0);
}
} finally {
// stop tethering which used to verify stopSoftAp
mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
// Verify clean up
PollingCheck.check(
"Stop Softap failed", 3_000,
() -> {
executor.runAll();
return WifiManager.WIFI_AP_STATE_DISABLED == callback.getCurrentState() &&
0 == callback.getCurrentSoftApInfo().getBandwidth() &&
0 == callback.getCurrentSoftApInfo().getFrequency();
});
if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S)) {
assertEquals(callback.getCurrentSoftApInfo().getBssid(), null);
assertEquals(ScanResult.WIFI_STANDARD_UNKNOWN,
callback.getCurrentSoftApInfo().getWifiStandard());
}
mWifiManager.unregisterSoftApCallback(callback);
uiAutomation.dropShellPermissionIdentity();
}
}
private static class TestActionListener implements WifiManager.ActionListener {
private final Object mLock;
public boolean onSuccessCalled = false;
public boolean onFailedCalled = false;
public int failureReason = -1;
TestActionListener(Object lock) {
mLock = lock;
}
@Override
public void onSuccess() {
synchronized (mLock) {
onSuccessCalled = true;
mLock.notify();
}
}
@Override
public void onFailure(int reason) {
synchronized (mLock) {
onFailedCalled = true;
failureReason = reason;
mLock.notify();
}
}
}
/**
* Triggers connection to one of the saved networks using {@link WifiManager#connect(
* int, WifiManager.ActionListener)} or {@link WifiManager#connect(WifiConfiguration,
* WifiManager.ActionListener)}
*
* @param withNetworkId Use networkId for triggering connection, false for using
* WifiConfiguration.
* @throws Exception
*/
private void testConnect(boolean withNetworkId) throws Exception {
TestActionListener actionListener = new TestActionListener(mLock);
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
List<WifiConfiguration> savedNetworks = null;
try {
uiAutomation.adoptShellPermissionIdentity();
// These below API's only work with privileged permissions (obtained via shell identity
// for test)
savedNetworks = mWifiManager.getConfiguredNetworks();
// Disable all the saved networks to trigger disconnect & disable autojoin.
for (WifiConfiguration network : savedNetworks) {
assertTrue(mWifiManager.disableNetwork(network.networkId));
}
waitForDisconnection();
// Now trigger connection to the last saved network.
WifiConfiguration savedNetworkToConnect =
savedNetworks.get(savedNetworks.size() - 1);
synchronized (mLock) {
try {
if (withNetworkId) {
mWifiManager.connect(savedNetworkToConnect.networkId, actionListener);
} else {
mWifiManager.connect(savedNetworkToConnect, actionListener);
}
// now wait for callback
mLock.wait(TEST_WAIT_DURATION_MS);
} catch (InterruptedException e) {
}
}
// check if we got the success callback
assertTrue(actionListener.onSuccessCalled);
// Wait for connection to complete & ensure we are connected to the saved network.
waitForConnection();
assertEquals(savedNetworkToConnect.networkId,
mWifiManager.getConnectionInfo().getNetworkId());
} finally {
// Re-enable all saved networks before exiting.
if (savedNetworks != null) {
for (WifiConfiguration network : savedNetworks) {
mWifiManager.enableNetwork(network.networkId, true);
}
}
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests {@link WifiManager#connect(int, WifiManager.ActionListener)} to an existing saved
* network.
*/
public void testConnectWithNetworkId() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
testConnect(true);
}
/**
* Tests {@link WifiManager#connect(WifiConfiguration, WifiManager.ActionListener)} to an
* existing saved network.
*/
public void testConnectWithWifiConfiguration() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
testConnect(false);
}
private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
private final Object mLock;
public boolean onAvailableCalled = false;
public Network network;
public NetworkCapabilities networkCapabilities;
TestNetworkCallback(Object lock) {
mLock = lock;
}
@Override
public void onAvailable(Network network) {
synchronized (mLock) {
onAvailableCalled = true;
this.network = network;
}
}
@Override
public void onCapabilitiesChanged(Network network,
NetworkCapabilities networkCapabilities) {
synchronized (mLock) {
this.networkCapabilities = networkCapabilities;
mLock.notify();
}
}
}
private void waitForNetworkCallbackAndCheckForMeteredness(boolean expectMetered) {
TestNetworkCallback networkCallbackListener = new TestNetworkCallback(mLock);
synchronized (mLock) {
try {
NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder()
.addTransportType(TRANSPORT_WIFI);
if (expectMetered) {
networkRequestBuilder.removeCapability(NET_CAPABILITY_NOT_METERED);
} else {
networkRequestBuilder.addCapability(NET_CAPABILITY_NOT_METERED);
}
// File a request for wifi network.
mConnectivityManager.registerNetworkCallback(
networkRequestBuilder.build(), networkCallbackListener);
// now wait for callback
mLock.wait(TEST_WAIT_DURATION_MS);
} catch (InterruptedException e) {
}
}
assertTrue(networkCallbackListener.onAvailableCalled);
}
/**
* Tests {@link WifiManager#save(WifiConfiguration, WifiManager.ActionListener)} by marking
* an existing saved network metered.
*/
public void testSave() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
TestActionListener actionListener = new TestActionListener(mLock);
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
List<WifiConfiguration> savedNetworks = null;
WifiConfiguration currentConfig = null;
try {
uiAutomation.adoptShellPermissionIdentity();
// These below API's only work with privileged permissions (obtained via shell identity
// for test)
// Trigger a scan & wait for connection to one of the saved networks.
mWifiManager.startScan();
waitForConnection();
WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
savedNetworks = mWifiManager.getConfiguredNetworks();
// find the current network's WifiConfiguration
currentConfig = savedNetworks
.stream()
.filter(config -> config.networkId == wifiInfo.getNetworkId())
.findAny()
.get();
// Ensure that the current network is not metered.
assertNotEquals("Ensure that the saved network is configured as unmetered",
currentConfig.meteredOverride,
WifiConfiguration.METERED_OVERRIDE_METERED);
// Disable all except the currently connected networks to avoid reconnecting to the
// wrong network after later setting the current network as metered.
for (WifiConfiguration network : savedNetworks) {
if (network.networkId != currentConfig.networkId) {
assertTrue(mWifiManager.disableNetwork(network.networkId));
}
}
// Check the network capabilities to ensure that the network is marked not metered.
waitForNetworkCallbackAndCheckForMeteredness(false);
// Now mark the network metered and save.
synchronized (mLock) {
try {
WifiConfiguration modSavedNetwork = new WifiConfiguration(currentConfig);
modSavedNetwork.meteredOverride = WifiConfiguration.METERED_OVERRIDE_METERED;
mWifiManager.save(modSavedNetwork, actionListener);
// now wait for callback
mLock.wait(TEST_WAIT_DURATION_MS);
} catch (InterruptedException e) {
}
}
// check if we got the success callback
assertTrue(actionListener.onSuccessCalled);
// Ensure we disconnected on marking the network metered & connect back.
waitForDisconnection();
waitForConnection();
// Check the network capabilities to ensure that the network is marked metered now.
waitForNetworkCallbackAndCheckForMeteredness(true);
} finally {
// Restore original network config (restore the meteredness back);
if (currentConfig != null) {
mWifiManager.updateNetwork(currentConfig);
}
// re-enable all networks
if (savedNetworks != null) {
for (WifiConfiguration network : savedNetworks) {
mWifiManager.enableNetwork(network.networkId, true);
}
}
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests {@link WifiManager#forget(int, WifiManager.ActionListener)} by adding/removing a new
* network.
*/
@AsbSecurityTest(cveBugId = 159373687)
public void testForget() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
TestActionListener actionListener = new TestActionListener(mLock);
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
int newNetworkId = INVALID_NETWORK_ID;
try {
uiAutomation.adoptShellPermissionIdentity();
// These below API's only work with privileged permissions (obtained via shell identity
// for test)
List<WifiConfiguration> savedNetworks = mWifiManager.getConfiguredNetworks();
WifiConfiguration newOpenNetwork = new WifiConfiguration();
newOpenNetwork.SSID = "\"" + TEST_SSID_UNQUOTED + "\"";
newNetworkId = mWifiManager.addNetwork(newOpenNetwork);
assertNotEquals(INVALID_NETWORK_ID, newNetworkId);
// Multi-type configurations might be converted to more than 1 configuration.
assertThat(savedNetworks.size() < mWifiManager.getConfiguredNetworks().size()).isTrue();
// Need an effectively-final holder because we need to modify inner Intent in callback.
class IntentHolder {
Intent intent;
}
IntentHolder intentHolder = new IntentHolder();
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Received CONFIGURED_NETWORKS_CHANGED_ACTION broadcast: " + intent);
intentHolder.intent = intent;
}
}, new IntentFilter(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION));
// Now remove the network
synchronized (mLock) {
try {
mWifiManager.forget(newNetworkId, actionListener);
// now wait for callback
mLock.wait(TEST_WAIT_DURATION_MS);
} catch (InterruptedException e) {
}
}
// check if we got the success callback
assertTrue(actionListener.onSuccessCalled);
PollingCheck.check(
"Didn't receive CONFIGURED_NETWORKS_CHANGED_ACTION broadcast!",
TEST_WAIT_DURATION_MS,
() -> intentHolder.intent != null);
Intent intent = intentHolder.intent;
assertEquals(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION, intent.getAction());
assertTrue(intent.getBooleanExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED, false));
assertEquals(WifiManager.CHANGE_REASON_REMOVED,
intent.getIntExtra(WifiManager.EXTRA_CHANGE_REASON, -1));
assertNull(intent.getParcelableExtra(WifiManager.EXTRA_WIFI_CONFIGURATION));
// Ensure that the new network has been successfully removed.
assertEquals(savedNetworks.size(), mWifiManager.getConfiguredNetworks().size());
} finally {
// For whatever reason, if the forget fails, try removing using the public remove API.
if (newNetworkId != INVALID_NETWORK_ID) mWifiManager.removeNetwork(newNetworkId);
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests {@link WifiManager#getFactoryMacAddresses()} returns at least one valid MAC address.
*/
@VirtualDeviceNotSupported
public void testGetFactoryMacAddresses() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
TestActionListener actionListener = new TestActionListener(mLock);
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
int newNetworkId = INVALID_NETWORK_ID;
try {
uiAutomation.adoptShellPermissionIdentity();
// Obtain the factory MAC address
String[] macAddresses = mWifiManager.getFactoryMacAddresses();
assertTrue("At list one MAC address should be returned.", macAddresses.length > 0);
try {
MacAddress mac = MacAddress.fromString(macAddresses[0]);
assertNotEquals(WifiInfo.DEFAULT_MAC_ADDRESS, mac);
assertFalse(MacAddressUtils.isMulticastAddress(mac));
} catch (IllegalArgumentException e) {
fail("Factory MAC address is invalid");
}
} finally {
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests {@link WifiManager#isApMacRandomizationSupported()} does not crash.
*/
public void testIsApMacRandomizationSupported() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
mWifiManager.isApMacRandomizationSupported();
}
/**
* Tests {@link WifiManager#isConnectedMacRandomizationSupported()} does not crash.
*/
public void testIsConnectedMacRandomizationSupported() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
mWifiManager.isConnectedMacRandomizationSupported();
}
/**
* Tests {@link WifiManager#isPreferredNetworkOffloadSupported()} does not crash.
*/
public void testIsPreferredNetworkOffloadSupported() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
mWifiManager.isPreferredNetworkOffloadSupported();
}
/** Test that PNO scans reconnects us when the device is disconnected and the screen is off. */
public void testPnoScan() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
if (!mWifiManager.isPreferredNetworkOffloadSupported()) {
// skip the test if PNO scanning is not supported
return;
}
// make sure we're connected
waitForConnection(WIFI_PNO_CONNECT_TIMEOUT_MILLIS);
WifiInfo currentNetwork = ShellIdentityUtils.invokeWithShellPermissions(
mWifiManager::getConnectionInfo);
// disable all networks that aren't already disabled
List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
mWifiManager::getConfiguredNetworks);
Set<Integer> disabledNetworkIds = new HashSet<>();
for (WifiConfiguration config : savedNetworks) {
if (config.getNetworkSelectionStatus().getNetworkSelectionDisableReason()
== WifiConfiguration.NetworkSelectionStatus.DISABLED_NONE) {
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.disableNetwork(config.networkId));
disabledNetworkIds.add(config.networkId);
}
}
try {
// wait for disconnection from current network
waitForDisconnection();
// turn screen off
turnScreenOffNoDelay();
// re-enable the current network - this will trigger PNO
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.enableNetwork(currentNetwork.getNetworkId(), false));
disabledNetworkIds.remove(currentNetwork.getNetworkId());
// PNO should reconnect us back to the network we disconnected from
waitForConnection(WIFI_PNO_CONNECT_TIMEOUT_MILLIS);
} finally {
// re-enable disabled networks
for (int disabledNetworkId : disabledNetworkIds) {
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.enableNetwork(disabledNetworkId, true));
}
}
}
/**
* Tests {@link WifiManager#isTdlsSupported()} does not crash.
*/
public void testIsTdlsSupported() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
mWifiManager.isTdlsSupported();
}
/**
* Tests {@link WifiManager#isStaApConcurrencySupported().
*/
public void testIsStaApConcurrencySupported() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// check that softap mode is supported by the device
if (!mWifiManager.isPortableHotspotSupported()) {
return;
}
assertTrue(mWifiManager.isWifiEnabled());
boolean isStaApConcurrencySupported = mWifiManager.isStaApConcurrencySupported();
// start local only hotspot.
TestLocalOnlyHotspotCallback callback = startLocalOnlyHotspot();
try {
if (isStaApConcurrencySupported) {
assertTrue(mWifiManager.isWifiEnabled());
} else {
// no concurrency, wifi should be disabled.
assertFalse(mWifiManager.isWifiEnabled());
}
} finally {
// clean up local only hotspot no matter if assertion passed or failed
stopLocalOnlyHotspot(callback, true);
}
assertTrue(mWifiManager.isWifiEnabled());
}
/**
* state is a bitset, where bit 0 indicates whether there was data in, and bit 1 indicates
* whether there was data out. Only count down on the latch once there was both data in and out.
*/
private static class TestTrafficStateCallback implements WifiManager.TrafficStateCallback {
public final CountDownLatch latch = new CountDownLatch(1);
private int mAccumulator = 0;
@Override
public void onStateChanged(int state) {
mAccumulator |= state;
if (mAccumulator == DATA_ACTIVITY_INOUT) {
latch.countDown();
}
}
}
private void sendTraffic() {
boolean didAnyConnectionSucceed = false;
for (int i = 0; i < 10; i++) {
// Do some network operations
HttpURLConnection connection = null;
try {
URL url = new URL("http://www.google.com/");
connection = (HttpURLConnection) url.openConnection();
connection.setInstanceFollowRedirects(false);
connection.setConnectTimeout(TEST_WAIT_DURATION_MS);
connection.setReadTimeout(TEST_WAIT_DURATION_MS);
connection.setUseCaches(false);
InputStream stream = connection.getInputStream();
byte[] bytes = new byte[100];
int receivedBytes = stream.read(bytes);
if (receivedBytes > 0) {
didAnyConnectionSucceed = true;
}
} catch (Exception e) {
// ignore
} finally {
if (connection != null) connection.disconnect();
}
}
assertTrue("All connections failed!", didAnyConnectionSucceed);
}
/**
* Tests {@link WifiManager#registerTrafficStateCallback(Executor,
* WifiManager.TrafficStateCallback)} by sending some traffic.
*/
public void testTrafficStateCallback() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
TestTrafficStateCallback callback = new TestTrafficStateCallback();
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
uiAutomation.adoptShellPermissionIdentity();
// Trigger a scan & wait for connection to one of the saved networks.
mWifiManager.startScan();
waitForConnection();
// Turn screen on for wifi traffic polling.
turnScreenOn();
mWifiManager.registerTrafficStateCallback(
Executors.newSingleThreadExecutor(), callback);
// Send some traffic to trigger the traffic state change callbacks.
sendTraffic();
// now wait for callback
boolean success = callback.latch.await(TEST_WAIT_DURATION_MS, TimeUnit.MILLISECONDS);
// check if we got the state changed callback with both data in and out
assertTrue(success);
} finally {
turnScreenOff();
mWifiManager.unregisterTrafficStateCallback(callback);
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests {@link WifiManager#setScanAlwaysAvailable(boolean)} &
* {@link WifiManager#isScanAlwaysAvailable()}.
*/
public void testScanAlwaysAvailable() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
Boolean currState = null;
try {
uiAutomation.adoptShellPermissionIdentity();
currState = mWifiManager.isScanAlwaysAvailable();
boolean newState = !currState;
mWifiManager.setScanAlwaysAvailable(newState);
PollingCheck.check(
"Wifi settings toggle failed!",
DURATION_SETTINGS_TOGGLE,
() -> mWifiManager.isScanAlwaysAvailable() == newState);
assertEquals(newState, mWifiManager.isScanAlwaysAvailable());
} finally {
if (currState != null) mWifiManager.setScanAlwaysAvailable(currState);
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests {@link WifiManager#setScanThrottleEnabled(boolean)} &
* {@link WifiManager#isScanThrottleEnabled()}.
*/
public void testScanThrottleEnabled() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
Boolean currState = null;
try {
uiAutomation.adoptShellPermissionIdentity();
currState = mWifiManager.isScanThrottleEnabled();
boolean newState = !currState;
mWifiManager.setScanThrottleEnabled(newState);
PollingCheck.check(
"Wifi settings toggle failed!",
DURATION_SETTINGS_TOGGLE,
() -> mWifiManager.isScanThrottleEnabled() == newState);
assertEquals(newState, mWifiManager.isScanThrottleEnabled());
} finally {
if (currState != null) mWifiManager.setScanThrottleEnabled(currState);
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests {@link WifiManager#setAutoWakeupEnabled(boolean)} &
* {@link WifiManager#isAutoWakeupEnabled()}.
*/
public void testAutoWakeUpEnabled() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
Boolean currState = null;
try {
uiAutomation.adoptShellPermissionIdentity();
currState = mWifiManager.isAutoWakeupEnabled();
boolean newState = !currState;
mWifiManager.setAutoWakeupEnabled(newState);
PollingCheck.check(
"Wifi settings toggle failed!",
DURATION_SETTINGS_TOGGLE,
() -> mWifiManager.isAutoWakeupEnabled() == newState);
assertEquals(newState, mWifiManager.isAutoWakeupEnabled());
} finally {
if (currState != null) mWifiManager.setAutoWakeupEnabled(currState);
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests {@link WifiManager#setVerboseLoggingEnabled(boolean)} &
* {@link WifiManager#isVerboseLoggingEnabled()}.
*/
public void testVerboseLoggingEnabled() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
Boolean currState = null;
TestWifiVerboseLoggingStatusChangedListener listener =
WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext) ?
new TestWifiVerboseLoggingStatusChangedListener() : null;
try {
uiAutomation.adoptShellPermissionIdentity();
if (listener != null) {
mWifiManager.addWifiVerboseLoggingStatusChangedListener(mExecutor, listener);
}
currState = mWifiManager.isVerboseLoggingEnabled();
boolean newState = !currState;
if (listener != null) {
assertEquals(0, listener.numCalls);
}
mWifiManager.setVerboseLoggingEnabled(newState);
PollingCheck.check(
"Wifi verbose logging toggle failed!",
DURATION_SETTINGS_TOGGLE,
() -> mWifiManager.isVerboseLoggingEnabled() == newState);
if (listener != null) {
PollingCheck.check(
"Verbose logging listener timeout",
DURATION_SETTINGS_TOGGLE,
() -> listener.status == newState && listener.numCalls == 1);
}
} finally {
if (currState != null) mWifiManager.setVerboseLoggingEnabled(currState);
if (listener != null) {
mWifiManager.removeWifiVerboseLoggingStatusChangedListener(listener);
}
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests {@link WifiManager#setVerboseLoggingLevel(int)}.
*/
public void testSetVerboseLogging() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
Boolean currState = null;
try {
uiAutomation.adoptShellPermissionIdentity();
currState = mWifiManager.isVerboseLoggingEnabled();
mWifiManager.setVerboseLoggingLevel(WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED);
assertTrue(mWifiManager.isVerboseLoggingEnabled());
assertEquals(WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED,
mWifiManager.getVerboseLoggingLevel());
mWifiManager.setVerboseLoggingLevel(WifiManager.VERBOSE_LOGGING_LEVEL_DISABLED);
assertFalse(mWifiManager.isVerboseLoggingEnabled());
assertEquals(WifiManager.VERBOSE_LOGGING_LEVEL_DISABLED,
mWifiManager.getVerboseLoggingLevel());
} finally {
if (currState != null) mWifiManager.setVerboseLoggingEnabled(currState);
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Test {@link WifiManager#setVerboseLoggingLevel(int)} for show key mode.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
public void testSetVerboseLoggingShowKeyModeNonUserBuild() throws Exception {
if (Build.TYPE.equals("user")) return;
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
Boolean currState = null;
try {
uiAutomation.adoptShellPermissionIdentity();
currState = mWifiManager.isVerboseLoggingEnabled();
mWifiManager.setVerboseLoggingLevel(WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED_SHOW_KEY);
assertTrue(mWifiManager.isVerboseLoggingEnabled());
assertEquals(WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED_SHOW_KEY,
mWifiManager.getVerboseLoggingLevel());
} finally {
if (currState != null) mWifiManager.setVerboseLoggingEnabled(currState);
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Test {@link WifiManager#setVerboseLoggingLevel(int)} for show key mode.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
public void testSetVerboseLoggingShowKeyModeUserBuild() throws Exception {
if (!Build.TYPE.equals("user")) return;
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
Boolean currState = null;
try {
uiAutomation.adoptShellPermissionIdentity();
currState = mWifiManager.isVerboseLoggingEnabled();
mWifiManager.setVerboseLoggingLevel(WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED_SHOW_KEY);
assertTrue(mWifiManager.isVerboseLoggingEnabled());
assertEquals(WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED_SHOW_KEY,
mWifiManager.getVerboseLoggingLevel());
fail("Verbosing logging show key mode should not be allowed for user build.");
} catch (SecurityException e) {
// expected
} finally {
if (currState != null) mWifiManager.setVerboseLoggingEnabled(currState);
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests {@link WifiManager#factoryReset()} cannot be invoked from a non-privileged app.
*
* Note: This intentionally does not test the full reset functionality because it causes
* the existing saved networks on the device to be lost after the test. If you add the
* networks back after reset, the ownership of saved networks will change.
*/
public void testFactoryReset() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
List<WifiConfiguration> beforeSavedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
mWifiManager::getConfiguredNetworks);
try {
mWifiManager.factoryReset();
fail("Factory reset should not be allowed for non-privileged apps");
} catch (SecurityException e) {
// expected
}
List<WifiConfiguration> afterSavedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
mWifiManager::getConfiguredNetworks);
assertEquals(beforeSavedNetworks.size(), afterSavedNetworks.size());
}
/**
* Test {@link WifiNetworkConnectionStatistics} does not crash.
* TODO(b/150891569): deprecate it in Android S, this API is not used anywhere.
*/
public void testWifiNetworkConnectionStatistics() {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
new WifiNetworkConnectionStatistics();
WifiNetworkConnectionStatistics stats = new WifiNetworkConnectionStatistics(0, 0);
new WifiNetworkConnectionStatistics(stats);
}
/**
* Verify that startRestrictingAutoJoinToSubscriptionId disconnects wifi and disables
* auto-connect to non-carrier-merged networks. Then verify that
* stopRestrictingAutoJoinToSubscriptionId makes the disabled networks clear to connect
* again.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
public void testStartAndStopRestrictingAutoJoinToSubscriptionId() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
startScan();
waitForConnection();
int fakeSubscriptionId = 5;
ShellIdentityUtils.invokeWithShellPermissions(() ->
mWifiManager.startRestrictingAutoJoinToSubscriptionId(fakeSubscriptionId));
startScan();
ensureNotConnected();
ShellIdentityUtils.invokeWithShellPermissions(() ->
mWifiManager.stopRestrictingAutoJoinToSubscriptionId());
startScan();
waitForConnection();
}
private class TestActiveCountryCodeChangedCallback implements
WifiManager.ActiveCountryCodeChangedCallback {
private String mCurrentCountryCode;
private boolean mIsOnActiveCountryCodeChangedCalled = false;
private boolean mIsOnCountryCodeInactiveCalled = false;
public boolean isOnActiveCountryCodeChangedCalled() {
return mIsOnActiveCountryCodeChangedCalled;
}
public boolean isOnCountryCodeInactiveCalled() {
return mIsOnCountryCodeInactiveCalled;
}
public void resetCallbackCallededHistory() {
mIsOnActiveCountryCodeChangedCalled = false;
mIsOnCountryCodeInactiveCalled = false;
}
public String getCurrentDriverCountryCode() {
return mCurrentCountryCode;
}
@Override
public void onActiveCountryCodeChanged(String country) {
Log.d(TAG, "Receive DriverCountryCodeChanged to " + country);
mCurrentCountryCode = country;
mIsOnActiveCountryCodeChangedCalled = true;
}
@Override
public void onCountryCodeInactive() {
Log.d(TAG, "Receive onCountryCodeInactive");
mCurrentCountryCode = null;
mIsOnCountryCodeInactiveCalled = true;
}
}
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
public void testActiveCountryCodeChangedCallback() throws Exception {
TestActiveCountryCodeChangedCallback testCountryCodeChangedCallback =
new TestActiveCountryCodeChangedCallback();
TestExecutor executor = new TestExecutor();
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
uiAutomation.adoptShellPermissionIdentity();
turnOffWifiAndTetheredHotspotIfEnabled();
// Run with scanning disable to make sure there is no active mode.
runWithScanning(() -> {
mWifiManager.registerActiveCountryCodeChangedCallback(
executor, testCountryCodeChangedCallback);
PollingCheck.check(
"DriverCountryCode is non-null when wifi off",
5000,
() -> {
executor.runAll();
return testCountryCodeChangedCallback
.isOnCountryCodeInactiveCalled()
&& testCountryCodeChangedCallback.getCurrentDriverCountryCode()
== null;
});
// Enable wifi to make sure country code has been updated.
setWifiEnabled(true);
PollingCheck.check(
"DriverCountryCode is null when wifi on",
5000,
() -> {
executor.runAll();
return testCountryCodeChangedCallback
.isOnActiveCountryCodeChangedCalled()
&& testCountryCodeChangedCallback.getCurrentDriverCountryCode()
!= null;
});
// Disable wifi to trigger country code change
setWifiEnabled(false);
PollingCheck.check(
"DriverCountryCode should be null when wifi off",
5000,
() -> {
executor.runAll();
return testCountryCodeChangedCallback.isOnCountryCodeInactiveCalled()
&& testCountryCodeChangedCallback
.getCurrentDriverCountryCode() == null;
});
mWifiManager.unregisterActiveCountryCodeChangedCallback(
testCountryCodeChangedCallback);
testCountryCodeChangedCallback.resetCallbackCallededHistory();
setWifiEnabled(true);
// Check there is no callback has been called.
PollingCheck.check(
"Callback is called after unregister",
5000,
() -> {
executor.runAll();
return !testCountryCodeChangedCallback.isOnCountryCodeInactiveCalled()
&& !testCountryCodeChangedCallback
.isOnActiveCountryCodeChangedCalled();
});
}, false /* Run with disabled */);
} finally {
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Test that the wifi country code is either null, or a length-2 string.
*/
public void testGetCountryCode() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
String wifiCountryCode = ShellIdentityUtils.invokeWithShellPermissions(
mWifiManager::getCountryCode);
if (wifiCountryCode == null) {
return;
}
assertEquals(2, wifiCountryCode.length());
// assert that the country code is all uppercase
assertEquals(wifiCountryCode.toUpperCase(Locale.US), wifiCountryCode);
// skip if Telephony is unsupported
if (!WifiFeature.isTelephonySupported(getContext())) {
return;
}
String telephonyCountryCode = getContext().getSystemService(TelephonyManager.class)
.getNetworkCountryIso();
// skip if Telephony country code is unavailable
if (telephonyCountryCode == null || telephonyCountryCode.isEmpty()) {
return;
}
assertEquals(telephonyCountryCode, wifiCountryCode.toLowerCase(Locale.US));
}
/**
* Test that {@link WifiManager#getCurrentNetwork()} returns a Network obeject consistent
* with {@link ConnectivityManager#registerNetworkCallback} when connected to a Wifi network,
* and returns null when not connected.
*/
public void testGetCurrentNetwork() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// ensure Wifi is connected
ShellIdentityUtils.invokeWithShellPermissions(() -> mWifiManager.reconnect());
PollingCheck.check(
"Wifi not connected - Please ensure there is a saved network in range of this "
+ "device",
WIFI_CONNECT_TIMEOUT_MILLIS,
() -> mWifiManager.getConnectionInfo().getNetworkId() != -1);
String networkKey = mWifiManager.getConnectionInfo().getNetworkKey();
assertNotNull(networkKey);
Network wifiCurrentNetwork = ShellIdentityUtils.invokeWithShellPermissions(
mWifiManager::getCurrentNetwork);
assertNotNull(wifiCurrentNetwork);
List<WifiConfiguration> configuredNetwork = ShellIdentityUtils.invokeWithShellPermissions(
mWifiManager::getConfiguredNetworks);
boolean isNetworkKeyExist = false;
for (WifiConfiguration config : configuredNetwork) {
if (config.getAllNetworkKeys().contains(networkKey)) {
isNetworkKeyExist = true;
break;
}
}
assertTrue(isNetworkKeyExist);
TestNetworkCallback networkCallbackListener = new TestNetworkCallback(mLock);
synchronized (mLock) {
try {
// File a request for wifi network.
mConnectivityManager.registerNetworkCallback(
new NetworkRequest.Builder()
.addTransportType(TRANSPORT_WIFI)
.build(),
networkCallbackListener);
// now wait for callback
mLock.wait(TEST_WAIT_DURATION_MS);
} catch (InterruptedException e) {
}
}
assertTrue(networkCallbackListener.onAvailableCalled);
Network connectivityCurrentNetwork = networkCallbackListener.network;
assertEquals(connectivityCurrentNetwork, wifiCurrentNetwork);
setWifiEnabled(false);
PollingCheck.check(
"Wifi not disconnected!",
20000,
() -> mWifiManager.getConnectionInfo().getNetworkId() == -1);
assertNull(ShellIdentityUtils.invokeWithShellPermissions(mWifiManager::getCurrentNetwork));
}
/**
* Tests {@link WifiManager#isWpa3SaeSupported()} does not crash.
*/
public void testIsWpa3SaeSupported() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
mWifiManager.isWpa3SaeSupported();
}
/**
* Tests {@link WifiManager#isWpa3SuiteBSupported()} does not crash.
*/
public void testIsWpa3SuiteBSupported() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
mWifiManager.isWpa3SuiteBSupported();
}
/**
* Tests {@link WifiManager#isEnhancedOpenSupported()} does not crash.
*/
public void testIsEnhancedOpenSupported() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
mWifiManager.isEnhancedOpenSupported();
}
/**
* Test that {@link WifiManager#is5GHzBandSupported()} returns successfully in
* both WiFi enabled/disabled states.
* Note that the response depends on device support and hence both true/false
* are valid responses.
*/
public void testIs5GhzBandSupported() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// Check for 5GHz support with wifi enabled
setWifiEnabled(true);
PollingCheck.check(
"Wifi not enabled!",
20000,
() -> mWifiManager.isWifiEnabled());
boolean isSupportedEnabled = mWifiManager.is5GHzBandSupported();
// Check for 5GHz support with wifi disabled
setWifiEnabled(false);
PollingCheck.check(
"Wifi not disabled!",
20000,
() -> !mWifiManager.isWifiEnabled());
boolean isSupportedDisabled = mWifiManager.is5GHzBandSupported();
// If Support is true when WiFi is disable, then it has to be true when it is enabled.
// Note, the reverse is a valid case.
if (isSupportedDisabled) {
assertTrue(isSupportedEnabled);
}
}
/**
* Test that {@link WifiManager#is6GHzBandSupported()} returns successfully in
* both Wifi enabled/disabled states.
* Note that the response depends on device support and hence both true/false
* are valid responses.
*/
public void testIs6GhzBandSupported() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// Check for 6GHz support with wifi enabled
setWifiEnabled(true);
PollingCheck.check(
"Wifi not enabled!",
20000,
() -> mWifiManager.isWifiEnabled());
boolean isSupportedEnabled = mWifiManager.is6GHzBandSupported();
// Check for 6GHz support with wifi disabled
setWifiEnabled(false);
PollingCheck.check(
"Wifi not disabled!",
20000,
() -> !mWifiManager.isWifiEnabled());
boolean isSupportedDisabled = mWifiManager.is6GHzBandSupported();
// If Support is true when WiFi is disable, then it has to be true when it is enabled.
// Note, the reverse is a valid case.
if (isSupportedDisabled) {
assertTrue(isSupportedEnabled);
}
}
/**
* Test that {@link WifiManager#is60GHzBandSupported()} returns successfully in
* both Wifi enabled/disabled states.
* Note that the response depends on device support and hence both true/false
* are valid responses.
*/
public void testIs60GhzBandSupported() throws Exception {
if (!(WifiFeature.isWifiSupported(getContext())
&& ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S))) {
// skip the test if WiFi is not supported
return;
}
// Check for 60GHz support with wifi enabled
setWifiEnabled(true);
PollingCheck.check(
"Wifi not enabled!",
20000,
() -> mWifiManager.isWifiEnabled());
boolean isSupportedEnabled = mWifiManager.is60GHzBandSupported();
// Check for 60GHz support with wifi disabled
setWifiEnabled(false);
PollingCheck.check(
"Wifi not disabled!",
20000,
() -> !mWifiManager.isWifiEnabled());
boolean isSupportedDisabled = mWifiManager.is60GHzBandSupported();
// If Support is true when WiFi is disable, then it has to be true when it is enabled.
// Note, the reverse is a valid case.
if (isSupportedDisabled) {
assertTrue(isSupportedEnabled);
}
}
/**
* Test that {@link WifiManager#isWifiStandardSupported()} returns successfully in
* both Wifi enabled/disabled states. The test is to be performed on
* {@link WifiAnnotations}'s {@code WIFI_STANDARD_}
* Note that the response depends on device support and hence both true/false
* are valid responses.
*/
public void testIsWifiStandardsSupported() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// Check for WiFi standards support with wifi enabled
setWifiEnabled(true);
PollingCheck.check(
"Wifi not enabled!",
20000,
() -> mWifiManager.isWifiEnabled());
boolean isLegacySupportedEnabled =
mWifiManager.isWifiStandardSupported(ScanResult.WIFI_STANDARD_LEGACY);
boolean is11nSupporedEnabled =
mWifiManager.isWifiStandardSupported(ScanResult.WIFI_STANDARD_11N);
boolean is11acSupportedEnabled =
mWifiManager.isWifiStandardSupported(ScanResult.WIFI_STANDARD_11AC);
boolean is11axSupportedEnabled =
mWifiManager.isWifiStandardSupported(ScanResult.WIFI_STANDARD_11AX);
boolean is11beSupportedEnabled =
mWifiManager.isWifiStandardSupported(ScanResult.WIFI_STANDARD_11BE);
// Check for WiFi standards support with wifi disabled
setWifiEnabled(false);
PollingCheck.check(
"Wifi not disabled!",
20000,
() -> !mWifiManager.isWifiEnabled());
boolean isLegacySupportedDisabled =
mWifiManager.isWifiStandardSupported(ScanResult.WIFI_STANDARD_LEGACY);
boolean is11nSupportedDisabled =
mWifiManager.isWifiStandardSupported(ScanResult.WIFI_STANDARD_11N);
boolean is11acSupportedDisabled =
mWifiManager.isWifiStandardSupported(ScanResult.WIFI_STANDARD_11AC);
boolean is11axSupportedDisabled =
mWifiManager.isWifiStandardSupported(ScanResult.WIFI_STANDARD_11AX);
boolean is11beSupportedDisabled =
mWifiManager.isWifiStandardSupported(ScanResult.WIFI_STANDARD_11BE);
if (isLegacySupportedDisabled) {
assertTrue(isLegacySupportedEnabled);
}
if (is11nSupportedDisabled) {
assertTrue(is11nSupporedEnabled);
}
if (is11acSupportedDisabled) {
assertTrue(is11acSupportedEnabled);
}
if (is11axSupportedDisabled) {
assertTrue(is11axSupportedEnabled);
}
if (is11beSupportedDisabled) {
assertTrue(is11beSupportedEnabled);
}
}
private static PasspointConfiguration createPasspointConfiguration() {
PasspointConfiguration config = new PasspointConfiguration();
HomeSp homeSp = new HomeSp();
homeSp.setFqdn("test.com");
homeSp.setFriendlyName("friendly name");
homeSp.setRoamingConsortiumOis(new long[]{0x55, 0x66});
config.setHomeSp(homeSp);
Credential.SimCredential simCred = new Credential.SimCredential();
simCred.setImsi("123456*");
simCred.setEapType(23 /* EAP_AKA */);
Credential cred = new Credential();
cred.setRealm("realm");
cred.setSimCredential(simCred);
config.setCredential(cred);
return config;
}
/**
* Tests {@link WifiManager#addOrUpdatePasspointConfiguration(PasspointConfiguration)}
* adds a Passpoint configuration correctly by getting it once it is added, and comparing it
* to the local copy of the configuration.
*/
public void testAddOrUpdatePasspointConfiguration() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// Create and install a Passpoint configuration
PasspointConfiguration passpointConfiguration = createPasspointConfiguration();
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
uiAutomation.adoptShellPermissionIdentity();
mWifiManager.addOrUpdatePasspointConfiguration(passpointConfiguration);
// Compare configurations
List<PasspointConfiguration> configurations = mWifiManager.getPasspointConfigurations();
assertNotNull("The installed passpoint profile is missing", configurations);
assertEquals(passpointConfiguration, getTargetPasspointConfiguration(configurations,
passpointConfiguration.getUniqueId()));
} finally {
// Clean up
mWifiManager.removePasspointConfiguration(passpointConfiguration.getHomeSp().getFqdn());
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests {@link WifiManager#setPasspointMeteredOverride(String, int)}
* adds a Passpoint configuration correctly, check the default metered setting. Use API change
* metered override, verify Passpoint configuration changes with it.
*/
public void testSetPasspointMeteredOverride() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// Create and install a Passpoint configuration
PasspointConfiguration passpointConfiguration = createPasspointConfiguration();
String fqdn = passpointConfiguration.getHomeSp().getFqdn();
String uniqueId = passpointConfiguration.getUniqueId();
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
uiAutomation.adoptShellPermissionIdentity();
mWifiManager.addOrUpdatePasspointConfiguration(passpointConfiguration);
PasspointConfiguration saved = getTargetPasspointConfiguration(
mWifiManager.getPasspointConfigurations(), uniqueId);
assertNotNull("The installed passpoint profile is missing", saved);
// Verify meter override setting.
assertEquals("Metered overrider default should be none",
WifiConfiguration.METERED_OVERRIDE_NONE, saved.getMeteredOverride());
// Change the meter override setting.
mWifiManager.setPasspointMeteredOverride(fqdn,
WifiConfiguration.METERED_OVERRIDE_METERED);
// Verify passpoint config change with the new setting.
saved = getTargetPasspointConfiguration(
mWifiManager.getPasspointConfigurations(), uniqueId);
assertNotNull("The installed passpoint profile is missing", saved);
assertEquals("Metered override should be metered",
WifiConfiguration.METERED_OVERRIDE_METERED, saved.getMeteredOverride());
} finally {
// Clean up
mWifiManager.removePasspointConfiguration(fqdn);
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests that
* {@link WifiManager#startSubscriptionProvisioning(OsuProvider, Executor, ProvisioningCallback)}
* starts a subscription provisioning, and confirm a status callback invoked once.
*/
public void testStartSubscriptionProvisioning() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// Using Java reflection to construct an OsuProvider instance because its constructor is
// hidden and not available to apps.
Class<?> osuProviderClass = Class.forName("android.net.wifi.hotspot2.OsuProvider");
Constructor<?> osuProviderClassConstructor = osuProviderClass.getConstructor(String.class,
Map.class, String.class, Uri.class, String.class, List.class);
OsuProvider osuProvider = (OsuProvider) osuProviderClassConstructor.newInstance(TEST_SSID,
TEST_FRIENDLY_NAMES, TEST_SERVICE_DESCRIPTION, TEST_SERVER_URI, TEST_NAI,
TEST_METHOD_LIST);
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
uiAutomation.adoptShellPermissionIdentity();
synchronized (mLock) {
// Start a subscription provisioning for a non-existent Passpoint R2 AP
mWifiManager.startSubscriptionProvisioning(osuProvider, mExecutor,
mProvisioningCallback);
mLock.wait(TEST_WAIT_DURATION_MS);
}
} finally {
uiAutomation.dropShellPermissionIdentity();
}
// Expect only a single callback event, connecting. Since AP doesn't exist, it ends here
assertEquals(ProvisioningCallback.OSU_STATUS_AP_CONNECTING, mProvisioningStatus);
// No failure callbacks expected
assertEquals(0, mProvisioningFailureStatus);
// No completion callback expected
assertFalse(mProvisioningComplete);
}
/**
* Tests {@link WifiManager#setTdlsEnabled(InetAddress, boolean)} does not crash.
*/
public void testSetTdlsEnabled() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// Trigger a scan & wait for connection to one of the saved networks.
mWifiManager.startScan();
waitForConnection();
InetAddress inetAddress = InetAddress.getByName(TEST_IP_ADDRESS);
mWifiManager.setTdlsEnabled(inetAddress, true);
Thread.sleep(50);
mWifiManager.setTdlsEnabled(inetAddress, false);
}
/**
* Tests {@link WifiManager#setTdlsEnabledWithMacAddress(String, boolean)} does not crash.
*/
public void testSetTdlsEnabledWithMacAddress() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// Trigger a scan & wait for connection to one of the saved networks.
mWifiManager.startScan();
waitForConnection();
mWifiManager.setTdlsEnabledWithMacAddress(TEST_MAC_ADDRESS, true);
Thread.sleep(50);
mWifiManager.setTdlsEnabledWithMacAddress(TEST_MAC_ADDRESS, false);
}
/**
* Verify WifiNetworkSuggestion.Builder.setMacRandomizationSetting(WifiNetworkSuggestion
* .RANDOMIZATION_NON_PERSISTENT) creates a
* WifiConfiguration with macRandomizationSetting == RANDOMIZATION_NON_PERSISTENT.
* Then verify by default, a WifiConfiguration created by suggestions should have
* macRandomizationSetting == RANDOMIZATION_PERSISTENT.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
public void testSuggestionBuilderNonPersistentRandomization() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
.setSsid(TEST_SSID).setWpa2Passphrase(TEST_PASSPHRASE)
.setMacRandomizationSetting(WifiNetworkSuggestion.RANDOMIZATION_NON_PERSISTENT)
.build();
assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
mWifiManager.addNetworkSuggestions(Arrays.asList(suggestion)));
verifySuggestionFoundWithMacRandomizationSetting(TEST_SSID,
WifiNetworkSuggestion.RANDOMIZATION_NON_PERSISTENT);
suggestion = new WifiNetworkSuggestion.Builder()
.setSsid(TEST_SSID).setWpa2Passphrase(TEST_PASSPHRASE)
.build();
assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
mWifiManager.addNetworkSuggestions(Arrays.asList(suggestion)));
verifySuggestionFoundWithMacRandomizationSetting(TEST_SSID,
WifiNetworkSuggestion.RANDOMIZATION_PERSISTENT);
}
private void verifySuggestionFoundWithMacRandomizationSetting(String ssid,
int macRandomizationSetting) {
List<WifiNetworkSuggestion> retrievedSuggestions = mWifiManager.getNetworkSuggestions();
for (WifiNetworkSuggestion entry : retrievedSuggestions) {
if (entry.getSsid().equals(ssid)) {
assertEquals(macRandomizationSetting, entry.getMacRandomizationSetting());
return; // pass test after the MAC randomization setting is verified.
}
}
fail("WifiNetworkSuggestion not found for SSID=" + ssid + ", macRandomizationSetting="
+ macRandomizationSetting);
}
/**
* Tests {@link WifiManager#getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(List)}
*/
public void testGetAllWifiConfigForMatchedNetworkSuggestion() {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
ScanResult scanResult = new ScanResult();
scanResult.SSID = TEST_SSID;
scanResult.capabilities = TEST_PSK_CAP;
scanResult.BSSID = TEST_BSSID;
List<ScanResult> testList = Arrays.asList(scanResult);
WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
.setSsid(TEST_SSID).setWpa2Passphrase(TEST_PASSPHRASE).build();
assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
mWifiManager.addNetworkSuggestions(Arrays.asList(suggestion)));
List<WifiConfiguration> matchedResult;
try {
uiAutomation.adoptShellPermissionIdentity();
matchedResult = mWifiManager
.getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(testList);
} finally {
uiAutomation.dropShellPermissionIdentity();
}
// As suggestion is not approved, will return empty list.
assertTrue(matchedResult.isEmpty());
}
/**
* Tests {@link WifiManager#getMatchingScanResults(List, List)}
*/
public void testGetMatchingScanResults() {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// Create pair of ScanResult and WifiNetworkSuggestion
ScanResult scanResult = new ScanResult();
scanResult.SSID = TEST_SSID;
scanResult.capabilities = TEST_PSK_CAP;
scanResult.BSSID = TEST_BSSID;
WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
.setSsid(TEST_SSID).setWpa2Passphrase(TEST_PASSPHRASE).build();
Map<WifiNetworkSuggestion, List<ScanResult>> matchedResults = mWifiManager
.getMatchingScanResults(Arrays.asList(suggestion), Arrays.asList(scanResult));
// Verify result is matched pair of ScanResult and WifiNetworkSuggestion
assertEquals(scanResult.SSID, matchedResults.get(suggestion).get(0).SSID);
// Change ScanResult to unmatched should return empty result.
scanResult.SSID = TEST_SSID_UNQUOTED;
matchedResults = mWifiManager
.getMatchingScanResults(Arrays.asList(suggestion), Arrays.asList(scanResult));
assertTrue(matchedResults.get(suggestion).isEmpty());
}
/**
* Tests {@link WifiManager#disableEphemeralNetwork(String)}.
*/
public void testDisableEphemeralNetwork() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// Trigger a scan & wait for connection to one of the saved networks.
mWifiManager.startScan();
waitForConnection();
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
List<WifiConfiguration> savedNetworks = null;
try {
uiAutomation.adoptShellPermissionIdentity();
// Temporarily disable on all networks.
savedNetworks = mWifiManager.getConfiguredNetworks();
for (WifiConfiguration network : savedNetworks) {
mWifiManager.disableEphemeralNetwork(network.SSID);
}
// trigger a disconnect and wait for disconnect.
mWifiManager.disconnect();
waitForDisconnection();
// Now trigger scan and ensure that the device does not connect to any networks.
mWifiManager.startScan();
ensureNotConnected();
} finally {
uiAutomation.dropShellPermissionIdentity();
setWifiEnabled(false);
}
}
/**
* Tests {@link WifiManager#allowAutojoin(int, boolean)}.
*/
public void testAllowAutojoin() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// Trigger a scan & wait for connection to one of the saved networks.
mWifiManager.startScan();
waitForConnection();
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
List<WifiConfiguration> savedNetworks = null;
try {
uiAutomation.adoptShellPermissionIdentity();
// disable autojoin on all networks.
savedNetworks = mWifiManager.getConfiguredNetworks();
for (WifiConfiguration network : savedNetworks) {
mWifiManager.allowAutojoin(network.networkId, false);
}
// trigger a disconnect and wait for disconnect.
mWifiManager.disconnect();
waitForDisconnection();
// Now trigger scan and ensure that the device does not connect to any networks.
mWifiManager.startScan();
ensureNotConnected();
// Now enable autojoin on all networks.
for (WifiConfiguration network : savedNetworks) {
mWifiManager.allowAutojoin(network.networkId, true);
}
// Trigger a scan & wait for connection to one of the saved networks.
mWifiManager.startScan();
waitForConnection();
} finally {
// Restore auto join state.
if (savedNetworks != null) {
for (WifiConfiguration network : savedNetworks) {
mWifiManager.allowAutojoin(network.networkId, network.allowAutojoin);
}
}
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests {@link WifiManager#allowAutojoinPasspoint(String, boolean)}.
*/
public void testAllowAutojoinPasspoint() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
PasspointConfiguration passpointConfiguration = createPasspointConfiguration();
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
uiAutomation.adoptShellPermissionIdentity();
mWifiManager.addOrUpdatePasspointConfiguration(passpointConfiguration);
// Turn off auto-join
mWifiManager.allowAutojoinPasspoint(
passpointConfiguration.getHomeSp().getFqdn(), false);
// Turn on auto-join
mWifiManager.allowAutojoinPasspoint(
passpointConfiguration.getHomeSp().getFqdn(), true);
} finally {
mWifiManager.removePasspointConfiguration(passpointConfiguration.getHomeSp().getFqdn());
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests {@link WifiManager#allowAutojoinGlobal(boolean)}.
*/
public void testAllowAutojoinGlobal() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// Trigger a scan & wait for connection to one of the saved networks.
mWifiManager.startScan();
waitForConnection();
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
uiAutomation.adoptShellPermissionIdentity();
// disable autojoin on all networks.
mWifiManager.allowAutojoinGlobal(false);
// trigger a disconnect and wait for disconnect.
mWifiManager.disconnect();
waitForDisconnection();
// Now trigger scan and ensure that the device does not connect to any networks.
mWifiManager.startScan();
ensureNotConnected();
// verify null is returned when attempting to get current configured network.
WifiConfiguration config = mWifiManager.getPrivilegedConnectedNetwork();
assertNull("config should be null because wifi is not connected", config);
// Now enable autojoin on all networks.
mWifiManager.allowAutojoinGlobal(true);
// Trigger a scan & wait for connection to one of the saved networks.
mWifiManager.startScan();
waitForConnection();
} finally {
// Re-enable auto join if the test fails for some reason.
mWifiManager.allowAutojoinGlobal(true);
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Verify the invalid and valid usages of {@code WifiManager#queryAutojoinGlobal}.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
public void testQueryAutojoinGlobal() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
AtomicBoolean enabled = new AtomicBoolean(false);
Consumer<Boolean> listener = new Consumer<Boolean>() {
@Override
public void accept(Boolean value) {
synchronized (mLock) {
enabled.set(value);
mLock.notify();
}
}
};
// Test invalid inputs trigger IllegalArgumentException
assertThrows("null executor should trigger exception", NullPointerException.class,
() -> mWifiManager.queryAutojoinGlobal(null, listener));
assertThrows("null listener should trigger exception", NullPointerException.class,
() -> mWifiManager.queryAutojoinGlobal(mExecutor, null));
// Test caller with no permission triggers SecurityException.
assertThrows("No permission should trigger SecurityException", SecurityException.class,
() -> mWifiManager.queryAutojoinGlobal(mExecutor, listener));
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
uiAutomation.adoptShellPermissionIdentity();
// Test get/set autojoin global enabled
mWifiManager.allowAutojoinGlobal(true);
mWifiManager.queryAutojoinGlobal(mExecutor, listener);
synchronized (mLock) {
mLock.wait(TEST_WAIT_DURATION_MS);
}
assertTrue(enabled.get());
// Test get/set autojoin global disabled
mWifiManager.allowAutojoinGlobal(false);
mWifiManager.queryAutojoinGlobal(mExecutor, listener);
synchronized (mLock) {
mLock.wait(TEST_WAIT_DURATION_MS);
}
assertFalse(enabled.get());
} finally {
// Re-enable auto join if the test fails for some reason.
mWifiManager.allowAutojoinGlobal(true);
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests {@link WifiManager#isWapiSupported()} does not crash.
*/
public void testIsWapiSupported() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
mWifiManager.isWapiSupported();
}
/**
* Tests {@link WifiManager#isWpa3SaePublicKeySupported()} does not crash.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
public void testIsWpa3SaePublicKeySupported() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
mWifiManager.isWpa3SaePublicKeySupported();
}
/**
* Tests {@link WifiManager#isWpa3SaeH2eSupported()} does not crash.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
public void testIsWpa3SaeH2eSupported() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
mWifiManager.isWpa3SaeH2eSupported();
}
/**
* Tests {@link WifiManager#isWifiDisplayR2Supported()} does not crash.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
public void testIsWifiDisplayR2Supported() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
mWifiManager.isWifiDisplayR2Supported();
}
/**
* Tests {@link WifiManager#isP2pSupported()} returns true
* if this device supports it, otherwise, ensure no crash.
*/
public void testIsP2pSupported() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
if (WifiFeature.isP2pSupported(getContext())) {
// if this device supports P2P, ensure hw capability is correct.
assertTrue(mWifiManager.isP2pSupported());
} else {
// ensure no crash.
mWifiManager.isP2pSupported();
}
}
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
public void testIsMultiStaConcurrencySupported() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// ensure no crash.
mWifiManager.isStaApConcurrencySupported();
}
private PasspointConfiguration getTargetPasspointConfiguration(
List<PasspointConfiguration> configurationList, String uniqueId) {
if (configurationList == null || configurationList.isEmpty()) {
return null;
}
for (PasspointConfiguration config : configurationList) {
if (TextUtils.equals(config.getUniqueId(), uniqueId)) {
return config;
}
}
return null;
}
/**
* Test that {@link WifiManager#is60GHzBandSupported()} throws UnsupportedOperationException
* if the release is older than S.
*/
@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.R)
public void testIs60GhzBandSupportedOnROrOlder() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// check for 60ghz support with wifi enabled
try {
boolean isSupported = mWifiManager.is60GHzBandSupported();
fail("Expected UnsupportedOperationException");
} catch (UnsupportedOperationException ex) {
}
}
/**
* Test that {@link WifiManager#is60GHzBandSupported()} returns successfully in
* both Wifi enabled/disabled states for release newer than R.
* Note that the response depends on device support and hence both true/false
* are valid responses.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
public void testIs60GhzBandSupportedOnSOrNewer() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// check for 60ghz support with wifi enabled
boolean isSupportedWhenWifiEnabled = mWifiManager.is60GHzBandSupported();
// Check for 60GHz support with wifi disabled
setWifiEnabled(false);
PollingCheck.check(
"Wifi not disabled!",
20000,
() -> !mWifiManager.isWifiEnabled());
boolean isSupportedWhenWifiDisabled = mWifiManager.is60GHzBandSupported();
// If Support is true when WiFi is disable, then it has to be true when it is enabled.
// Note, the reverse is a valid case.
if (isSupportedWhenWifiDisabled) {
assertTrue(isSupportedWhenWifiEnabled);
}
}
/**
* Tests {@link WifiManager#isTrustOnFirstUseSupported()} does not crash.
*/
// TODO(b/196180536): Wait for T SDK finalization before changing
// to `@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)`
@SdkSuppress(minSdkVersion = 31)
public void testIsTrustOnFirstUseSupported() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
mWifiManager.isTrustOnFirstUseSupported();
}
public class TestCoexCallback extends WifiManager.CoexCallback {
private Object mCoexLock;
private int mOnCoexUnsafeChannelChangedCount;
private List<CoexUnsafeChannel> mCoexUnsafeChannels;
private int mCoexRestrictions;
TestCoexCallback(Object lock) {
mCoexLock = lock;
}
@Override
public void onCoexUnsafeChannelsChanged(
@NonNull List<CoexUnsafeChannel> unsafeChannels, int restrictions) {
synchronized (mCoexLock) {
mCoexUnsafeChannels = unsafeChannels;
mCoexRestrictions = restrictions;
mOnCoexUnsafeChannelChangedCount++;
mCoexLock.notify();
}
}
public int getOnCoexUnsafeChannelChangedCount() {
synchronized (mCoexLock) {
return mOnCoexUnsafeChannelChangedCount;
}
}
public List<CoexUnsafeChannel> getCoexUnsafeChannels() {
return mCoexUnsafeChannels;
}
public int getCoexRestrictions() {
return mCoexRestrictions;
}
}
/**
* Test that coex-related methods fail without the needed privileged permissions
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
public void testCoexMethodsShouldFailNoPermission() {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
try {
mWifiManager.setCoexUnsafeChannels(Collections.emptyList(), 0);
fail("setCoexUnsafeChannels should not succeed - privileged call");
} catch (SecurityException e) {
// expected
}
final TestCoexCallback callback = new TestCoexCallback(mLock);
try {
mWifiManager.registerCoexCallback(mExecutor, callback);
fail("registerCoexCallback should not succeed - privileged call");
} catch (SecurityException e) {
// expected
}
try {
mWifiManager.unregisterCoexCallback(callback);
fail("unregisterCoexCallback should not succeed - privileged call");
} catch (SecurityException e) {
// expected
}
}
/**
* Test that coex-related methods succeed in setting the current unsafe channels and notifying
* the listener. Since the default coex algorithm may be enabled, no-op is also valid behavior.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
public void testListenOnCoexUnsafeChannels() {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// These below API's only work with privileged permissions (obtained via shell identity
// for test)
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
List<CoexUnsafeChannel> prevUnsafeChannels = null;
int prevRestrictions = -1;
try {
uiAutomation.adoptShellPermissionIdentity();
final TestCoexCallback callback = new TestCoexCallback(mLock);
final List<CoexUnsafeChannel> testUnsafeChannels = new ArrayList<>();
testUnsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6));
final int testRestrictions = COEX_RESTRICTION_WIFI_DIRECT
| COEX_RESTRICTION_SOFTAP | COEX_RESTRICTION_WIFI_AWARE;
synchronized (mLock) {
try {
mWifiManager.registerCoexCallback(mExecutor, callback);
// Callback should be called after registering
mLock.wait(TEST_WAIT_DURATION_MS);
assertEquals(1, callback.getOnCoexUnsafeChannelChangedCount());
// Store the previous coex channels and set new coex channels
prevUnsafeChannels = callback.getCoexUnsafeChannels();
prevRestrictions = callback.getCoexRestrictions();
mWifiManager.setCoexUnsafeChannels(testUnsafeChannels, testRestrictions);
mLock.wait(TEST_WAIT_DURATION_MS);
// Unregister callback and try setting again
mWifiManager.unregisterCoexCallback(callback);
mWifiManager.setCoexUnsafeChannels(testUnsafeChannels, testRestrictions);
// Callback should not be called here since it was unregistered.
mLock.wait(TEST_WAIT_DURATION_MS);
} catch (InterruptedException e) {
fail("Thread interrupted unexpectedly while waiting on mLock");
}
}
if (callback.getOnCoexUnsafeChannelChangedCount() == 2) {
// Default algorithm disabled, setter should set the getter values.
assertEquals(testUnsafeChannels, callback.getCoexUnsafeChannels());
assertEquals(testRestrictions, callback.getCoexRestrictions());
} else if (callback.getOnCoexUnsafeChannelChangedCount() != 1) {
fail("Coex callback called " + callback.mOnCoexUnsafeChannelChangedCount
+ " times. Expected 0 or 1 calls." );
}
} finally {
// Reset the previous unsafe channels if we overrode them.
if (prevRestrictions != -1) {
mWifiManager.setCoexUnsafeChannels(prevUnsafeChannels, prevRestrictions);
}
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Verify that secure WPA-Enterprise network configurations can be added and updated.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
public void testSecureEnterpriseConfigurationsAccepted() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
WifiConfiguration wifiConfiguration = new WifiConfiguration();
wifiConfiguration.SSID = SSID1;
wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
wifiConfiguration.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TTLS);
int networkId = INVALID_NETWORK_ID;
// These below API's only work with privileged permissions (obtained via shell identity
// for test)
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
uiAutomation.adoptShellPermissionIdentity();
// Now configure it correctly with a Root CA cert and domain name
wifiConfiguration.enterpriseConfig.setCaCertificate(FakeKeys.CA_CERT0);
wifiConfiguration.enterpriseConfig.setAltSubjectMatch(TEST_DOM_SUBJECT_MATCH);
// Verify that the network is added
networkId = mWifiManager.addNetwork(wifiConfiguration);
assertNotEquals(INVALID_NETWORK_ID, networkId);
// Verify that the update API accepts configurations configured securely
wifiConfiguration.networkId = networkId;
assertEquals(networkId, mWifiManager.updateNetwork(wifiConfiguration));
} finally {
if (networkId != INVALID_NETWORK_ID) {
// Clean up the previously added network
mWifiManager.removeNetwork(networkId);
}
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests {@link WifiManager#isPasspointTermsAndConditionsSupported)} does not crash.
*/
public void testIsPasspointTermsAndConditionsSupported() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
if (!WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(getContext())) {
// Skip the test if wifi module version is older than S.
return;
}
mWifiManager.isPasspointTermsAndConditionsSupported();
}
/**
* Test that call to {@link WifiManager#setOverrideCountryCode()},
* {@link WifiManager#clearOverrideCountryCode()} and
* {@link WifiManager#setDefaultCountryCode()} need privileged permission
* and the permission is not even given to shell user.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
public void testManageCountryCodeMethodsFailWithoutPermissions() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
ShellIdentityUtils.invokeWithShellPermissions(() -> {
try {
mWifiManager.setOverrideCountryCode(TEST_COUNTRY_CODE);
fail("setOverrideCountryCode() expected to fail - privileged call");
} catch (SecurityException e) {
// expected
}
try {
mWifiManager.clearOverrideCountryCode();
fail("clearOverrideCountryCode() expected to fail - privileged call");
} catch (SecurityException e) {
// expected
}
try {
mWifiManager.setDefaultCountryCode(TEST_COUNTRY_CODE);
fail("setDefaultCountryCode() expected to fail - privileged call");
} catch (SecurityException e) {
// expected
}
});
}
/**
* Tests {@link WifiManager#flushPasspointAnqpCache)} does not crash.
*/
public void testFlushPasspointAnqpCache() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
if (!WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(getContext())) {
// Skip the test if wifi module version is older than S.
return;
}
// The below API only works with privileged permissions (obtained via shell identity
// for test)
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
uiAutomation.adoptShellPermissionIdentity();
mWifiManager.flushPasspointAnqpCache();
} finally {
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests {@link WifiManager#setWifiPasspointEnabled)} raise security exception without
* permission.
*/
// TODO(b/139192273): Wait for T SDK finalization before changing
// to `@SdkSuppress(minSdkVersion = Build.VERSION_CODES.T)`
@SdkSuppress(minSdkVersion = 31)
public void testEnablePasspointWithoutPermission() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
try {
mWifiManager.setWifiPasspointEnabled(true);
fail("setWifiPasspointEnabled() expected to fail - privileged call");
} catch (SecurityException e) {
// expected
}
}
/**
* Tests {@link WifiManager#setWifiPasspointEnabled)} does not crash and returns success.
*/
// TODO(b/139192273): Wait for T SDK finalization before changing
// to `@SdkSuppress(minSdkVersion = Build.VERSION_CODES.T)`
@SdkSuppress(minSdkVersion = 31)
public void testEnablePasspoint() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// The below API only works with privileged permissions (obtained via shell identity
// for test)
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
uiAutomation.adoptShellPermissionIdentity();
// Check if passpoint is enabled by default.
assertTrue(mWifiManager.isWifiPasspointEnabled());
// Try to disable passpoint
mWifiManager.setWifiPasspointEnabled(false);
PollingCheck.check(
"Wifi passpoint turn off failed!", 2_000,
() -> mWifiManager.isWifiPasspointEnabled() == false);
// Try to enable passpoint
mWifiManager.setWifiPasspointEnabled(true);
PollingCheck.check(
"Wifi passpoint turn on failed!", 2_000,
() -> mWifiManager.isWifiPasspointEnabled() == true);
} finally {
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests {@link WifiManager#isDecoratedIdentitySupported)} does not crash.
*/
public void testIsDecoratedIdentitySupported() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
if (!WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(getContext())) {
// Skip the test if wifi module version is older than S.
return;
}
mWifiManager.isDecoratedIdentitySupported();
}
/**
* Tests {@link WifiManager#setCarrierNetworkOffloadEnabled)} and
* {@link WifiManager#isCarrierNetworkOffloadEnabled} work as expected.
*/
public void testSetCarrierNetworkOffloadEnabled() {
if (!WifiFeature.isWifiSupported(getContext())
|| !WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(getContext())) {
// skip the test if WiFi is not supported
return;
}
assertTrue(mWifiManager.isCarrierNetworkOffloadEnabled(TEST_SUB_ID, false));
// The below API only works with privileged permissions (obtained via shell identity
// for test)
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
uiAutomation.adoptShellPermissionIdentity();
mWifiManager.setCarrierNetworkOffloadEnabled(TEST_SUB_ID, false, false);
assertFalse(mWifiManager.isCarrierNetworkOffloadEnabled(TEST_SUB_ID, false));
} finally {
mWifiManager.setCarrierNetworkOffloadEnabled(TEST_SUB_ID, false, true);
uiAutomation.dropShellPermissionIdentity();
}
assertTrue(mWifiManager.isCarrierNetworkOffloadEnabled(TEST_SUB_ID, false));
}
/**
* Test that {@link WifiManager#getUsableChannels(int, int)},
* {@link WifiManager#getAllowedChannels(int, int)}
* throws UnsupportedOperationException if the release is older than S.
*/
@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.R)
public void testGetAllowedUsableChannelsOnROrOlder() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
try {
mWifiManager.getAllowedChannels(WIFI_BAND_24_GHZ, OP_MODE_STA);
fail("getAllowedChannels Expected to fail - UnsupportedOperationException");
} catch (UnsupportedOperationException ex) {}
try {
mWifiManager.getUsableChannels(WIFI_BAND_24_GHZ, OP_MODE_STA);
fail("getUsableChannels Expected to fail - UnsupportedOperationException");
} catch (UnsupportedOperationException ex) {}
}
/**
* Tests {@link WifiManager#getAllowedChannels(int, int))} does not crash
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
public void testGetAllowedChannels() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// The below API only works with privileged permissions (obtained via shell identity
// for test)
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
WifiAvailableChannel channel = new WifiAvailableChannel(2412, OP_MODE_SAP);
assertEquals(channel.getFrequencyMhz(), 2412);
assertEquals(channel.getOperationalModes(), OP_MODE_SAP);
final List<Integer> valid24GhzFreqs = Arrays.asList(
2412, 2417, 2422, 2427, 2432, 2437, 2442,
2447, 2452, 2457, 2462, 2467, 2472, 2484);
Set<Integer> supported24GhzFreqs = new HashSet<Integer>();
uiAutomation.adoptShellPermissionIdentity();
List<WifiAvailableChannel> allowedChannels =
mWifiManager.getAllowedChannels(WIFI_BAND_24_GHZ, OP_MODE_STA);
assertNotNull(allowedChannels);
for (WifiAvailableChannel ch : allowedChannels) {
//Must contain a valid 2.4GHz frequency
assertTrue(valid24GhzFreqs.contains(ch.getFrequencyMhz()));
if(ch.getFrequencyMhz() <= 2462) {
//Channels 1-11 are supported for STA in all countries
assertEquals(ch.getOperationalModes() & OP_MODE_STA, OP_MODE_STA);
supported24GhzFreqs.add(ch.getFrequencyMhz());
}
}
//Channels 1-11 are supported for STA in all countries
assertEquals(supported24GhzFreqs.size(), 11);
} catch (UnsupportedOperationException ex) {
//expected if the device does not support this API
} catch (Exception ex) {
fail("getAllowedChannels unexpected Exception " + ex);
} finally {
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests {@link WifiManager#getUsableChannels(int, int))} does not crash.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
public void testGetUsableChannels() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// The below API only works with privileged permissions (obtained via shell identity
// for test)
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
uiAutomation.adoptShellPermissionIdentity();
List<WifiAvailableChannel> usableChannels =
mWifiManager.getUsableChannels(WIFI_BAND_24_GHZ, OP_MODE_STA);
//There must be at least one usable channel in 2.4GHz band
assertFalse(usableChannels.isEmpty());
} catch (UnsupportedOperationException ex) {
//expected if the device does not support this API
} catch (Exception ex) {
fail("getUsableChannels unexpected Exception " + ex);
} finally {
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Validate that the Passpoint feature is enabled on the device.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
public void testPasspointCapability() {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
PackageManager packageManager = mContext.getPackageManager();
assertTrue("Passpoint must be supported",
packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_PASSPOINT));
}
/**
* Validate add and remove SuggestionUserApprovalStatusListener. And verify the listener's
* stickiness.
*/
public void testAddRemoveSuggestionUserApprovalStatusListener() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())
|| !WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(getContext())) {
return;
}
CountDownLatch countDownLatch = new CountDownLatch(1);
TestUserApprovalStatusListener listener = new TestUserApprovalStatusListener(
countDownLatch);
try {
mWifiManager.addSuggestionUserApprovalStatusListener(mExecutor, listener);
assertTrue(countDownLatch.await(TEST_WAIT_DURATION_MS, TimeUnit.MILLISECONDS));
} finally {
mWifiManager.removeSuggestionUserApprovalStatusListener(listener);
}
}
private static class TestUserApprovalStatusListener implements
WifiManager.SuggestionUserApprovalStatusListener {
private final CountDownLatch mBlocker;
public TestUserApprovalStatusListener(CountDownLatch countDownLatch) {
mBlocker = countDownLatch;
}
@Override
public void onUserApprovalStatusChange(int status) {
mBlocker.countDown();
}
}
/**
* Tests {@link WifiManager#setStaConcurrencyForMultiInternetMode)} raise security exception
* without permission.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
public void testIsStaConcurrencyForMultiInternetSupported() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// ensure no crash.
mWifiManager.isStaConcurrencyForMultiInternetSupported();
}
/**
* Tests {@link WifiManager#setStaConcurrencyForMultiInternetMode)} raise security exception
* without permission.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
public void testSetStaConcurrencyForMultiInternetModeWithoutPermission() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())
|| !mWifiManager.isStaConcurrencyForMultiInternetSupported()) {
// skip the test if WiFi is not supported or multi internet feature not supported.
return;
}
try {
mWifiManager.setStaConcurrencyForMultiInternetMode(
WifiManager.WIFI_MULTI_INTERNET_MODE_DISABLED);
fail("setWifiPasspointEnabled() expected to fail - privileged call");
} catch (SecurityException e) {
// expected
}
}
/**
* Tests {@link WifiManager#setStaConcurrencyForMultiInternetMode)} does not crash.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
public void testSetStaConcurrencyForMultiInternetMode() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())
|| !mWifiManager.isStaConcurrencyForMultiInternetSupported()) {
// skip the test if WiFi is not supported or multi internet feature not supported.
return;
}
// The below API only works with privileged permissions (obtained via shell identity
// for test)
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
try {
uiAutomation.adoptShellPermissionIdentity();
// Try to disable multi internet
mWifiManager.setStaConcurrencyForMultiInternetMode(
WifiManager.WIFI_MULTI_INTERNET_MODE_DISABLED);
PollingCheck.check(
"Wifi multi internet disable failed!", 2_000,
() -> mWifiManager.getStaConcurrencyForMultiInternetMode()
== WifiManager.WIFI_MULTI_INTERNET_MODE_DISABLED);
// Try to enable multi internet
mWifiManager.setStaConcurrencyForMultiInternetMode(
WifiManager.WIFI_MULTI_INTERNET_MODE_MULTI_AP);
PollingCheck.check(
"Wifi multi internet turn on failed!", 2_000,
() -> mWifiManager.getStaConcurrencyForMultiInternetMode()
== WifiManager.WIFI_MULTI_INTERNET_MODE_MULTI_AP);
} finally {
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests {@link WifiConfiguration#setBssidAllowlist(List)}.
*/
public void testBssidAllowlist() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
// Trigger a scan & wait for connection to one of the saved networks.
mWifiManager.startScan();
waitForConnection();
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
List<WifiConfiguration> savedNetworks = null;
try {
uiAutomation.adoptShellPermissionIdentity();
WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
String connectedBssid = wifiInfo.getBSSID();
int networkId = wifiInfo.getNetworkId();
// Set empty BSSID allow list to block all APs
savedNetworks = mWifiManager.getConfiguredNetworks();
for (WifiConfiguration network : savedNetworks) {
network.setBssidAllowlist(Collections.emptyList());
mWifiManager.updateNetwork(network);
}
// trigger a disconnect and wait for disconnect.
mWifiManager.disconnect();
waitForDisconnection();
// Now trigger scan and ensure that the device does not connect to any networks.
mWifiManager.startScan();
ensureNotConnected();
// Set the previous connected BSSID on that network. Other network set with a fake
// (not visible) BSSID only
for (WifiConfiguration network : savedNetworks) {
if (network.networkId == networkId) {
network.setBssidAllowlist(List.of(MacAddress.fromString(connectedBssid)));
mWifiManager.updateNetwork(network);
} else {
network.setBssidAllowlist(List.of(MacAddress.fromString(TEST_BSSID)));
mWifiManager.updateNetwork(network);
}
}
// Trigger a scan & wait for connection to one of the saved networks.
mWifiManager.startScan();
waitForConnection();
wifiInfo = mWifiManager.getConnectionInfo();
assertEquals(networkId, wifiInfo.getNetworkId());
assertEquals(connectedBssid, wifiInfo.getBSSID());
} finally {
// Reset BSSID allow list to accept all APs
for (WifiConfiguration network : savedNetworks) {
assertNotNull(network.getBssidAllowlist());
network.setBssidAllowlist(null);
mWifiManager.updateNetwork(network);
}
uiAutomation.dropShellPermissionIdentity();
}
}
/**
* Tests {@link WifiManager#notifyMinimumRequiredWifiSecurityLevelChanged(int)}
* raise security exception without permission.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
public void testNotifyMinimumRequiredWifiSecurityLevelChangedWithoutPermission()
throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported.
return;
}
assertThrows(SecurityException.class,
() -> mWifiManager.notifyMinimumRequiredWifiSecurityLevelChanged(
DevicePolicyManager.WIFI_SECURITY_PERSONAL));
}
/**
* Tests {@link WifiManager#notifyMinimumRequiredWifiSecurityLevelChanged(int)}
* raise security exception without permission.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
public void testNotifyWifiSsidPolicyChangedWithoutPermission() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported.
return;
}
WifiSsidPolicy policy = new WifiSsidPolicy(
WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST, new ArraySet<>(Arrays.asList(
WifiSsid.fromBytes("ssid".getBytes(StandardCharsets.UTF_8)))));
try {
mWifiManager.notifyWifiSsidPolicyChanged(policy);
fail("Expected security exception due to lack of permission");
} catch (SecurityException e) {
// expected
}
}
/**
* Verifies that
* {@link WifiManager#reportCreateInterfaceImpact(int, boolean, Executor, BiConsumer)} raises
* a security exception without permission.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
public void testIsItPossibleToCreateInterfaceNotAllowed() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
assertThrows(SecurityException.class, () -> mWifiManager.reportCreateInterfaceImpact(
WifiManager.WIFI_INTERFACE_TYPE_AP, false, mExecutor,
(canBeCreatedLocal, interfacesWhichWillBeDeletedLocal) -> {
// should not get here (security exception!)
}));
}
/**
* Verifies
* {@link WifiManager#reportCreateInterfaceImpact(int, boolean, Executor, BiConsumer)} .
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
public void testIsItPossibleToCreateInterface() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
AtomicBoolean called = new AtomicBoolean(false);
AtomicBoolean canBeCreated = new AtomicBoolean(false);
AtomicReference<Set<WifiManager.InterfaceCreationImpact>>
interfacesWhichWillBeDeleted = new AtomicReference<>(null);
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.reportCreateInterfaceImpact(
WifiManager.WIFI_INTERFACE_TYPE_AP, false, mExecutor,
(canBeCreatedLocal, interfacesWhichWillBeDeletedLocal) -> {
synchronized (mLock) {
canBeCreated.set(canBeCreatedLocal);
called.set(true);
interfacesWhichWillBeDeleted.set(interfacesWhichWillBeDeletedLocal);
mLock.notify();
}
}));
synchronized (mLock) {
mLock.wait(TEST_WAIT_DURATION_MS);
}
assertTrue(called.get());
if (canBeCreated.get()) {
for (WifiManager.InterfaceCreationImpact entry : interfacesWhichWillBeDeleted.get()) {
int interfaceType = entry.getInterfaceType();
assertTrue(interfaceType == WifiManager.WIFI_INTERFACE_TYPE_STA
|| interfaceType == WifiManager.WIFI_INTERFACE_TYPE_AP
|| interfaceType == WifiManager.WIFI_INTERFACE_TYPE_DIRECT
|| interfaceType == WifiManager.WIFI_INTERFACE_TYPE_AWARE);
Set<String> packages = entry.getPackages();
for (String p : packages) {
assertNotNull(p);
}
}
}
// verify the WifiManager.InterfaceCreationImpact APIs
int interfaceType = WifiManager.WIFI_INTERFACE_TYPE_STA;
Set<String> packages = Set.of("package1", "packages2");
WifiManager.InterfaceCreationImpact element = new WifiManager.InterfaceCreationImpact(
interfaceType, packages);
assertEquals(interfaceType, element.getInterfaceType());
assertEquals(packages, element.getPackages());
}
/**
* Tests {@link WifiManager#isEasyConnectDppAkmSupported)} does not crash.
*/
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
public void testIsEasyConnectDppAkmSupported() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
mWifiManager.isEasyConnectDppAkmSupported();
}
}