diff options
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 |