summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--service/java/com/android/server/wifi/WifiDeviceStateChangeManager.java8
-rw-r--r--service/tests/wifitests/src/com/android/server/wifi/WifiDeviceStateChangeManagerTest.java21
-rw-r--r--tests/hostsidetests/multidevices/com.google.snippet.wifi/direct/JsonDeserializer.java4
-rw-r--r--tests/hostsidetests/multidevices/com.google.snippet.wifi/direct/WifiP2pManagerSnippet.java58
-rw-r--r--tests/hostsidetests/multidevices/test/Android.bp31
-rw-r--r--tests/hostsidetests/multidevices/test/aware/AndroidTestNew.xml43
-rw-r--r--tests/hostsidetests/multidevices/test/direct/AndroidTest.xml43
-rw-r--r--tests/hostsidetests/multidevices/test/direct/constants.py62
-rw-r--r--tests/hostsidetests/multidevices/test/direct/cts_wifi_direct_test_suite.py2
-rw-r--r--tests/hostsidetests/multidevices/test/direct/group_owner_negotiation_test.py6
-rw-r--r--tests/hostsidetests/multidevices/test/direct/group_owner_test.py66
-rw-r--r--tests/hostsidetests/multidevices/test/direct/group_owner_with_config_test.py8
-rw-r--r--tests/hostsidetests/multidevices/test/direct/p2p_utils.py214
-rw-r--r--tests/hostsidetests/multidevices/test/direct/service_discovery_test.py631
-rw-r--r--tests/hostsidetests/multidevices/test/softap/AndroidTest.xml43
15 files changed, 1085 insertions, 155 deletions
diff --git a/service/java/com/android/server/wifi/WifiDeviceStateChangeManager.java b/service/java/com/android/server/wifi/WifiDeviceStateChangeManager.java
index 6efa28fc90..0b6ffc68d1 100644
--- a/service/java/com/android/server/wifi/WifiDeviceStateChangeManager.java
+++ b/service/java/com/android/server/wifi/WifiDeviceStateChangeManager.java
@@ -29,6 +29,7 @@ import android.security.advancedprotection.AdvancedProtectionManager;
import android.text.TextUtils;
import android.util.ArraySet;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.HandlerExecutor;
import com.android.wifi.flags.FeatureFlags;
@@ -98,7 +99,8 @@ public class WifiDeviceStateChangeManager {
},
filter);
handleScreenStateChanged(mPowerManager.isInteractive());
- if (Environment.isSdkAtLeastB() && mFeatureFlags.wepDisabledInApm() && Flags.aapmApi()) {
+ if (Environment.isSdkAtLeastB() && mFeatureFlags.wepDisabledInApm()
+ && isAapmApiFlagEnabled()) {
mAdvancedProtectionManager =
mContext.getSystemService(AdvancedProtectionManager.class);
if (mAdvancedProtectionManager != null) {
@@ -118,6 +120,10 @@ public class WifiDeviceStateChangeManager {
mIsWifiServiceStarted = true;
}
+ @VisibleForTesting
+ public boolean isAapmApiFlagEnabled() {
+ return Flags.aapmApi();
+ }
/**
* Register a state change callback. When the state is changed, caller with receive the callback
* event
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiDeviceStateChangeManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiDeviceStateChangeManagerTest.java
index ec3c259f11..394e4ff334 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiDeviceStateChangeManagerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiDeviceStateChangeManagerTest.java
@@ -52,7 +52,6 @@ import org.mockito.MockitoAnnotations;
@SmallTest
public class WifiDeviceStateChangeManagerTest extends WifiBaseTest {
-
@Mock Context mContext;
@Mock WifiDeviceStateChangeManager.StateChangeCallback mStateChangeCallback;
@Mock PowerManager mPowerManager;
@@ -63,7 +62,19 @@ public class WifiDeviceStateChangeManagerTest extends WifiBaseTest {
@Captor ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
private TestLooper mLooper;
private Handler mHandler;
- private WifiDeviceStateChangeManager mWifiDeviceStateChangeManager;
+ private WifiDeviceStateChangeManagerSpy mWifiDeviceStateChangeManager;
+ private boolean mIsAapmApiFlagEnabled = false;
+
+ class WifiDeviceStateChangeManagerSpy extends WifiDeviceStateChangeManager {
+ WifiDeviceStateChangeManagerSpy() {
+ super(mContext, mHandler, mWifiInjector);
+ }
+
+ @Override
+ public boolean isAapmApiFlagEnabled() {
+ return mIsAapmApiFlagEnabled;
+ }
+ }
@Before
public void setUp() {
@@ -74,9 +85,7 @@ public class WifiDeviceStateChangeManagerTest extends WifiBaseTest {
when(mPowerManager.isInteractive()).thenReturn(true);
mLooper = new TestLooper();
mHandler = new Handler(mLooper.getLooper());
- mWifiDeviceStateChangeManager = new WifiDeviceStateChangeManager(mContext, mHandler,
- mWifiInjector);
-
+ mWifiDeviceStateChangeManager = new WifiDeviceStateChangeManagerSpy();
}
@Test
@@ -123,7 +132,7 @@ public class WifiDeviceStateChangeManagerTest extends WifiBaseTest {
@Test
public void testCallbackWhenAdvancedProtectionModeSupported() {
assumeTrue(Environment.isSdkAtLeastB());
- assumeTrue(android.security.Flags.aapmApi());
+ mIsAapmApiFlagEnabled = true;
ArgumentCaptor<AdvancedProtectionManager.Callback> apmCallbackCaptor =
ArgumentCaptor.forClass(AdvancedProtectionManager.Callback.class);
when(mFeatureFlags.wepDisabledInApm()).thenReturn(true);
diff --git a/tests/hostsidetests/multidevices/com.google.snippet.wifi/direct/JsonDeserializer.java b/tests/hostsidetests/multidevices/com.google.snippet.wifi/direct/JsonDeserializer.java
index 8d99990d82..f8fdf3b7a0 100644
--- a/tests/hostsidetests/multidevices/com.google.snippet.wifi/direct/JsonDeserializer.java
+++ b/tests/hostsidetests/multidevices/com.google.snippet.wifi/direct/JsonDeserializer.java
@@ -61,7 +61,8 @@ public class JsonDeserializer {
}
if (jsonObject.has(GROUP_CLIENT_IP_PROVISIONING_MODE)) {
builder.setGroupClientIpProvisioningMode(
- jsonObject.getInt(GROUP_CLIENT_IP_PROVISIONING_MODE));
+ jsonObject.getInt(GROUP_CLIENT_IP_PROVISIONING_MODE)
+ );
}
if (jsonObject.has(GROUP_OPERATING_BAND)) {
builder.setGroupOperatingBand(jsonObject.getInt(GROUP_OPERATING_BAND));
@@ -77,4 +78,5 @@ public class JsonDeserializer {
}
return builder.build();
}
+
}
diff --git a/tests/hostsidetests/multidevices/com.google.snippet.wifi/direct/WifiP2pManagerSnippet.java b/tests/hostsidetests/multidevices/com.google.snippet.wifi/direct/WifiP2pManagerSnippet.java
index 75f3185b6e..ab5fa1ca49 100644
--- a/tests/hostsidetests/multidevices/com.google.snippet.wifi/direct/WifiP2pManagerSnippet.java
+++ b/tests/hostsidetests/multidevices/com.google.snippet.wifi/direct/WifiP2pManagerSnippet.java
@@ -486,8 +486,8 @@ public class WifiP2pManagerSnippet implements Snippet {
String uuid,
String device,
JSONArray services,
- @RpcDefault(value = "0") Integer channelId
- ) throws Throwable {
+ @RpcDefault(value = "0"
+ ) Integer channelId) throws Throwable {
WifiP2pManager.Channel channel = getChannel(channelId);
List<String> serviceList = new ArrayList<String>();
for (int i = 0; i < services.length(); i++) {
@@ -581,17 +581,24 @@ public class WifiP2pManagerSnippet implements Snippet {
/**
* Add a service Upnp discovery request.
*
+ * @param serviceType The service type to be passed to {@link WifiP2pUpnpServiceRequest}.
* @param channelId The ID of the channel for Wi-Fi P2P to operate on.
* @return The ID of the service request, which is used when calling
* {@link #wifiP2pRemoveServiceRequest(int, Integer)}.
* @throws Throwable If add service request action timed out or got invalid channel ID.
*/
@Rpc(description = "Add a service Upnp discovery request.")
- public Integer wifiP2pAddUpnpServiceRequest(@RpcDefault(value = "0") Integer channelId)
- throws Throwable {
+ public Integer wifiP2pAddUpnpServiceRequest(
+ @RpcOptional String serviceType,
+ @RpcDefault(value = "0") Integer channelId
+ ) throws Throwable {
WifiP2pManager.Channel channel = getChannel(channelId);
-
- WifiP2pUpnpServiceRequest request = WifiP2pUpnpServiceRequest.newInstance();
+ WifiP2pUpnpServiceRequest request;
+ if (serviceType == null) {
+ request = WifiP2pUpnpServiceRequest.newInstance();
+ } else {
+ request = WifiP2pUpnpServiceRequest.newInstance(serviceType);
+ }
mServiceRequestCnt += 1;
mServiceRequests.put(mServiceRequestCnt, request);
@@ -604,17 +611,30 @@ public class WifiP2pManagerSnippet implements Snippet {
/**
* Add a service Bonjour discovery request.
*
+ * @param instanceName The instance name to be passed to
+ * {@link WifiP2pDnsSdServiceRequest#newInstance(String, String)}.
+ * @param serviceType The service type to be passed to
+ * {@link WifiP2pDnsSdServiceRequest#newInstance(String, String)}.
* @param channelId The ID of the channel for Wi-Fi P2P to operate on.
* @return The ID of the service request, which is used when calling
* {@link #wifiP2pRemoveServiceRequest(int, Integer)}.
* @throws Throwable If add service request action timed out or got invalid channel ID.
*/
@Rpc(description = "Add a service Bonjour discovery request.")
- public Integer wifiP2pAddBonjourServiceRequest(@RpcDefault(value = "0") Integer channelId)
- throws Throwable {
+ public Integer wifiP2pAddBonjourServiceRequest(
+ @RpcOptional String instanceName,
+ @RpcOptional String serviceType,
+ @RpcDefault(value = "0") Integer channelId
+ ) throws Throwable {
WifiP2pManager.Channel channel = getChannel(channelId);
-
- WifiP2pDnsSdServiceRequest request = WifiP2pDnsSdServiceRequest.newInstance();
+ WifiP2pDnsSdServiceRequest request;
+ if (instanceName != null) {
+ request = WifiP2pDnsSdServiceRequest.newInstance(instanceName, serviceType);
+ } else if (serviceType == null) {
+ request = WifiP2pDnsSdServiceRequest.newInstance();
+ } else {
+ request = WifiP2pDnsSdServiceRequest.newInstance(serviceType);
+ }
mServiceRequestCnt += 1;
mServiceRequests.put(mServiceRequestCnt, request);
@@ -636,7 +656,12 @@ public class WifiP2pManagerSnippet implements Snippet {
throws Throwable {
WifiP2pManager.Channel channel = getChannel(channelId);
String callbackId = UUID.randomUUID().toString();
- mP2pManager.removeServiceRequest(channel, mServiceRequests.remove(index),
+ WifiP2pServiceRequest serviceRequest = mServiceRequests.remove(index);
+ if (serviceRequest == null) {
+ throw new WifiP2pManagerException("Service request not found. Please use the request ID"
+ + " returned by `wifiP2pAddServiceRequest`.");
+ }
+ mP2pManager.removeServiceRequest(channel, serviceRequest,
new ActionListener(callbackId));
verifyActionListenerSucceed(callbackId);
}
@@ -725,8 +750,9 @@ public class WifiP2pManagerSnippet implements Snippet {
* @throws Throwable If the P2P operation failed or timed out, or got invalid channel ID.
*/
@Rpc(description = "Initiate service discovery.")
- public void wifiP2pDiscoverServices(@RpcDefault(value = "0") Integer channelId)
- throws Throwable {
+ public void wifiP2pDiscoverServices(
+ @RpcDefault(value = "0") Integer channelId
+ ) throws Throwable {
WifiP2pManager.Channel channel = getChannel(channelId);
String callbackId = UUID.randomUUID().toString();
mP2pManager.discoverServices(channel, new ActionListener(callbackId));
@@ -972,7 +998,7 @@ public class WifiP2pManagerSnippet implements Snippet {
throws WifiP2pManagerException {
WifiP2pManager.Channel channel = mChannels.get(channelId);
if (channel == null) {
- Log.e(TAG + ": checkAndGetChannel : channel keys" + mChannels.keySet());
+ Log.e(TAG + ": getChannel : channel keys" + mChannels.keySet());
throw new WifiP2pManagerException(
"The channelId " + channelId + " is wrong. Please use the channelId returned "
+ "by calling `wifiP2pInitialize` or `wifiP2pInitExtraChannel`.");
@@ -1033,8 +1059,10 @@ public class WifiP2pManagerSnippet implements Snippet {
return;
}
if (Objects.equals(ACTION_LISTENER_ON_FAILURE, result)) {
+ // Please keep reason code in error message for client side to check the reason.
throw new WifiP2pManagerException(
- "Action failed with reason code: " + eventData.getInt(EVENT_KEY_REASON));
+ "Action failed with reason_code=" + eventData.getInt(EVENT_KEY_REASON)
+ );
}
throw new WifiP2pManagerException("Action got unknown event: " + eventData.toString());
}
diff --git a/tests/hostsidetests/multidevices/test/Android.bp b/tests/hostsidetests/multidevices/test/Android.bp
index 5cb2d2b32b..b683a2a405 100644
--- a/tests/hostsidetests/multidevices/test/Android.bp
+++ b/tests/hostsidetests/multidevices/test/Android.bp
@@ -64,10 +64,10 @@ python_test_host {
defaults: ["CtsWifiMultiDevicePythonDefaults"],
}
-// TODO: Rename this to CtsWifiAwareTestCases when adding the CTS tag.
python_test_host {
- name: "WifiAwareManagerTestCases",
+ name: "CtsWifiAwareTests",
main: "aware/cts_wifi_aware_test_suite.py",
+ test_config: "aware/AndroidTestNew.xml",
srcs: [
"aware/cts_wifi_aware_test_suite.py",
"aware/wifi_aware_network_test.py",
@@ -84,9 +84,12 @@ python_test_host {
device_common_data: [":wifi_mobly_snippet"],
test_options: {
unit_test: false,
- tags: ["mobly"],
+ runner: "mobly",
},
- test_suites: ["general-tests"],
+ test_suites: [
+ "general-tests",
+ "cts-v-host",
+ ],
version: {
py3: {
embedded_launcher: true,
@@ -130,14 +133,16 @@ python_test_host {
}
python_test_host {
- name: "CtsWifiDirectTestCases",
+ name: "CtsWifiDirectTests",
main: "direct/cts_wifi_direct_test_suite.py",
srcs: [
"direct/group_owner_negotiation_test.py",
"direct/group_owner_test.py",
"direct/group_owner_with_config_test.py",
+ "direct/service_discovery_test.py",
"direct/cts_wifi_direct_test_suite.py",
],
+ test_config: "direct/AndroidTest.xml",
libs: [
"mobly",
"wifi_direct_constants",
@@ -148,9 +153,12 @@ python_test_host {
device_common_data: [":wifi_mobly_snippet"],
test_options: {
unit_test: false,
- tags: ["mobly"],
+ runner: "mobly",
},
- test_suites: ["general-tests"],
+ test_suites: [
+ "general-tests",
+ "cts-v-host",
+ ],
version: {
py3: {
embedded_launcher: true,
@@ -164,7 +172,7 @@ python_library_host {
}
python_test_host {
- name: "WifiSoftApTestCases",
+ name: "CtsWifiSoftApTestCases",
main: "softap/wifi_softap_test.py",
srcs: ["softap/wifi_softap_test.py"],
libs: [
@@ -174,9 +182,12 @@ python_test_host {
device_common_data: [":wifi_mobly_snippet"],
test_options: {
unit_test: false,
- tags: ["mobly"],
+ runner: "mobly",
},
- test_suites: ["general-tests"],
+ test_suites: [
+ "general-tests",
+ "cts-v-host",
+ ],
version: {
py3: {
embedded_launcher: true,
diff --git a/tests/hostsidetests/multidevices/test/aware/AndroidTestNew.xml b/tests/hostsidetests/multidevices/test/aware/AndroidTestNew.xml
new file mode 100644
index 0000000000..244257efec
--- /dev/null
+++ b/tests/hostsidetests/multidevices/test/aware/AndroidTestNew.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2025 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.
+-->
+<configuration description="Test config for Wi-Fi Aware multi-device CTS tests">
+ <option name="test-suite-tag" value="cts-v-host" />
+ <option name="config-descriptor:metadata" key="component" value="wifi" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+ <device name="AndroidDevice">
+ <target_preparer class="AndroidDeviceFeaturesCheckDecorator">
+ <option name="required_feature" value="android.hardware.wifi.aware" />
+ </target_preparer>
+ <target_preparer class="AndroidMainlineModulesCheckDecorator">
+ <option name="mainline_module_package_name" value="com.google.android.wifi" />
+ </target_preparer>
+ <target_preparer class="AndroidInstallAppsDecorator" />
+ </device>
+ <device name="AndroidDevice">
+ <target_preparer class="AndroidDeviceFeaturesCheckDecorator">
+ <option name="required_feature" value="android.hardware.wifi.aware" />
+ </target_preparer>
+ <target_preparer class="AndroidMainlineModulesCheckDecorator">
+ <option name="mainline_module_package_name" value="com.google.android.wifi" />
+ </target_preparer>
+ <target_preparer class="AndroidInstallAppsDecorator" />
+ </device>
+
+ <test class="MoblyAospPackageTest" />
+
+ <option name="mobly_pkg" key="file" value="CtsWifiAwareTests" />
+ <option name="build_apk" key="file" value="wifi_mobly_snippet.apk" />
+</configuration> \ No newline at end of file
diff --git a/tests/hostsidetests/multidevices/test/direct/AndroidTest.xml b/tests/hostsidetests/multidevices/test/direct/AndroidTest.xml
new file mode 100644
index 0000000000..63f3af4ada
--- /dev/null
+++ b/tests/hostsidetests/multidevices/test/direct/AndroidTest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2025 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.
+-->
+<configuration description="Test config for Wi-Fi Direct multi-device CTS tests">
+ <option name="test-suite-tag" value="cts-v-host" />
+ <option name="config-descriptor:metadata" key="component" value="wifi" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+ <device name="AndroidDevice">
+ <target_preparer class="AndroidDeviceFeaturesCheckDecorator">
+ <option name="required_feature" value="android.hardware.wifi.direct" />
+ </target_preparer>
+ <target_preparer class="AndroidMainlineModulesCheckDecorator">
+ <option name="mainline_module_package_name" value="com.google.android.wifi" />
+ </target_preparer>
+ <target_preparer class="AndroidInstallAppsDecorator" />
+ </device>
+ <device name="AndroidDevice">
+ <target_preparer class="AndroidDeviceFeaturesCheckDecorator">
+ <option name="required_feature" value="android.hardware.wifi.direct" />
+ </target_preparer>
+ <target_preparer class="AndroidMainlineModulesCheckDecorator">
+ <option name="mainline_module_package_name" value="com.google.android.wifi" />
+ </target_preparer>
+ <target_preparer class="AndroidInstallAppsDecorator" />
+ </device>
+
+ <test class="MoblyAospPackageTest" />
+
+ <option name="mobly_pkg" key="file" value="CtsWifiDirectTests" />
+ <option name="build_apk" key="file" value="wifi_mobly_snippet.apk" />
+</configuration> \ No newline at end of file
diff --git a/tests/hostsidetests/multidevices/test/direct/constants.py b/tests/hostsidetests/multidevices/test/direct/constants.py
index 6ba6cd2b06..903b10850a 100644
--- a/tests/hostsidetests/multidevices/test/direct/constants.py
+++ b/tests/hostsidetests/multidevices/test/direct/constants.py
@@ -267,11 +267,46 @@ class WifiP2pGroup:
return [cls.from_dict(group) for group in groups]
+@enum.unique
+class ServiceType(enum.IntEnum):
+ """Indicates the type of Wi-Fi p2p services.
+
+ https://developer.android.com/reference/android/net/wifi/p2p/nsd/WifiP2pServiceInfo#summary
+ """
+
+ ALL = 0
+ BONJOUR = 1
+ UPNP = 2
+ WS_DISCOVERY = 3
+
+
class ServiceData:
"""Constants for Wi-Fi p2p services."""
# Service configurations.
# Configuration for Bonjour IPP local service.
+ IPP_DNS_SD = (('MyPrinter', '_ipp._tcp.local.'),)
+ AFP_DNS_SD = (('Example', '_afpovertcp._tcp.local.'),)
+ ALL_DNS_SD = (
+ ('MyPrinter', '_ipp._tcp.local.'),
+ ('Example', '_afpovertcp._tcp.local.'),
+ )
+
+ IPP_DNS_TXT = (
+ ('myprinter._ipp._tcp.local.', {
+ 'txtvers': '1',
+ 'pdl': 'application/postscript'
+ }),
+ )
+ AFP_DNS_TXT = (('example._afpovertcp._tcp.local.', {}),)
+ ALL_DNS_TXT = (('myprinter._ipp._tcp.local.',
+ {
+ 'txtvers': '1',
+ 'pdl': 'application/postscript'
+ }
+ ), ('example._afpovertcp._tcp.local.', {}),)
+
+ # Configuration for IPP local service.
DEFAULT_IPP_SERVICE_CONF = {
'instance_name': 'MyPrinter',
'service_type': '_ipp._tcp',
@@ -297,7 +332,7 @@ class ServiceData:
}
# Expected services to be discovered.
- ALL_UPNP_SERVICES = [
+ ALL_UPNP_SERVICES = (
'uuid:6859dede-8574-59ab-9332-123456789011',
'uuid:6859dede-8574-59ab-9332-123456789011::upnp:rootdevice',
(
@@ -312,19 +347,14 @@ class ServiceData:
'uuid:6859dede-8574-59ab-9332-123456789011::urn:schemas-upnp-org:'
'service:ConnectionManager:1'
),
- ]
- ALL_DNS_SD = [
- ('MyPrinter', '_ipp._tcp.local.'),
- ('Example', '_afpovertcp._tcp.local.'),
- ]
- ALL_DNS_TXT = [
- (
- 'myprinter._ipp._tcp.local.',
- {
- 'txtvers': '1',
- 'pdl': 'application/postscript',
- },
- ),
- ('example._afpovertcp._tcp.local.', {}),
- ]
+ )
+
+ UPNP_ROOT_DEVICE = ('uuid:6859dede-8574-59ab-9332-123456789011::upnp:rootdevice',)
+
+
+class WifiP2pManagerConstants:
+ """Constants for Wi-Fi p2p manager.
+ https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pManager#NO_SERVICE_REQUESTS
+ """
+ NO_SERVICE_REQUESTS = 3
diff --git a/tests/hostsidetests/multidevices/test/direct/cts_wifi_direct_test_suite.py b/tests/hostsidetests/multidevices/test/direct/cts_wifi_direct_test_suite.py
index f10025f402..80d12abe05 100644
--- a/tests/hostsidetests/multidevices/test/direct/cts_wifi_direct_test_suite.py
+++ b/tests/hostsidetests/multidevices/test/direct/cts_wifi_direct_test_suite.py
@@ -21,6 +21,7 @@ from mobly import suite_runner
from direct import group_owner_negotiation_test
from direct import group_owner_test
from direct import group_owner_with_config_test
+from direct import service_discovery_test
class CtsWifiDirectTestSuite(base_suite.BaseSuite):
@@ -33,6 +34,7 @@ class CtsWifiDirectTestSuite(base_suite.BaseSuite):
)
self.add_test_class(group_owner_test.GroupOwnerTest)
self.add_test_class(group_owner_with_config_test.GroupOwnerWithConfigTest)
+ self.add_test_class(service_discovery_test.ServiceDiscoveryTest)
if __name__ == '__main__':
diff --git a/tests/hostsidetests/multidevices/test/direct/group_owner_negotiation_test.py b/tests/hostsidetests/multidevices/test/direct/group_owner_negotiation_test.py
index 2195dcc764..a1e733f869 100644
--- a/tests/hostsidetests/multidevices/test/direct/group_owner_negotiation_test.py
+++ b/tests/hostsidetests/multidevices/test/direct/group_owner_negotiation_test.py
@@ -147,8 +147,10 @@ class GroupOwnerNegotiationTest(base_test.BaseTestClass):
)
def _teardown_device(self, ad: android_device.AndroidDevice):
- p2p_utils.teardown_wifi_p2p(ad)
- ad.services.create_output_excerpts_all(self.current_test_info)
+ try:
+ p2p_utils.teardown_wifi_p2p(ad)
+ finally:
+ ad.services.create_output_excerpts_all(self.current_test_info)
def teardown_test(self) -> None:
utils.concurrent_exec(
diff --git a/tests/hostsidetests/multidevices/test/direct/group_owner_test.py b/tests/hostsidetests/multidevices/test/direct/group_owner_test.py
index 35e56e5605..bf98fa0645 100644
--- a/tests/hostsidetests/multidevices/test/direct/group_owner_test.py
+++ b/tests/hostsidetests/multidevices/test/direct/group_owner_test.py
@@ -105,21 +105,12 @@ class GroupOwnerTest(base_test.BaseTestClass):
client.ad.wifi.wifiP2pDiscoverServices()
# Client should only discover Upnp services, but not Bonjour services.
- group_owner_address = group_owner.p2p_device.device_address
- p2p_utils.check_discovered_upnp_services(
+ p2p_utils.check_discovered_services(
client,
- expected_services=constants.ServiceData.ALL_UPNP_SERVICES,
- expected_src_device_address=group_owner_address,
- )
- p2p_utils.check_discovered_dns_sd_response(
- client,
- expected_responses=[],
- expected_src_device_address=group_owner_address,
- )
- p2p_utils.check_discovered_dns_sd_txt_record(
- client,
- expected_records=[],
- expected_src_device_address=group_owner_address,
+ expected_src_device_address=group_owner.p2p_device.device_address,
+ expected_dns_sd_sequence=(),
+ expected_dns_txt_sequence=(),
+ expected_upnp_sequence=constants.ServiceData.ALL_UPNP_SERVICES,
)
# Step 5 - 7. Verify that the client can join the group with WPS PBC.
@@ -155,6 +146,7 @@ class GroupOwnerTest(base_test.BaseTestClass):
logging.info('Initializing Wi-Fi p2p.')
group_owner = p2p_utils.setup_wifi_p2p(self.group_owner_ad)
client = p2p_utils.setup_wifi_p2p(self.client_ad)
+ main_channel_id = client.channel_ids[0]
# Step 2. Add p2p local services on the group owner.
self._add_local_services(group_owner)
@@ -166,29 +158,25 @@ class GroupOwnerTest(base_test.BaseTestClass):
# Step 4. Perform p2p service discovery on the client.
client.ad.log.info('Searching for target p2p services.')
# Initialize an extra p2p channel.
- sub_channel_id = client.ad.wifi.wifiP2pInitExtraChannel()
- client.ad.wifi.wifiP2pAddBonjourServiceRequest(sub_channel_id)
+ sub_channel_id = p2p_utils.init_extra_channel(client)
+ client.ad.wifi.wifiP2pAddBonjourServiceRequest(
+ None, # serviceType
+ None, # instanceName
+ sub_channel_id,
+ )
p2p_utils.set_upnp_response_listener(client, sub_channel_id)
p2p_utils.set_dns_sd_response_listeners(client, sub_channel_id)
- # Discover services on the default p2p channel.
- client.ad.wifi.wifiP2pDiscoverServices()
+ # Discover services on the main channel.
+ client.ad.wifi.wifiP2pDiscoverServices(main_channel_id)
# Client should only discover Bonjour services, but not UPnP services.
- group_owner_address = group_owner.p2p_device.device_address
- p2p_utils.check_discovered_dns_sd_response(
+ p2p_utils.check_discovered_services(
client,
- expected_responses=constants.ServiceData.ALL_DNS_SD,
- expected_src_device_address=group_owner_address,
- )
- p2p_utils.check_discovered_dns_sd_txt_record(
- client,
- expected_records=constants.ServiceData.ALL_DNS_TXT,
- expected_src_device_address=group_owner_address,
- )
- p2p_utils.check_discovered_upnp_services(
- client,
- expected_services=[],
- expected_src_device_address=group_owner_address,
+ expected_src_device_address=group_owner.p2p_device.device_address,
+ expected_dns_sd_sequence=constants.ServiceData.ALL_DNS_SD,
+ expected_dns_txt_sequence=constants.ServiceData.ALL_DNS_TXT,
+ expected_upnp_sequence=(),
+ channel_id=sub_channel_id,
)
# Step 5 - 7. Verify that the client can join the group with WPS PIN.
@@ -243,9 +231,11 @@ class GroupOwnerTest(base_test.BaseTestClass):
client, group_owner, is_group_negotiation=False
)
- def _teardown_wifi_p2p(self, ad: android_device.AndroidDevice) -> None:
- p2p_utils.reset_p2p_service_state(ad)
- p2p_utils.teardown_wifi_p2p(ad)
+ def _teardown_wifi_p2p(self, ad: android_device.AndroidDevice):
+ try:
+ p2p_utils.teardown_wifi_p2p(ad)
+ finally:
+ ad.services.create_output_excerpts_all(self.current_test_info)
def teardown_test(self) -> None:
utils.concurrent_exec(
@@ -253,12 +243,6 @@ class GroupOwnerTest(base_test.BaseTestClass):
param_list=[[ad] for ad in self.ads],
raise_on_exception=True,
)
- self.client_ad.services.create_output_excerpts_all(
- self.current_test_info
- )
- self.group_owner_ad.services.create_output_excerpts_all(
- self.current_test_info
- )
def on_fail(self, record: records.TestResult) -> None:
logging.info('Collecting bugreports...')
diff --git a/tests/hostsidetests/multidevices/test/direct/group_owner_with_config_test.py b/tests/hostsidetests/multidevices/test/direct/group_owner_with_config_test.py
index fa1f2455a5..5b1bdba8c0 100644
--- a/tests/hostsidetests/multidevices/test/direct/group_owner_with_config_test.py
+++ b/tests/hostsidetests/multidevices/test/direct/group_owner_with_config_test.py
@@ -86,7 +86,7 @@ class GroupOwnerWithConfigTest(base_test.BaseTestClass):
@ApiTest([
'android.net.wifi.p2p.WifiP2pManager#createGroup(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pConfig, android.net.wifi.p2p.WifiP2pManager.ActionListener)',
'android.net.wifi.p2p.WifiP2pManager#removeGroup(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener)',
- ])
+ ])
def test_p2p_group_with_band_auto(self) -> None:
"""Tests p2p group without specifying band or frequency.
@@ -172,8 +172,10 @@ class GroupOwnerWithConfigTest(base_test.BaseTestClass):
)
def _teardown_wifi_p2p(self, ad: android_device.AndroidDevice):
- p2p_utils.teardown_wifi_p2p(ad)
- ad.services.create_output_excerpts_all(self.current_test_info)
+ try:
+ p2p_utils.teardown_wifi_p2p(ad)
+ finally:
+ ad.services.create_output_excerpts_all(self.current_test_info)
def teardown_test(self) -> None:
utils.concurrent_exec(
diff --git a/tests/hostsidetests/multidevices/test/direct/p2p_utils.py b/tests/hostsidetests/multidevices/test/direct/p2p_utils.py
index f301d50437..4be872f25d 100644
--- a/tests/hostsidetests/multidevices/test/direct/p2p_utils.py
+++ b/tests/hostsidetests/multidevices/test/direct/p2p_utils.py
@@ -11,7 +11,6 @@
# 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.
-
# Lint as: python3
from collections.abc import Sequence
@@ -26,8 +25,7 @@ from mobly.controllers import android_device
from direct import constants
-_DEFAULT_TIMEOUT = datetime.timedelta(seconds=60)
-DEFAULT_CHANNEL_ID = 0
+_DEFAULT_TIMEOUT = datetime.timedelta(seconds=45)
@dataclasses.dataclass
@@ -39,20 +37,20 @@ class DeviceState:
p2p_device: The object that represents a Wi-Fi p2p device.
broadcast_receiver: The object for getting events that represent
Wi-Fi p2p broadcast intents on device.
- upnp_response_listener: The listener that corresponds to
- UpnpServiceResponseListener on device.
- dns_sd_response_listener: The listener that listens for callback
- invocation of DnsSdServiceResponseListener and
- DnsSdTxtRecordListener.
+ channel_ids: The IDs of all p2p channels initialized by Mobly snippets.
+ upnp_response_listeners: Maps channel IDs to UPnP listeners. These
+ listeners correspond to UpnpServiceResponseListener on device.
+ dns_sd_response_listeners: Maps channel IDs to DNS SD listeners. These
+ listeners listen for callback invocation of
+ DnsSdServiceResponseListener and DnsSdTxtRecordListener.
"""
ad: android_device.AndroidDevice
p2p_device: constants.WifiP2pDevice
broadcast_receiver: callback_handler_v2.CallbackHandlerV2
- upnp_response_listener: callback_handler_v2.CallbackHandlerV2 | None = None
- dns_sd_response_listener: callback_handler_v2.CallbackHandlerV2 | None = (
- None
- )
+ channel_ids: Sequence[int] = dataclasses.field(default_factory=list)
+ upnp_response_listeners: dict[int, callback_handler_v2.CallbackHandlerV2] = dataclasses.field(default_factory=dict)
+ dns_sd_response_listeners: dict[int, callback_handler_v2.CallbackHandlerV2] = dataclasses.field(default_factory=dict)
def setup_wifi_p2p(ad: android_device.AndroidDevice) -> DeviceState:
@@ -67,7 +65,10 @@ def setup_wifi_p2p(ad: android_device.AndroidDevice) -> DeviceState:
'required by API WifiP2pManager#requestConnectionInfo',
)
return DeviceState(
- ad=ad, p2p_device=p2p_device, broadcast_receiver=broadcast_receiver
+ ad=ad,
+ p2p_device=p2p_device,
+ broadcast_receiver=broadcast_receiver,
+ channel_ids=[broadcast_receiver.ret_value],
)
@@ -159,6 +160,13 @@ def _get_p2p_device(
)
+def init_extra_channel(device: DeviceState) -> int:
+ """Initializes an extra p2p channel and returns the channel ID."""
+ channel_id = device.ad.wifi.wifiP2pInitExtraChannel()
+ device.channel_ids.append(channel_id)
+ return channel_id
+
+
def discover_p2p_peer(
requester: DeviceState,
responder: DeviceState,
@@ -516,39 +524,27 @@ def add_bonjour_local_service(device: DeviceState, config: dict):
def set_upnp_response_listener(
- device: DeviceState, channel_id: int = DEFAULT_CHANNEL_ID
+ device: DeviceState, channel_id: int | None = None
):
"""Set response listener for Upnp service."""
+ channel_id = channel_id or device.channel_ids[0]
upnp_response_listener = device.ad.wifi.wifiP2pSetUpnpResponseListener(
channel_id
)
- device.upnp_response_listener = upnp_response_listener
-
-
-def unset_upnp_response_listener(
- device: DeviceState, channel_id: int = DEFAULT_CHANNEL_ID
-):
- """Unset response listener for Upnp service."""
- device.ad.wifi.wifiP2pUnsetUpnpResponseListener(channel_id)
- device.upnp_response_listener = None
+ device.upnp_response_listeners[channel_id] = upnp_response_listener
def set_dns_sd_response_listeners(
- device: DeviceState, channel_id: int = DEFAULT_CHANNEL_ID
+ device: DeviceState, channel_id: int | None = None
):
"""Set response listener for Bonjour service."""
+ channel_id = channel_id or device.channel_ids[0]
listener = device.ad.wifi.wifiP2pSetDnsSdResponseListeners(channel_id)
- device.dns_sd_response_listener = listener
-
-
-def unset_dns_sd_response_listender(device: DeviceState):
- """Unset response listener for Bonjour service."""
- device.ad.wifi.wifiP2pUnsetDnsSdResponseListeners()
- device.dns_sd_response_listener = None
+ device.dns_sd_response_listeners[channel_id] = listener
def reset_p2p_service_state(
- ad: android_device.AndroidDevice, channel_id: int = DEFAULT_CHANNEL_ID
+ ad: android_device.AndroidDevice, channel_id
):
"""Clears all p2p service related states on device."""
ad.wifi.wifiP2pClearServiceRequests(channel_id)
@@ -557,15 +553,68 @@ def reset_p2p_service_state(
ad.wifi.wifiP2pClearLocalServices(channel_id)
+def check_discovered_services(
+ requester: DeviceState,
+ expected_src_device_address: str,
+ expected_dns_sd_sequence: Sequence[Sequence[str, dict[str, str]]],
+ expected_dns_txt_sequence: Sequence[Sequence[str, str]],
+ expected_upnp_sequence: Sequence[str],
+ channel_id: int | None = None,
+):
+ """Checks the discovered service responses are as expected.
+
+ This checks all services discovered within the timeout `_DEFAULT_TIMEOUT`.
+ If any expected service sequence is empty, this checks that no such service
+ are received within timeout.
+
+ Args:
+ device: The device that is discovering services.
+ expected_src_device_address: Only check services advertised from this
+ device address.
+ expected_dns_sd_sequence: Expected DNS SD responses.
+ expected_dns_txt_sequence: Expected DNS SD TXT records.
+ expected_upnp_sequence: Expected UPnP services.
+ channel_id: The channel to check for expected services.
+ """
+ channel_id = channel_id or requester.channel_ids[0]
+ start_time = datetime.datetime.now()
+ timeout = _DEFAULT_TIMEOUT
+ check_discovered_dns_sd_response(
+ requester,
+ expected_responses=expected_dns_sd_sequence,
+ expected_src_device_address=expected_src_device_address,
+ channel_id=channel_id,
+ timeout=timeout,
+ )
+ remaining_timeout = timeout - (datetime.datetime.now() - start_time)
+ check_discovered_dns_sd_txt_record(
+ requester,
+ expected_records=expected_dns_txt_sequence,
+ expected_src_device_address=expected_src_device_address,
+ channel_id=channel_id,
+ timeout=remaining_timeout,
+ )
+ remaining_timeout = timeout - (datetime.datetime.now() - start_time)
+ check_discovered_upnp_services(
+ requester,
+ expected_services=expected_upnp_sequence,
+ expected_src_device_address=expected_src_device_address,
+ channel_id=channel_id,
+ timeout=remaining_timeout,
+ )
+
+
def check_discovered_upnp_services(
device: DeviceState,
expected_services: Sequence[str],
expected_src_device_address: str,
+ channel_id: int | None = None,
+ timeout: datetime.timedelta = _DEFAULT_TIMEOUT,
):
"""Check discovered Upnp services.
- If no services are expected, check all discovered services now and return
- immediately. Otherwise, wait until all expected services are discovered.
+ If no services are expected, check that no UPnP service appear within
+ timeout. Otherwise, wait for all expected services within timeout.
This assumes that Upnp service listener is set by
`set_upnp_response_listener`.
@@ -575,18 +624,22 @@ def check_discovered_upnp_services(
expected_services: The expected Upnp services.
expected_src_device_address: This only checks services that are from the
expected source device.
+ channel_id: The channel to check for expected services.
+ timeout: The wait timeout.
"""
+ channel_id = channel_id or device.channel_ids[0]
+ callback_handler = device.upnp_response_listeners[channel_id]
if len(expected_services) == 0:
_check_no_discovered_service(
ad=device.ad,
- callback_handler=device.upnp_response_listener,
+ callback_handler=callback_handler,
event_name=constants.ON_UPNP_SERVICE_AVAILABLE,
expected_src_device_address=expected_src_device_address,
+ timeout=timeout,
)
return
- expected_services = set(expected_services.copy())
-
+ expected_services = set(expected_services)
def _all_service_received(event):
nonlocal expected_services
src_device = constants.WifiP2pDevice.from_dict(
@@ -600,11 +653,15 @@ def check_discovered_upnp_services(
expected_services.remove(service)
return len(expected_services) == 0
+ device.ad.log.debug('Waiting for UpnP services: %s', expected_services)
+ # Set to a small timeout to allow pulling all received events
+ if timeout.total_seconds() <= 1:
+ timeout = datetime.timedelta(seconds=1)
try:
- device.upnp_response_listener.waitForEvent(
+ callback_handler.waitForEvent(
event_name=constants.ON_UPNP_SERVICE_AVAILABLE,
predicate=_all_service_received,
- timeout=_DEFAULT_TIMEOUT.total_seconds(),
+ timeout=timeout.total_seconds(),
)
except errors.CallbackHandlerTimeoutError as e:
asserts.fail(
@@ -614,13 +671,15 @@ def check_discovered_upnp_services(
def check_discovered_dns_sd_response(
device: DeviceState,
- expected_responses: list[tuple[str, str]],
+ expected_responses: Sequence[Sequence[str, str]],
expected_src_device_address: str,
+ channel_id: int | None = None,
+ timeout: datetime.timedelta = _DEFAULT_TIMEOUT,
):
"""Check discovered DNS SD responses.
- If no responses are expected, check all discovered responses now and return
- immediately. Otherwise, wait until all expected responses are discovered.
+ If no responses are expected, check that no DNS SD response appear within
+ timeout. Otherwise, wait for all expected responses within timeout.
This assumes that Bonjour service listener is set by
`set_dns_sd_response_listeners`.
@@ -630,16 +689,23 @@ def check_discovered_dns_sd_response(
expected_responses: The expected DNS SD responses.
expected_src_device_address: This only checks services that are from the
expected source device.
+ channel_id: The channel to check for expected responses.
+ timeout: The wait timeout.
"""
+ channel_id = channel_id or device.channel_ids[0]
+ callback_handler = device.dns_sd_response_listeners[channel_id]
if not expected_responses:
_check_no_discovered_service(
device.ad,
- callback_handler=device.dns_sd_response_listener,
+ callback_handler=callback_handler,
event_name=constants.ON_DNS_SD_SERVICE_AVAILABLE,
expected_src_device_address=expected_src_device_address,
+ timeout=timeout,
)
return
+ expected_responses = list(expected_responses)
+
def _all_service_received(event):
nonlocal expected_responses
src_device = constants.WifiP2pDevice.from_dict(
@@ -655,11 +721,15 @@ def check_discovered_dns_sd_response(
expected_responses.remove(service_tuple)
return len(expected_responses) == 0
+ device.ad.log.debug('Waiting for DNS SD services: %s', expected_responses)
+ # Set to a small timeout to allow pulling all received events
+ if timeout.total_seconds() <= 1:
+ timeout = datetime.timedelta(seconds=1)
try:
- device.dns_sd_response_listener.waitForEvent(
+ callback_handler.waitForEvent(
event_name=constants.ON_DNS_SD_SERVICE_AVAILABLE,
predicate=_all_service_received,
- timeout=_DEFAULT_TIMEOUT.total_seconds(),
+ timeout=timeout.total_seconds(),
)
except errors.CallbackHandlerTimeoutError as e:
asserts.fail(
@@ -669,13 +739,15 @@ def check_discovered_dns_sd_response(
def check_discovered_dns_sd_txt_record(
device: DeviceState,
- expected_records: list[tuple[str, dict[str, str]]],
+ expected_records: Sequence[Sequence[str, dict[str, str]]],
expected_src_device_address: str,
+ channel_id: int | None = None,
+ timeout: datetime.timedelta = _DEFAULT_TIMEOUT,
):
"""Check discovered DNS SD TXT records.
- If no records are expected, check all discovered records now and return
- immediately. Otherwise, wait until all expected records are discovered.
+ If no records are expected, check that no DNS SD TXT record appear within
+ timeout. Otherwise, wait for all expected records within timeout.
This assumes that Bonjour service listener is set by
`set_dns_sd_response_listeners`.
@@ -685,16 +757,24 @@ def check_discovered_dns_sd_txt_record(
expected_records: The expected DNS SD TXT records.
expected_src_device_address: This only checks services that are from the
expected source device.
+ channel_id: The channel to check for expected records.
+ timeout: The wait timeout.
"""
+ channel_id = channel_id or device.channel_ids[0]
+ idx = device.channel_ids.index(channel_id)
+ callback_handler = device.dns_sd_response_listeners[idx]
if not expected_records:
_check_no_discovered_service(
device.ad,
- callback_handler=device.dns_sd_response_listener,
+ callback_handler=callback_handler,
event_name=constants.ON_DNS_SD_TXT_RECORD_AVAILABLE,
expected_src_device_address=expected_src_device_address,
+ timeout=timeout,
)
return
+ expected_records = list(expected_records)
+ device.ad.log.debug('Expected DNS SD TXT records: %s', expected_records)
def _all_service_received(event):
nonlocal expected_records
src_device = constants.WifiP2pDevice.from_dict(
@@ -710,11 +790,15 @@ def check_discovered_dns_sd_txt_record(
expected_records.remove(record_to_remove)
return len(expected_records) == 0
+ device.ad.log.debug('Waiting for DNS SD TXT records: %s', expected_records)
+ # Set to a small timeout to allow pulling all received events
+ if timeout.total_seconds() <= 1:
+ timeout = datetime.timedelta(seconds=1)
try:
- device.dns_sd_response_listener.waitForEvent(
+ callback_handler.waitForEvent(
event_name=constants.ON_DNS_SD_TXT_RECORD_AVAILABLE,
predicate=_all_service_received,
- timeout=_DEFAULT_TIMEOUT.total_seconds(),
+ timeout=timeout.total_seconds(),
)
except errors.CallbackHandlerTimeoutError as e:
asserts.fail(
@@ -727,18 +811,28 @@ def _check_no_discovered_service(
callback_handler: callback_handler_v2.CallbackHandlerV2,
event_name: str,
expected_src_device_address: str,
+ timeout: datetime.timedelta = _DEFAULT_TIMEOUT,
):
"""Checks that no service is received from the specified source device."""
- all_events = callback_handler.getAll(event_name)
- filtered_events = []
- for event in all_events:
+ def _is_expected_event(event):
src_device = constants.WifiP2pDevice.from_dict(
event.data['sourceDevice']
)
- if src_device.device_address == expected_src_device_address:
- filtered_events.append(event)
- asserts.assert_equal(
- len(filtered_events),
- 0,
- f'{ad} should not discover p2p service. Discovered: {filtered_events}',
+ return src_device.device_address == expected_src_device_address
+
+ # Set to a small timeout to allow pulling all received events
+ if timeout.total_seconds() <= 1:
+ timeout = datetime.timedelta(seconds=1)
+ try:
+ event = callback_handler.waitForEvent(
+ event_name=event_name,
+ predicate=_is_expected_event,
+ timeout=timeout.total_seconds(),
+ )
+ except errors.CallbackHandlerTimeoutError as e:
+ # Timeout error is expected as there should not be any qualified service
+ return
+ asserts.assert_is_none(
+ event,
+ f'{ad} should not discover p2p service. Discovered: {event}',
)
diff --git a/tests/hostsidetests/multidevices/test/direct/service_discovery_test.py b/tests/hostsidetests/multidevices/test/direct/service_discovery_test.py
new file mode 100644
index 0000000000..daef99e196
--- /dev/null
+++ b/tests/hostsidetests/multidevices/test/direct/service_discovery_test.py
@@ -0,0 +1,631 @@
+# Copyright (C) 2024 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.
+
+# Lint as: python3
+"""Test cases for Wi-Fi p2p service discovery."""
+from collections.abc import Sequence
+import logging
+
+from android.platform.test.annotations import ApiTest
+from direct import constants
+from direct import p2p_utils
+from mobly import asserts
+from mobly import base_test
+from mobly import records
+from mobly import test_runner
+from mobly import utils
+from mobly.controllers import android_device
+
+import wifi_test_utils
+
+
+@ApiTest(
+ [
+ "android.net.wifi.p2p.WifiP2pManager#discoverServices(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener)",
+ "android.net.wifi.p2p.WifiP2pManager#addServiceRequest(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.WifiP2pServiceRequest, android.net.wifi.p2p.WifiP2pManager.ActionListener)",
+ "android.net.wifi.p2p.WifiP2pManager#setUpnpServiceResponseListener(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.UpnpServiceResponseListener)",
+ "android.net.wifi.p2p.WifiP2pManager#setDnsSdResponseListeners(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.DnsSdServiceResponseListener, android.net.wifi.p2p.WifiP2pManager.DnsSdTxtRecordListener)",
+ "android.net.wifi.p2p.WifiP2pManager#removeServiceRequest(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.WifiP2pServiceRequest, android.net.wifi.p2p.WifiP2pManager.ActionListener)",
+ "android.net.wifi.p2p.WifiP2pManager#clearServiceRequests(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener)"
+ ]
+)
+class ServiceDiscoveryTest(base_test.BaseTestClass):
+ """Test cases for Wi-Fi p2p service discovery.
+
+ Test Preconditions:
+ Two Android phones that support Wi-Fi Direct.
+
+ Test steps are described in the docstring of each test case.
+ """
+
+ ads: Sequence[android_device.AndroidDevice]
+ responder_ad: android_device.AndroidDevice
+ requester_ad: android_device.AndroidDevice
+
+ def setup_class(self) -> None:
+ super().setup_class()
+ self.ads = self.register_controller(android_device, min_number=2)
+ utils.concurrent_exec(
+ self._setup_device,
+ param_list=[[ad] for ad in self.ads],
+ raise_on_exception=True,
+ )
+ self.responder_ad, self.requester_ad, *_ = self.ads
+ self.responder_ad.debug_tag = (
+ f'{self.responder_ad.serial}(Responder)'
+ )
+ self.requester_ad.debug_tag = f'{self.requester_ad.serial}(Requester)'
+
+ def _setup_device(self, ad: android_device.AndroidDevice) -> None:
+ ad.load_snippet('wifi', constants.WIFI_SNIPPET_PACKAGE_NAME)
+ wifi_test_utils.set_screen_on_and_unlock(ad)
+ # Clear all saved Wi-Fi networks.
+ ad.wifi.wifiDisable()
+ ad.wifi.wifiClearConfiguredNetworks()
+ ad.wifi.wifiEnable()
+
+ def test_search_all_services_01(self) -> None:
+ """Searches all p2p services with API
+ `WifiP2pServiceRequest.newInstance(WifiP2pServiceInfo.SERVICE_TYPE_ALL)`.
+
+ Test Steps:
+ 1. Initialize Wi-Fi p2p on both devices.
+ 2. Add local services UPnP and Bonjour and initiate peer discovery on one device
+ (responder).
+ 3. Add service request on another device (requester) with API
+ `WifiP2pServiceRequest.newInstance(WifiP2pServiceInfo.SERVICE_TYPE_ALL)`.
+ 4. Initiate p2p service discovery on the requester. Verify that the requester
+ discovers all services.
+ """
+ requester, responder = self._setup_wifi_p2p()
+ self._add_p2p_services(responder)
+ requester.ad.wifi.wifiP2pAddServiceRequest(
+ constants.ServiceType.ALL
+ )
+ self._search_p2p_services(
+ responder,
+ requester,
+ expected_dns_sd_sequence=constants.ServiceData.ALL_DNS_SD,
+ expected_dns_txt_sequence=constants.ServiceData.ALL_DNS_TXT,
+ expected_upnp_sequence=constants.ServiceData.ALL_UPNP_SERVICES,
+ )
+
+ def test_search_all_services_02(self) -> None:
+ """Searches all p2p services with API
+ `WifiP2pServiceRequest.newInstance(SERVICE_TYPE_BONJOUR/SERVICE_TYPE_UPNP)`
+
+ Test Steps:
+ 1. Initialize Wi-Fi p2p on both devices.
+ 2. Add local services UPnP and Bonjour and initiate peer discovery on one device
+ (responder).
+ 3. Add service request on another device (requester) with API
+ `WifiP2pServiceRequest.newInstance(SERVICE_TYPE_BONJOUR)` and
+ `WifiP2pServiceRequest.newInstance(SERVICE_TYPE_UPNP)`.
+ 4. Initiate p2p service discovery on the requester. Verify that the requester discovers
+ all services.
+ """
+ requester, responder = self._setup_wifi_p2p()
+ self._add_p2p_services(responder)
+ requester.ad.wifi.wifiP2pAddServiceRequest(
+ constants.ServiceType.BONJOUR,
+ )
+ requester.ad.wifi.wifiP2pAddServiceRequest(
+ constants.ServiceType.UPNP,
+ )
+ self._search_p2p_services(
+ responder,
+ requester,
+ expected_dns_sd_sequence=constants.ServiceData.ALL_DNS_SD,
+ expected_dns_txt_sequence=constants.ServiceData.ALL_DNS_TXT,
+ expected_upnp_sequence=constants.ServiceData.ALL_UPNP_SERVICES,
+ )
+
+ def test_search_all_services_03(self) -> None:
+ """Searches all p2p services with API `WifiP2pDnsSdServiceRequest.newInstance()`
+ and `WifiP2pUpnpServiceRequest.newInstance()`.
+
+ Test Steps:
+ 1. Initialize Wi-Fi p2p on both devices.
+ 2. Add local services UPnP and Bonjour and initiate peer discovery on one device
+ (responder).
+ 3. Add service request on another device (requester) with API
+ `WifiP2pUpnpServiceRequest.newInstance()` and
+ `WifiP2pDnsSdServiceRequest.newInstance()`.
+ 4. Initiate p2p service discovery on the requester. Verify that the requester discovers
+ all services.
+ """
+ requester, responder = self._setup_wifi_p2p()
+ self._add_p2p_services(responder)
+ requester.ad.wifi.wifiP2pAddBonjourServiceRequest()
+ requester.ad.wifi.wifiP2pAddUpnpServiceRequest()
+ self._search_p2p_services(
+ responder,
+ requester,
+ expected_dns_sd_sequence=constants.ServiceData.ALL_DNS_SD,
+ expected_dns_txt_sequence=constants.ServiceData.ALL_DNS_TXT,
+ expected_upnp_sequence=constants.ServiceData.ALL_UPNP_SERVICES,
+ )
+
+ def test_serv_req_dns_ptr(self) -> None:
+ """Searches Bonjour services with Bonjour domain.
+
+ Test Steps:
+ 1. Initialize Wi-Fi p2p on both devices.
+ 2. Add local services UPnP and Bonjour and initiate peer discovery on one device
+ (responder).
+ 3. Add Bonjour service request with service type `_ipp._tcp`.
+ 4. Initiate p2p service discovery on the requester. Verify that the requester discovers
+ expected services.
+ """
+ requester, responder = self._setup_wifi_p2p()
+ self._add_p2p_services(responder)
+ requester.ad.wifi.wifiP2pAddBonjourServiceRequest(
+ None, # instanceName
+ '_ipp._tcp',
+ )
+ self._search_p2p_services(
+ responder,
+ requester,
+ expected_dns_sd_sequence=constants.ServiceData.IPP_DNS_SD,
+ expected_dns_txt_sequence=(),
+ expected_upnp_sequence=(),
+ )
+
+ def test_serv_req_dns_txt(self) -> None:
+ """Searches Bonjour services with TXT record.
+
+ Test Steps:
+ 1. Initialize Wi-Fi p2p on both devices.
+ 2. Add local services UPnP and Bonjour and initiate peer discovery on one device
+ (responder).
+ 3. Add Bonjour service request with instance name `MyPrinter` and
+ service type `_ipp._tcp`.
+ 4. Initiate p2p service discovery on the requester. Verify that the requester discovers
+ expected services.
+ """
+ requester, responder = self._setup_wifi_p2p()
+ self._add_p2p_services(responder)
+ requester.ad.wifi.wifiP2pAddBonjourServiceRequest(
+ 'MyPrinter',
+ '_ipp._tcp',
+ )
+ self._search_p2p_services(
+ responder,
+ requester,
+ expected_dns_sd_sequence=(),
+ expected_dns_txt_sequence=constants.ServiceData.IPP_DNS_TXT,
+ expected_upnp_sequence=(),
+ )
+
+ def test_serv_req_upnp_all(self) -> None:
+ """Searches all UPnP services with service type `ssdp:all`.
+
+ Test Steps:
+ 1. Initialize Wi-Fi p2p on both devices.
+ 2. Add local services UPnP and Bonjour and initiate peer discovery on one device
+ (responder).
+ 3. Add UPnP service request with service type `ssdp:all`.
+ 4. Initiate p2p service discovery on the requester. Verify that the requester discovers
+ expected services.
+ """
+ requester, responder = self._setup_wifi_p2p()
+ self._add_p2p_services(responder)
+ requester.ad.wifi.wifiP2pAddUpnpServiceRequest('ssdp:all')
+ self._search_p2p_services(
+ responder,
+ requester,
+ expected_dns_sd_sequence=(),
+ expected_dns_txt_sequence=(),
+ expected_upnp_sequence=constants.ServiceData.ALL_UPNP_SERVICES,
+ )
+
+ def test_serv_req_upnp_root_device(self) -> None:
+ """Searches UPnP root devices.
+
+ Test Steps:
+ 1. Initialize Wi-Fi p2p on both devices.
+ 2. Add local services UPnP and Bonjour and initiate peer discovery on one device
+ (responder).
+ 3. Add UPnP service request with service type `upnp:rootdevice`.
+ 4. Initiate p2p service discovery on the requester. Verify that the requester discovers
+ expected services.
+ """
+ requester, responder = self._setup_wifi_p2p()
+ self._add_p2p_services(responder)
+ requester.ad.wifi.wifiP2pAddUpnpServiceRequest('upnp:rootdevice')
+ self._search_p2p_services(
+ responder,
+ requester,
+ expected_dns_sd_sequence=(),
+ expected_dns_txt_sequence=(),
+ expected_upnp_sequence=constants.ServiceData.UPNP_ROOT_DEVICE,
+ )
+
+ def test_serv_req_remove_request(self) -> None:
+ """Checks that API `WifiP2pManager#removeServiceRequest` works well.
+
+ Test Steps:
+ 1. Initialize Wi-Fi p2p on both devices.
+ 2. Add local services UPnP and Bonjour and initiate peer discovery on one device
+ (responder).
+ 3. Add 2 UPnP service requests and 2 Bonjour service requests on the
+ requester.
+ 4. Removes 3 of the 4 added requests.
+ 5. Initiate p2p service discovery on the requester. Verify that the requester
+ only discovers services corresponds to the remaining request.
+ """
+ requester, responder = self._setup_wifi_p2p()
+ self._add_p2p_services(responder)
+
+ # Add requests
+ requester.ad.log.info('Adding service requests.')
+ upnp_req_1_id = requester.ad.wifi.wifiP2pAddUpnpServiceRequest()
+ requester.ad.wifi.wifiP2pAddUpnpServiceRequest('ssdp:all')
+ bonjour_req_1_id = requester.ad.wifi.wifiP2pAddBonjourServiceRequest()
+ bonjour_req_2_id = requester.ad.wifi.wifiP2pAddBonjourServiceRequest(
+ None, # instanceName
+ '_ipp._tcp',
+ )
+
+ # Remove 3 of the 4 added requests except for ssdp:all
+ requester.ad.log.info('Removing service requests.')
+ requester.ad.wifi.wifiP2pRemoveServiceRequest(upnp_req_1_id)
+ requester.ad.wifi.wifiP2pRemoveServiceRequest(bonjour_req_1_id)
+ requester.ad.wifi.wifiP2pRemoveServiceRequest(bonjour_req_2_id)
+
+ # Initialize test listener.
+ p2p_utils.set_upnp_response_listener(requester)
+ p2p_utils.set_dns_sd_response_listeners(requester)
+ # Search service
+ requester.ad.wifi.wifiP2pDiscoverServices()
+
+ # Initiates service discovery and check expected services.
+ p2p_utils.check_discovered_services(
+ requester,
+ responder.p2p_device.device_address,
+ expected_dns_sd_sequence=(),
+ expected_dns_txt_sequence=(),
+ expected_upnp_sequence=constants.ServiceData.ALL_UPNP_SERVICES
+ )
+
+ def test_serv_req_clear_request(self) -> None:
+ """Checks that API `WifiP2pManager#clearServiceRequests` works well.
+
+ Test Steps:
+ 1. Initialize Wi-Fi p2p on both devices.
+ 2. Add local services UPnP and Bonjour and initiate peer discovery on one device
+ (responder).
+ 3. Add 2 UPnP service requests and 2 Bonjour service requests on the
+ requester.
+ 4. Clears all added requests.
+ 5. Initiate p2p service discovery on the requester. Verify that the service
+ discovery should fail due to no service request.
+ """
+ requester, responder = self._setup_wifi_p2p()
+ self._add_p2p_services(responder)
+
+ # Add requests
+ requester.ad.log.info('Adding service requests.')
+ requester.ad.wifi.wifiP2pAddUpnpServiceRequest()
+ requester.ad.wifi.wifiP2pAddUpnpServiceRequest('ssdp:all')
+ requester.ad.wifi.wifiP2pAddBonjourServiceRequest()
+ requester.ad.wifi.wifiP2pAddBonjourServiceRequest(
+ None, # instanceName
+ '_ipp._tcp',
+ )
+
+ # Clear requests
+ requester.ad.log.info('Clearing all service requests.')
+ requester.ad.wifi.wifiP2pClearServiceRequests()
+
+ # Search services, but NO_SERVICE_REQUESTS is returned.
+ requester.ad.log.info('Initiating service discovery.')
+ expect_error_code = constants.WifiP2pManagerConstants.NO_SERVICE_REQUESTS
+ with asserts.assert_raises_regex(
+ Exception,
+ f'reason_code={str(expect_error_code)}',
+ extras='Service discovery should fail due to no service request.',
+ ):
+ requester.ad.wifi.wifiP2pDiscoverServices()
+
+ def test_serv_req_multi_channel_01(self) -> None:
+ """Searches all UPnP services on channel1 and all Bonjour services on
+ channel2.
+
+ Test Steps:
+ 1. Initialize Wi-Fi p2p on both devices. This initializes p2p channel
+ channel1.
+ 2. Add local services UPnP and Bonjour and initiate peer discovery on one device
+ (responder).
+ 3. Initialize an extra p2p channel channel2 on another device (requester).
+ 4. Add a UPnP service request to channel1.
+ 5. Add a Bonjour request to channel2.
+ 6. Initiate p2p service discovery on channel1. Verify that the requester
+ discovers UPnP services on channel1 and Bonjour services on channel2.
+ """
+ requester, responder = self._setup_wifi_p2p()
+ self._add_p2p_services(responder)
+ channel1 = requester.channel_ids[0]
+ channel2 = p2p_utils.init_extra_channel(requester)
+
+ requester.ad.log.info('Adding service requests.')
+ # Add UPnP request to the channel 1.
+ requester.ad.wifi.wifiP2pAddUpnpServiceRequest(
+ None, # serviceType
+ channel1,
+ )
+ # Add UPnP request to channel 2.
+ requester.ad.wifi.wifiP2pAddBonjourServiceRequest(
+ None, # instanceName
+ None, # serviceType
+ channel2,
+ )
+
+ # Set service listener.
+ requester.ad.log.info('Setting service listeners.')
+ p2p_utils.set_upnp_response_listener(requester, channel1)
+ p2p_utils.set_dns_sd_response_listeners(requester, channel1)
+ p2p_utils.set_upnp_response_listener(requester, channel2)
+ p2p_utils.set_dns_sd_response_listeners(requester, channel2)
+
+ # Discover services
+ requester.ad.log.info('Initiating service discovery.')
+ requester.ad.wifi.wifiP2pDiscoverServices(channel1)
+ responder_address = responder.p2p_device.device_address
+
+ # Check discovered services
+ # Channel1 receive only UPnP service.
+ requester.ad.log.info('Checking services on channel %d.', channel1)
+ p2p_utils.check_discovered_services(
+ requester,
+ responder.p2p_device.device_address,
+ expected_dns_sd_sequence=(),
+ expected_dns_txt_sequence=(),
+ expected_upnp_sequence=constants.ServiceData.ALL_UPNP_SERVICES,
+ channel_id=channel1,
+ )
+ # Channel2 receive only Bonjour service.
+ requester.ad.log.info('Checking services on channel %d.', channel2)
+ p2p_utils.check_discovered_services(
+ requester,
+ responder.p2p_device.device_address,
+ expected_dns_sd_sequence=constants.ServiceData.ALL_DNS_SD,
+ expected_dns_txt_sequence=constants.ServiceData.ALL_DNS_TXT,
+ expected_upnp_sequence=(),
+ channel_id=channel2,
+ )
+
+ # Clean up.
+ p2p_utils.reset_p2p_service_state(requester.ad, channel1)
+ p2p_utils.reset_p2p_service_state(requester.ad, channel2)
+
+ def test_serv_req_multi_channel_02(self) -> None:
+ """Searches Bonjour IPP PTR service on channel1 and AFP TXT service on channel2.
+
+ Test Steps:
+ 1. Initialize Wi-Fi p2p on both devices. This initializes p2p channel
+ channel1.
+ 2. Add local services UPnP and Bonjour and initiate peer discovery on one device
+ (responder).
+ 3. Initialize an extra p2p channel channel2 on another device (requester).
+ 4. Add a Bonjour IPP PTR request to channel1.
+ 5. Add a Bonjour AFP TXT request to channel2.
+ 6. Initiate p2p service discovery on channel1. Verify that the requester
+ discovers IPP PTR services on channel1 and AFP TXT services on channel2.
+ """
+ requester, responder = self._setup_wifi_p2p()
+ self._add_p2p_services(responder)
+ channel1 = requester.channel_ids[0]
+ channel2 = p2p_utils.init_extra_channel(requester)
+
+ # Add Bonjour IPP PRT request to channel1.
+ requester.ad.log.info('Adding service requests.')
+ requester.ad.wifi.wifiP2pAddBonjourServiceRequest(
+ None, # instanceName
+ '_ipp._tcp',
+ channel1,
+ )
+
+ # Add Bonjour AFP TXT request to channel2.
+ requester.ad.wifi.wifiP2pAddBonjourServiceRequest(
+ 'Example',
+ '_afpovertcp._tcp',
+ channel2,
+ )
+
+ # Initialize listener test objects.
+ requester.ad.log.info('Setting service listeners.')
+ p2p_utils.set_upnp_response_listener(requester, channel1)
+ p2p_utils.set_dns_sd_response_listeners(requester, channel1)
+ p2p_utils.set_upnp_response_listener(requester, channel2)
+ p2p_utils.set_dns_sd_response_listeners(requester, channel2)
+
+ # Discover services
+ requester.ad.log.info('Initiating service discovery.')
+ requester.ad.wifi.wifiP2pDiscoverServices(channel1)
+ responder_address = responder.p2p_device.device_address
+
+ # Check discovered services
+ # Channel1 receive only Bonjour IPP PTR.
+ requester.ad.log.info('Checking services on channel %d.', channel1)
+ p2p_utils.check_discovered_services(
+ requester,
+ responder.p2p_device.device_address,
+ expected_dns_sd_sequence=constants.ServiceData.IPP_DNS_SD,
+ expected_dns_txt_sequence=(),
+ expected_upnp_sequence=(),
+ channel_id=channel1,
+ )
+ # Channel2 receive only Bonjour AFP TXT.
+ requester.ad.log.info('Checking services on channel %d.', channel2)
+ p2p_utils.check_discovered_services(
+ requester,
+ responder.p2p_device.device_address,
+ expected_dns_sd_sequence=(),
+ expected_dns_txt_sequence=constants.ServiceData.AFP_DNS_TXT,
+ expected_upnp_sequence=(),
+ channel_id=channel2,
+ )
+
+ # Clean up.
+ p2p_utils.reset_p2p_service_state(requester.ad, channel1)
+ p2p_utils.reset_p2p_service_state(requester.ad, channel2)
+
+ def test_serv_req_multi_channel_03(self) -> None:
+ """Checks that `removeServiceRequest` and `clearServiceRequests` have no
+ effect against another channel.
+
+ Test Steps:
+ 1. Initialize Wi-Fi p2p on both devices. This initializes p2p channel
+ channel1.
+ 2. Add local services UPnP and Bonjour and initiate peer discovery on one device
+ (responder).
+ 3. Initialize an extra p2p channel channel2 on another device (requester).
+ 4. Add a Bonjour request to channel1.
+ 5. Try to remove the request of channel1 on channel2. This should
+ not have effect.
+ 6. Try to clear service requests on channel2. This should not have
+ effect.
+ 4. Add a Bonjour request to channel2.
+ 5. Initiate p2p service discovery on channel1. Verify that the requester
+ discovers Bonjour services but not UPnP services on channel1.
+ """
+ requester, responder = self._setup_wifi_p2p()
+ self._add_p2p_services(responder)
+ channel1 = requester.channel_ids[0]
+ channel2 = p2p_utils.init_extra_channel(requester)
+
+ # Add Bonjour request to channel1.
+ requester.ad.log.info('Adding service request to channel %d', channel1)
+ bonjour_req_id = requester.ad.wifi.wifiP2pAddBonjourServiceRequest(
+ None, # instanceName
+ None, # serviceType
+ channel1,
+ )
+ requester.ad.log.info('Added request %d', bonjour_req_id)
+
+ # Try to remove the Bonjour request of channel1 on channel2.
+ # However, it should silently succeed but have no effect
+ requester.ad.log.info('Removing the request %d from channel %d', bonjour_req_id, channel1)
+ requester.ad.wifi.wifiP2pRemoveServiceRequest(bonjour_req_id, channel2)
+
+ # Clear the all requests on channel2.
+ # However, it should silently succeed but have no effect
+ requester.ad.log.info('Clearing service requests on channel %d', channel2)
+ requester.ad.wifi.wifiP2pClearServiceRequests(channel2)
+
+ # Initialize service listeners.
+ requester.ad.log.info('Setting service listeners on both channels.')
+ p2p_utils.set_upnp_response_listener(requester, channel1)
+ p2p_utils.set_dns_sd_response_listeners(requester, channel1)
+ p2p_utils.set_upnp_response_listener(requester, channel2)
+ p2p_utils.set_dns_sd_response_listeners(requester, channel2)
+
+ # Discover services
+ requester.ad.log.info('Initiating service discovery.')
+ requester.ad.wifi.wifiP2pDiscoverServices(channel1)
+ responder_address = responder.p2p_device.device_address
+
+ # Check that Bonjour response can be received on channel1
+ requester.ad.log.info('Checking Bonjour services on channel %d.', channel1)
+ p2p_utils.check_discovered_services(
+ requester,
+ responder.p2p_device.device_address,
+ expected_dns_sd_sequence=constants.ServiceData.ALL_DNS_SD,
+ expected_dns_txt_sequence=constants.ServiceData.AFP_DNS_TXT,
+ expected_upnp_sequence=(),
+ channel_id=channel1,
+ )
+
+ # Clean up.
+ p2p_utils.reset_p2p_service_state(requester.ad, channel1)
+ p2p_utils.reset_p2p_service_state(requester.ad, channel2)
+
+ def _search_p2p_services(
+ self,
+ responder: p2p_utils.DeviceState,
+ requester: p2p_utils.DeviceState,
+ expected_dns_sd_sequence: Sequence[Sequence[str, dict[str, str]]],
+ expected_dns_txt_sequence: Sequence[Sequence[str, str]],
+ expected_upnp_sequence: Sequence[str],
+ ) -> None:
+ """Initiate service discovery and assert expected p2p services are discovered.
+
+ Args:
+ responder: The responder device state.
+ requester: The requester device state.
+ expected_dns_sd_sequence: Expected DNS-SD responses.
+ expected_dns_txt_sequence: Expected DNS TXT records.
+ expected_upnp_sequence: Expected UPNP services.
+ """
+ requester.ad.log.info('Initiating service discovery.')
+ p2p_utils.set_upnp_response_listener(requester)
+ p2p_utils.set_dns_sd_response_listeners(requester)
+ requester.ad.wifi.wifiP2pDiscoverServices()
+
+ requester.ad.log.info('Checking discovered services.')
+ p2p_utils.check_discovered_services(
+ requester,
+ responder.p2p_device.device_address,
+ expected_dns_sd_sequence=expected_dns_sd_sequence,
+ expected_dns_txt_sequence=expected_dns_txt_sequence,
+ expected_upnp_sequence=expected_upnp_sequence,
+ )
+
+ def _add_p2p_services(self, responder: p2p_utils.DeviceState):
+ """Sets up P2P services on the responder device.
+
+ This method adds local UPNP and Bonjour services to the responder device and
+ initiates peer discovery.
+ """
+ responder.ad.log.info('Setting up p2p local services UpnP and Bonjour.')
+ p2p_utils.add_upnp_local_service(
+ responder, constants.ServiceData.DEFAULT_UPNP_SERVICE_CONF
+ )
+ p2p_utils.add_bonjour_local_service(
+ responder, constants.ServiceData.DEFAULT_IPP_SERVICE_CONF
+ )
+ p2p_utils.add_bonjour_local_service(
+ responder, constants.ServiceData.DEFAULT_AFP_SERVICE_CONF
+ )
+ responder.ad.wifi.wifiP2pDiscoverPeers()
+
+ def _setup_wifi_p2p(self):
+ logging.info('Initializing Wi-Fi p2p.')
+ responder = p2p_utils.setup_wifi_p2p(self.responder_ad)
+ requester = p2p_utils.setup_wifi_p2p(self.requester_ad)
+ return requester, responder
+
+ def _teardown_wifi_p2p(self, ad: android_device.AndroidDevice):
+ try:
+ p2p_utils.teardown_wifi_p2p(ad)
+ finally:
+ ad.services.create_output_excerpts_all(self.current_test_info)
+
+ def teardown_test(self) -> None:
+ utils.concurrent_exec(
+ self._teardown_wifi_p2p,
+ param_list=[[ad] for ad in self.ads],
+ raise_on_exception=False,
+ )
+
+ def on_fail(self, record: records.TestResult) -> None:
+ logging.info('Collecting bugreports...')
+ android_device.take_bug_reports(
+ self.ads, destination=self.current_test_info.output_path
+ )
+
+
+if __name__ == '__main__':
+ test_runner.main()
diff --git a/tests/hostsidetests/multidevices/test/softap/AndroidTest.xml b/tests/hostsidetests/multidevices/test/softap/AndroidTest.xml
new file mode 100644
index 0000000000..19da4cca39
--- /dev/null
+++ b/tests/hostsidetests/multidevices/test/softap/AndroidTest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2025 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.
+-->
+<configuration description="Wifi SoftAp multi-device CTS tests">
+ <option name="test-suite-tag" value="cts-v-host" />
+ <option name="config-descriptor:metadata" key="component" value="wifi" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+ <device name="AndroidDevice">
+ <target_preparer class="AndroidDeviceFeaturesCheckDecorator">
+ <option name="required_feature" value="android.hardware.wifi" />
+ </target_preparer>
+ <target_preparer class="AndroidMainlineModulesCheckDecorator">
+ <option name="mainline_module_package_name" value="com.google.android.wifi" />
+ </target_preparer>
+ <target_preparer class="AndroidInstallAppsDecorator" />
+ </device>
+ <device name="AndroidDevice">
+ <target_preparer class="AndroidDeviceFeaturesCheckDecorator">
+ <option name="required_feature" value="android.hardware.wifi" />
+ </target_preparer>
+ <target_preparer class="AndroidMainlineModulesCheckDecorator">
+ <option name="mainline_module_package_name" value="com.google.android.wifi" />
+ </target_preparer>
+ <target_preparer class="AndroidInstallAppsDecorator" />
+ </device>
+
+ <test class="MoblyAospPackageTest" />
+
+ <option name="mobly_pkg" key="file" value="CtsWifiSoftApTestCases" />
+ <option name="build_apk" key="file" value="wifi_mobly_snippet.apk" />
+</configuration> \ No newline at end of file