Merge "CTS: Add test for WifiManager#getCountryCode()" into rvc-dev
diff --git a/tests/cts/net/src/android/net/wifi/cts/WifiManagerTest.java b/tests/cts/net/src/android/net/wifi/cts/WifiManagerTest.java
index 4a8edee..c62d3cb 100644
--- a/tests/cts/net/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/cts/net/src/android/net/wifi/cts/WifiManagerTest.java
@@ -87,6 +87,7 @@
private NetworkInfo mNetworkInfo;
private Object mLock = new Object();
private UiDevice mUiDevice;
+ private boolean mWasVerboseLoggingEnabled;
// Please refer to WifiManager
private static final int MIN_RSSI = -100;
@@ -187,6 +188,13 @@
mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
mConnectivityManager = getContext().getSystemService(ConnectivityManager.class);
assertNotNull(mWifiManager);
+
+ // turn on verbose logging for tests
+ mWasVerboseLoggingEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.isVerboseLoggingEnabled());
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.setVerboseLoggingEnabled(true));
+
mWifiLock = mWifiManager.createWifiLock(TAG);
mWifiLock.acquire();
if (!mWifiManager.isWifiEnabled())
@@ -215,6 +223,8 @@
setWifiEnabled(true);
mWifiLock.release();
mContext.unregisterReceiver(mReceiver);
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.setVerboseLoggingEnabled(mWasVerboseLoggingEnabled));
Thread.sleep(DURATION);
super.tearDown();
}
diff --git a/tests/cts/net/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java b/tests/cts/net/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
new file mode 100644
index 0000000..96cf45f
--- /dev/null
+++ b/tests/cts/net/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
@@ -0,0 +1,509 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.cts;
+
+import static android.net.NetworkCapabilitiesProto.TRANSPORT_WIFI;
+import static android.os.Process.myUid;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.UiAutomation;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.MacAddress;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.NetworkRequestMatchCallback;
+import android.net.wifi.WifiNetworkSpecifier;
+import android.os.PatternMatcher;
+import android.os.WorkSource;
+import android.platform.test.annotations.AppModeFull;
+import android.support.test.uiautomator.UiDevice;
+import android.test.AndroidTestCase;
+import android.text.TextUtils;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.util.List;
+import java.util.concurrent.Executors;
+
+/**
+ * Tests the entire connection flow using {@link WifiNetworkSpecifier} embedded in a
+ * {@link NetworkRequest} & passed into {@link ConnectivityManager#requestNetwork(NetworkRequest,
+ * ConnectivityManager.NetworkCallback)}.
+ *
+ * Assumes that all the saved networks is either open/WPA1/WPA2/WPA3 authenticated network.
+ * TODO(b/150716005): Use assumeTrue for wifi support check.
+ */
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+public class WifiNetworkSpecifierTest extends AndroidTestCase {
+ private static final String TAG = "WifiNetworkSpecifierTest";
+
+ private WifiManager mWifiManager;
+ private ConnectivityManager mConnectivityManager;
+ private UiDevice mUiDevice;
+ private final Object mLock = new Object();
+ private final Object mUiLock = new Object();
+ private WifiConfiguration mTestNetwork;
+ private boolean mWasVerboseLoggingEnabled;
+
+ private static final int DURATION = 10_000;
+ private static final int DURATION_UI_INTERACTION = 15_000;
+ private static final int DURATION_NETWORK_CONNECTION = 30_000;
+ private static final int DURATION_SCREEN_TOGGLE = 2000;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
+ mConnectivityManager = getContext().getSystemService(ConnectivityManager.class);
+ assertNotNull(mWifiManager);
+
+ // turn on verbose logging for tests
+ mWasVerboseLoggingEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.isVerboseLoggingEnabled());
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.setVerboseLoggingEnabled(true));
+
+ if (!mWifiManager.isWifiEnabled()) setWifiEnabled(true);
+ mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ turnScreenOn();
+ PollingCheck.check("Wifi not enabled", DURATION, () -> mWifiManager.isWifiEnabled());
+
+ List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.getPrivilegedConfiguredNetworks());
+ assertFalse("Need at least one saved network", savedNetworks.isEmpty());
+ // Pick any one of the saved networks on the device (assumes that it is in range)
+ mTestNetwork = savedNetworks.get(0);
+ // Disconnect & disable auto-join on the saved network to prevent auto-connect from
+ // interfering with the test.
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.disableNetwork(mTestNetwork.networkId));
+ // wait for Wifi to be disconnected
+ PollingCheck.check(
+ "Wifi not disconnected",
+ 20000,
+ () -> mWifiManager.getConnectionInfo().getNetworkId() == -1);
+ }
+
+ @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);
+ turnScreenOff();
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.enableNetwork(mTestNetwork.networkId, false));
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.setVerboseLoggingEnabled(mWasVerboseLoggingEnabled));
+ super.tearDown();
+ }
+
+ private void setWifiEnabled(boolean enable) throws Exception {
+ // now trigger the change using shell commands.
+ SystemUtil.runShellCommand("svc wifi " + (enable ? "enable" : "disable"));
+ }
+
+ private void turnScreenOn() throws Exception {
+ mUiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+ mUiDevice.executeShellCommand("wm dismiss-keyguard");
+ // Since the screen on/off intent is ordered, they will not be sent right now.
+ Thread.sleep(DURATION_SCREEN_TOGGLE);
+ }
+
+ private void turnScreenOff() throws Exception {
+ mUiDevice.executeShellCommand("input keyevent KEYCODE_SLEEP");
+ // Since the screen on/off intent is ordered, they will not be sent right now.
+ Thread.sleep(DURATION_SCREEN_TOGGLE);
+ }
+
+ private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
+ private final Object mLock;
+ public boolean onAvailableCalled = false;
+ public boolean onUnavailableCalled = false;
+ public NetworkCapabilities networkCapabilities;
+
+ TestNetworkCallback(Object lock) {
+ mLock = lock;
+ }
+
+ @Override
+ public void onAvailable(Network network, NetworkCapabilities networkCapabilities,
+ LinkProperties linkProperties, boolean blocked) {
+ synchronized (mLock) {
+ onAvailableCalled = true;
+ this.networkCapabilities = networkCapabilities;
+ mLock.notify();
+ }
+ }
+
+ @Override
+ public void onUnavailable() {
+ synchronized (mLock) {
+ onUnavailableCalled = true;
+ mLock.notify();
+ }
+ }
+ }
+
+ private static class TestNetworkRequestMatchCallback implements NetworkRequestMatchCallback {
+ private final Object mLock;
+
+ public boolean onRegistrationCalled = false;
+ public boolean onAbortCalled = false;
+ public boolean onMatchCalled = false;
+ public boolean onConnectSuccessCalled = false;
+ public boolean onConnectFailureCalled = false;
+ public WifiManager.NetworkRequestUserSelectionCallback userSelectionCallback = null;
+ public List<ScanResult> matchedScanResults = null;
+
+ TestNetworkRequestMatchCallback(Object lock) {
+ mLock = lock;
+ }
+
+ @Override
+ public void onUserSelectionCallbackRegistration(
+ WifiManager.NetworkRequestUserSelectionCallback userSelectionCallback) {
+ synchronized (mLock) {
+ onRegistrationCalled = true;
+ this.userSelectionCallback = userSelectionCallback;
+ mLock.notify();
+ }
+ }
+
+ @Override
+ public void onAbort() {
+ synchronized (mLock) {
+ onAbortCalled = true;
+ mLock.notify();
+ }
+ }
+
+ @Override
+ public void onMatch(List<ScanResult> scanResults) {
+ synchronized (mLock) {
+ // This can be invoked multiple times. So, ignore after the first one to avoid
+ // disturbing the rest of the test sequence.
+ if (onMatchCalled) return;
+ onMatchCalled = true;
+ matchedScanResults = scanResults;
+ mLock.notify();
+ }
+ }
+
+ @Override
+ public void onUserSelectionConnectSuccess(WifiConfiguration config) {
+ synchronized (mLock) {
+ onConnectSuccessCalled = true;
+ mLock.notify();
+ }
+ }
+
+ @Override
+ public void onUserSelectionConnectFailure(WifiConfiguration config) {
+ synchronized (mLock) {
+ onConnectFailureCalled = true;
+ mLock.notify();
+ }
+ }
+ }
+
+ private void handleUiInteractions(boolean shouldUserReject) {
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ TestNetworkRequestMatchCallback networkRequestMatchCallback =
+ new TestNetworkRequestMatchCallback(mUiLock);
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+
+ // 1. Wait for registration callback.
+ synchronized (mUiLock) {
+ try {
+ mWifiManager.registerNetworkRequestMatchCallback(
+ Executors.newSingleThreadExecutor(), networkRequestMatchCallback);
+ // now wait for the registration callback first.
+ mUiLock.wait(DURATION_UI_INTERACTION);
+ } catch (InterruptedException e) {
+ }
+ }
+ assertTrue(networkRequestMatchCallback.onRegistrationCalled);
+ assertNotNull(networkRequestMatchCallback.userSelectionCallback);
+
+ // 2. Wait for matching scan results
+ synchronized (mUiLock) {
+ try {
+ // now wait for the registration callback first.
+ mUiLock.wait(DURATION_UI_INTERACTION);
+ } catch (InterruptedException e) {
+ }
+ }
+ assertTrue(networkRequestMatchCallback.onMatchCalled);
+ assertNotNull(networkRequestMatchCallback.matchedScanResults);
+ assertThat(networkRequestMatchCallback.matchedScanResults.size()).isAtLeast(1);
+
+ // 3. Trigger connection to one of the matched networks or reject the request.
+ if (shouldUserReject) {
+ networkRequestMatchCallback.userSelectionCallback.reject();
+ } else {
+ networkRequestMatchCallback.userSelectionCallback.select(mTestNetwork);
+ }
+
+ // 4. Wait for connection success or abort.
+ synchronized (mUiLock) {
+ try {
+ // now wait for the registration callback first.
+ mUiLock.wait(DURATION_UI_INTERACTION);
+ } catch (InterruptedException e) {
+ }
+ }
+ if (shouldUserReject) {
+ assertTrue(networkRequestMatchCallback.onAbortCalled);
+ } else {
+ assertTrue(networkRequestMatchCallback.onConnectSuccessCalled);
+ }
+ } finally {
+ mWifiManager.unregisterNetworkRequestMatchCallback(networkRequestMatchCallback);
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ /**
+ * Tests the entire connection flow using the provided specifier.
+ *
+ * @param specifier Specifier to use for network request.
+ * @param shouldUserReject Whether to simulate user rejection or not.
+ */
+ private void testConnectionFlowWithSpecifier(
+ WifiNetworkSpecifier specifier, boolean shouldUserReject) {
+ // Fork a thread to handle the UI interactions.
+ Thread uiThread = new Thread(() -> handleUiInteractions(shouldUserReject));
+
+ // File the network request & wait for the callback.
+ TestNetworkCallback networkCallbackListener = new TestNetworkCallback(mLock);
+ synchronized (mLock) {
+ try {
+ // File a request for wifi network.
+ mConnectivityManager.requestNetwork(
+ new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .setNetworkSpecifier(specifier)
+ .build(),
+ networkCallbackListener);
+ // Wait for the request to reach the wifi stack before kick-starting the UI
+ // interactions.
+ Thread.sleep(100);
+ // Start the UI interactions.
+ uiThread.run();
+ // now wait for callback
+ mLock.wait(DURATION_NETWORK_CONNECTION);
+ } catch (InterruptedException e) {
+ }
+ }
+ if (shouldUserReject) {
+ assertTrue(networkCallbackListener.onUnavailableCalled);
+ } else {
+ assertTrue(networkCallbackListener.onAvailableCalled);
+ }
+
+ try {
+ // Ensure that the UI interaction thread has completed.
+ uiThread.join(DURATION_UI_INTERACTION);
+ } catch (InterruptedException e) {
+ fail("UI interaction interrupted");
+ }
+
+ // Release the request after the test.
+ mConnectivityManager.unregisterNetworkCallback(networkCallbackListener);
+ }
+
+ private void testSuccessfulConnectionWithSpecifier(WifiNetworkSpecifier specifier) {
+ testConnectionFlowWithSpecifier(specifier, false);
+ }
+
+ private void testUserRejectionWithSpecifier(WifiNetworkSpecifier specifier) {
+ testConnectionFlowWithSpecifier(specifier, true);
+ }
+
+ private WifiNetworkSpecifier.Builder createSpecifierBuilderWithCredentialFromSavedNetwork() {
+ WifiNetworkSpecifier.Builder specifierBuilder = new WifiNetworkSpecifier.Builder();
+ if (mTestNetwork.preSharedKey != null) {
+ if (mTestNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+ specifierBuilder.setWpa2Passphrase(mTestNetwork.preSharedKey);
+ } else if (mTestNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
+ specifierBuilder.setWpa3Passphrase(mTestNetwork.preSharedKey);
+ } else {
+ fail("Unsupported security type found in saved networks");
+ }
+ } else if (!mTestNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
+ specifierBuilder.setIsEnhancedOpen(false);
+ } else if (!mTestNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
+ fail("Unsupported security type found in saved networks");
+ }
+ specifierBuilder.setIsHiddenSsid(mTestNetwork.hiddenSSID);
+ return specifierBuilder;
+ }
+
+ /**
+ * Tests the entire connection flow using a specific SSID in the specifier.
+ */
+ public void testConnectionWithSpecificSsid() {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ WifiNetworkSpecifier specifier = createSpecifierBuilderWithCredentialFromSavedNetwork()
+ .setSsid(WifiInfo.sanitizeSsid(mTestNetwork.SSID))
+ .build();
+ testSuccessfulConnectionWithSpecifier(specifier);
+ }
+
+ /**
+ * Tests the entire connection flow using a SSID pattern in the specifier.
+ */
+ public void testConnectionWithSsidPattern() {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ // Creates a ssid pattern by dropping the last char in the saved network & pass that
+ // as a prefix match pattern in the request.
+ String ssidUnquoted = WifiInfo.sanitizeSsid(mTestNetwork.SSID);
+ assertThat(ssidUnquoted.length()).isAtLeast(2);
+ String ssidPrefix = ssidUnquoted.substring(0, ssidUnquoted.length() - 1);
+ // Note: The match may return more than 1 network in this case since we use a prefix match,
+ // But, we will still ensure that the UI interactions in the test still selects the
+ // saved network for connection.
+ WifiNetworkSpecifier specifier = createSpecifierBuilderWithCredentialFromSavedNetwork()
+ .setSsidPattern(new PatternMatcher(ssidPrefix, PatternMatcher.PATTERN_PREFIX))
+ .build();
+ testSuccessfulConnectionWithSpecifier(specifier);
+ }
+
+ private static class TestScanResultsCallback extends WifiManager.ScanResultsCallback {
+ private final Object mLock;
+ public boolean onAvailableCalled = false;
+
+ TestScanResultsCallback(Object lock) {
+ mLock = lock;
+ }
+
+ @Override
+ public void onScanResultsAvailable() {
+ synchronized (mLock) {
+ onAvailableCalled = true;
+ mLock.notify();
+ }
+ }
+ }
+
+ /**
+ * Loops through all available scan results and finds the first match for the saved network.
+ *
+ * Note:
+ * a) If there are more than 2 networks with the same SSID, but different credential type, then
+ * this matching may pick the wrong one.
+ */
+ private ScanResult findScanResultMatchingSavedNetwork() {
+ // Trigger a scan to get fresh scan results.
+ TestScanResultsCallback scanResultsCallback = new TestScanResultsCallback(mLock);
+ synchronized (mLock) {
+ try {
+ mWifiManager.registerScanResultsCallback(
+ Executors.newSingleThreadExecutor(), scanResultsCallback);
+ mWifiManager.startScan(new WorkSource(myUid()));
+ // now wait for callback
+ mLock.wait(DURATION_NETWORK_CONNECTION);
+ } catch (InterruptedException e) {
+ } finally {
+ mWifiManager.unregisterScanResultsCallback(scanResultsCallback);
+ }
+ }
+ List<ScanResult> scanResults = mWifiManager.getScanResults();
+ if (scanResults == null || scanResults.isEmpty()) fail("No scan results available");
+ for (ScanResult scanResult : scanResults) {
+ if (TextUtils.equals(scanResult.SSID, WifiInfo.sanitizeSsid(mTestNetwork.SSID))) {
+ return scanResult;
+ }
+ }
+ fail("No matching scan results found");
+ return null;
+ }
+
+ /**
+ * Tests the entire connection flow using a specific BSSID in the specifier.
+ */
+ public void testConnectionWithSpecificBssid() {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ ScanResult scanResult = findScanResultMatchingSavedNetwork();
+ WifiNetworkSpecifier specifier = createSpecifierBuilderWithCredentialFromSavedNetwork()
+ .setBssid(MacAddress.fromString(scanResult.BSSID))
+ .build();
+ testSuccessfulConnectionWithSpecifier(specifier);
+ }
+
+ /**
+ * Tests the entire connection flow using a BSSID pattern in the specifier.
+ */
+ public void testConnectionWithBssidPattern() {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ ScanResult scanResult = findScanResultMatchingSavedNetwork();
+ // Note: The match may return more than 1 network in this case since we use a prefix match,
+ // But, we will still ensure that the UI interactions in the test still selects the
+ // saved network for connection.
+ WifiNetworkSpecifier specifier = createSpecifierBuilderWithCredentialFromSavedNetwork()
+ .setBssidPattern(MacAddress.fromString(scanResult.BSSID),
+ MacAddress.fromString("ff:ff:ff:00:00:00"))
+ .build();
+ testSuccessfulConnectionWithSpecifier(specifier);
+ }
+
+ /**
+ * Tests the entire connection flow using a BSSID pattern in the specifier.
+ */
+ public void testUserRejectionWithSpecificSsid() {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ WifiNetworkSpecifier specifier = createSpecifierBuilderWithCredentialFromSavedNetwork()
+ .setSsid(WifiInfo.sanitizeSsid(mTestNetwork.SSID))
+ .build();
+ testUserRejectionWithSpecifier(specifier);
+ }
+}