From 3e24dec82a95fe3d37518a6e7bcee43c152b3c54 Mon Sep 17 00:00:00 2001 From: Paul Hu Date: Thu, 4 Jul 2024 08:54:45 +0000 Subject: [PATCH 1/2] Setup Wi-Fi P2P connection Add functionalities to setup Wi-Fi P2P connection for subsequent Wi-Fi P2P mDns tests Bug: 343311941 Test: atest CtsConnectivityMultiDevicesTestCases Change-Id: I060509deb32c078829c1108f9ae2a29561e00d53 --- .../testutils/host/python/wifip2p_utils.py | 49 +++- .../snippet/Wifip2pMultiDevicesSnippet.kt | 245 ++++++++++++++++++ 2 files changed, 291 insertions(+), 3 deletions(-) diff --git a/staticlibs/testutils/host/python/wifip2p_utils.py b/staticlibs/testutils/host/python/wifip2p_utils.py index 8b4ffa505c..ef6af75708 100644 --- a/staticlibs/testutils/host/python/wifip2p_utils.py +++ b/staticlibs/testutils/host/python/wifip2p_utils.py @@ -14,11 +14,13 @@ from mobly import asserts from mobly.controllers import android_device +from net_tests_utils.host.python import tether_utils def assume_wifi_p2p_test_preconditions( server_device: android_device, client_device: android_device ) -> None: + """Preconditions check for running Wi-Fi P2P test.""" server = server_device.connectivity_multi_devices_snippet client = client_device.connectivity_multi_devices_snippet @@ -36,10 +38,51 @@ def assume_wifi_p2p_test_preconditions( def setup_wifi_p2p_server_and_client( server_device: android_device, client_device: android_device ) -> None: - """Set up the Wi-Fi P2P server and client.""" + """Set up the Wi-Fi P2P server and client, then connect them to establish a Wi-Fi P2P connection.""" + server = server_device.connectivity_multi_devices_snippet + client = client_device.connectivity_multi_devices_snippet + # Start Wi-Fi P2P on both server and client. - server_device.connectivity_multi_devices_snippet.startWifiP2p() - client_device.connectivity_multi_devices_snippet.startWifiP2p() + server.startWifiP2p() + client.startWifiP2p() + + # Get the current device name + server_name = server.getDeviceName() + client_name = client.getDeviceName() + + # Generate Wi-Fi P2P group passphrase with random characters. + group_name = "DIRECT-" + tether_utils.generate_uuid32_base64() + group_passphrase = tether_utils.generate_uuid32_base64() + + # Server creates a Wi-Fi P2P group + server.createGroup(group_name, group_passphrase) + + # Start Wi-Fi P2p peers discovery on both devices + server.startPeersDiscovery() + client.startPeersDiscovery() + + # Ensure the target device has been discovered + server_address = client.ensureDeviceDiscovered(server_name) + client_address = server.ensureDeviceDiscovered(client_name) + + # Server invites the device to the group + server.inviteDeviceToGroup(group_name, group_passphrase, client_address) + + # Wait for a p2p connection changed intent to ensure the invitation has been + # received. + client.waitForP2pConnectionChanged(True, group_name) + # Accept the group invitation + client.acceptGroupInvitation(server_address) + + # Server waits for connection request from client and accept joining + server.waitForPeerConnectionRequestAndAcceptJoining(client_address) + + # Wait for a p2p connection changed intent to ensure joining the group + client.waitForP2pConnectionChanged(False, group_name) + + # Ensure Wi-Fi P2P connected on both devices + client.ensureDeviceConnected(server_name) + server.ensureDeviceConnected(client_name) def cleanup_wifi_p2p( diff --git a/tests/cts/multidevices/snippet/Wifip2pMultiDevicesSnippet.kt b/tests/cts/multidevices/snippet/Wifip2pMultiDevicesSnippet.kt index e0929bb47a..f8c9351fc1 100644 --- a/tests/cts/multidevices/snippet/Wifip2pMultiDevicesSnippet.kt +++ b/tests/cts/multidevices/snippet/Wifip2pMultiDevicesSnippet.kt @@ -16,13 +16,28 @@ package com.google.snippet.connectivity +import android.Manifest.permission.MANAGE_WIFI_NETWORK_SELECTION +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.MacAddress import android.net.wifi.WifiManager +import android.net.wifi.p2p.WifiP2pConfig +import android.net.wifi.p2p.WifiP2pDevice +import android.net.wifi.p2p.WifiP2pDeviceList +import android.net.wifi.p2p.WifiP2pGroup import android.net.wifi.p2p.WifiP2pManager import androidx.test.platform.app.InstrumentationRegistry +import com.android.net.module.util.ArrayTrackRecord +import com.android.testutils.runAsShell import com.google.android.mobly.snippet.Snippet import com.google.android.mobly.snippet.rpc.Rpc +import com.google.snippet.connectivity.Wifip2pMultiDevicesSnippet.Wifip2pIntentReceiver.IntentReceivedEvent.ConnectionChanged +import com.google.snippet.connectivity.Wifip2pMultiDevicesSnippet.Wifip2pIntentReceiver.IntentReceivedEvent.PeersChanged import java.util.concurrent.CompletableFuture import java.util.concurrent.TimeUnit +import kotlin.test.assertNotNull import kotlin.test.fail private const val TIMEOUT_MS = 60000L @@ -38,6 +53,35 @@ class Wifip2pMultiDevicesSnippet : Snippet { ?: fail("Could not get WifiP2pManager service") } private lateinit var wifip2pChannel: WifiP2pManager.Channel + private val wifip2pIntentReceiver = Wifip2pIntentReceiver() + + private class Wifip2pIntentReceiver : BroadcastReceiver() { + val history = ArrayTrackRecord().newReadHead() + + sealed class IntentReceivedEvent { + abstract val intent: Intent + data class ConnectionChanged(override val intent: Intent) : IntentReceivedEvent() + data class PeersChanged(override val intent: Intent) : IntentReceivedEvent() + } + + override fun onReceive(context: Context, intent: Intent) { + when (intent.action) { + WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> { + history.add(ConnectionChanged(intent)) + } + WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> { + history.add(PeersChanged(intent)) + } + } + } + + inline fun eventuallyExpectedIntent( + timeoutMs: Long = TIMEOUT_MS, + crossinline predicate: (T) -> Boolean = { true } + ): T = history.poll(timeoutMs) { it is T && predicate(it) }.also { + assertNotNull(it, "Intent ${T::class} not received within ${timeoutMs}ms.") + } as T + } @Rpc(description = "Check whether the device supports Wi-Fi P2P.") fun isP2pSupported() = wifiManager.isP2pSupported() @@ -55,6 +99,10 @@ class Wifip2pMultiDevicesSnippet : Snippet { } } p2pStateEnabledFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS) + // Register an intent filter to receive Wi-Fi P2P intents + val filter = IntentFilter(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION) + filter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION) + context.registerReceiver(wifip2pIntentReceiver, filter) } @Rpc(description = "Stop Wi-Fi P2P") @@ -63,5 +111,202 @@ class Wifip2pMultiDevicesSnippet : Snippet { wifip2pManager.cancelConnect(wifip2pChannel, null) wifip2pManager.removeGroup(wifip2pChannel, null) } + // Unregister the intent filter + context.unregisterReceiver(wifip2pIntentReceiver) + } + + @Rpc(description = "Get the current device name") + fun getDeviceName(): String { + // Retrieve current device info + val deviceFuture = CompletableFuture() + wifip2pManager.requestDeviceInfo(wifip2pChannel) { wifiP2pDevice -> + if (wifiP2pDevice != null) { + deviceFuture.complete(wifiP2pDevice.deviceName) + } + } + // Return current device name + return deviceFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS) + } + + @Rpc(description = "Wait for a p2p connection changed intent and check the group") + @Suppress("DEPRECATION") + fun waitForP2pConnectionChanged(ignoreGroupCheck: Boolean, groupName: String) { + wifip2pIntentReceiver.eventuallyExpectedIntent() { + val p2pGroup: WifiP2pGroup? = + it.intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP) + val groupMatched = p2pGroup?.networkName == groupName + return@eventuallyExpectedIntent ignoreGroupCheck || groupMatched + } + } + + @Rpc(description = "Create a Wi-Fi P2P group") + fun createGroup(groupName: String, groupPassphrase: String) { + // Create a Wi-Fi P2P group + val wifip2pConfig = WifiP2pConfig.Builder() + .setNetworkName(groupName) + .setPassphrase(groupPassphrase) + .build() + val createGroupFuture = CompletableFuture() + wifip2pManager.createGroup( + wifip2pChannel, + wifip2pConfig, + object : WifiP2pManager.ActionListener { + override fun onFailure(reason: Int) = Unit + override fun onSuccess() { createGroupFuture.complete(true) } + } + ) + createGroupFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS) + + // Ensure the Wi-Fi P2P group is created. + waitForP2pConnectionChanged(false, groupName) + } + + @Rpc(description = "Start Wi-Fi P2P peers discovery") + fun startPeersDiscovery() { + // Start discovery Wi-Fi P2P peers + wifip2pManager.discoverPeers(wifip2pChannel, null) + + // Ensure the discovery is started + val p2pDiscoveryStartedFuture = CompletableFuture() + wifip2pManager.requestDiscoveryState(wifip2pChannel) { state -> + if (state == WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED) { + p2pDiscoveryStartedFuture.complete(true) + } + } + p2pDiscoveryStartedFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS) + } + + /** + * Get the device address from the given intent that matches the given device name. + * + * @param peersChangedIntent the intent to get the device address from + * @param deviceName the target device name + * @return the address of the target device or null if no devices match. + */ + @Suppress("DEPRECATION") + private fun getDeviceAddress(peersChangedIntent: Intent, deviceName: String): String? { + val peers: WifiP2pDeviceList? = + peersChangedIntent.getParcelableExtra(WifiP2pManager.EXTRA_P2P_DEVICE_LIST) + return peers?.deviceList?.firstOrNull { it.deviceName == deviceName }?.deviceAddress + } + + /** + * Ensure the given device has been discovered and returns the associated device address for + * connection. + * + * @param deviceName the target device name + * @return the address of the target device. + */ + @Rpc(description = "Ensure the target Wi-Fi P2P device is discovered") + fun ensureDeviceDiscovered(deviceName: String): String { + val changedEvent = wifip2pIntentReceiver.eventuallyExpectedIntent() { + return@eventuallyExpectedIntent getDeviceAddress(it.intent, deviceName) != null + } + return getDeviceAddress(changedEvent.intent, deviceName) + ?: fail("Missing device in filtered intent") + } + + @Rpc(description = "Invite a Wi-Fi P2P device to the group") + fun inviteDeviceToGroup(groupName: String, groupPassphrase: String, deviceAddress: String) { + // Connect to the device to send invitation + val wifip2pConfig = WifiP2pConfig.Builder() + .setNetworkName(groupName) + .setPassphrase(groupPassphrase) + .setDeviceAddress(MacAddress.fromString(deviceAddress)) + .build() + val connectedFuture = CompletableFuture() + wifip2pManager.connect( + wifip2pChannel, + wifip2pConfig, + object : WifiP2pManager.ActionListener { + override fun onFailure(reason: Int) = Unit + override fun onSuccess() { + connectedFuture.complete(true) + } + } + ) + connectedFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS) + } + + private fun runExternalApproverForGroupProcess( + deviceAddress: String, + isGroupInvitation: Boolean + ) { + val peer = MacAddress.fromString(deviceAddress) + runAsShell(MANAGE_WIFI_NETWORK_SELECTION) { + val connectionRequestFuture = CompletableFuture() + val attachedFuture = CompletableFuture() + wifip2pManager.addExternalApprover( + wifip2pChannel, + peer, + object : WifiP2pManager.ExternalApproverRequestListener { + override fun onAttached(deviceAddress: MacAddress) { + attachedFuture.complete(true) + } + override fun onDetached(deviceAddress: MacAddress, reason: Int) = Unit + override fun onConnectionRequested( + requestType: Int, + config: WifiP2pConfig, + device: WifiP2pDevice + ) { + connectionRequestFuture.complete(true) + } + override fun onPinGenerated(deviceAddress: MacAddress, pin: String) = Unit + } + ) + if (isGroupInvitation) attachedFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS) else + connectionRequestFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS) + + val resultFuture = CompletableFuture() + wifip2pManager.setConnectionRequestResult( + wifip2pChannel, + peer, + WifiP2pManager.CONNECTION_REQUEST_ACCEPT, + object : WifiP2pManager.ActionListener { + override fun onFailure(reason: Int) = Unit + override fun onSuccess() { + resultFuture.complete(true) + } + } + ) + resultFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS) + + val removeFuture = CompletableFuture() + wifip2pManager.removeExternalApprover( + wifip2pChannel, + peer, + object : WifiP2pManager.ActionListener { + override fun onFailure(reason: Int) = Unit + override fun onSuccess() { + removeFuture.complete(true) + } + } + ) + removeFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS) + } + } + + @Rpc(description = "Accept P2P group invitation from device") + fun acceptGroupInvitation(deviceAddress: String) { + // Accept the Wi-Fi P2P group invitation + runExternalApproverForGroupProcess(deviceAddress, true /* isGroupInvitation */) + } + + @Rpc(description = "Wait for connection request from the peer and accept joining") + fun waitForPeerConnectionRequestAndAcceptJoining(deviceAddress: String) { + // Wait for connection request from the peer and accept joining + runExternalApproverForGroupProcess(deviceAddress, false /* isGroupInvitation */) + } + + @Rpc(description = "Ensure the target device is connected") + fun ensureDeviceConnected(deviceName: String) { + // Retrieve peers and ensure the target device is connected + val connectedFuture = CompletableFuture() + wifip2pManager.requestPeers(wifip2pChannel) { peers -> peers?.deviceList?.any { + it.deviceName == deviceName && it.status == WifiP2pDevice.CONNECTED }.let { + connectedFuture.complete(true) + } + } + connectedFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS) } } From 5f763b7da7c833745b0e50886b07a2759ec03737 Mon Sep 17 00:00:00 2001 From: Paul Hu Date: Thu, 4 Jul 2024 09:13:00 +0000 Subject: [PATCH 2/2] Add Wi-Fi P2P mdns tests Add tests to verify mdns advertising, discovery, and resolution over a Wi-Fi P2P connection using multiple devices. Bug: 343311941 Test: atest CtsConnectivityMultiDevicesTestCases Change-Id: I3286aeb19769b96a1965c969e52da2118b786c14 --- .../connectivity_multi_devices_test.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/cts/multidevices/connectivity_multi_devices_test.py b/tests/cts/multidevices/connectivity_multi_devices_test.py index eceb5356ef..416a2e8ced 100644 --- a/tests/cts/multidevices/connectivity_multi_devices_test.py +++ b/tests/cts/multidevices/connectivity_multi_devices_test.py @@ -13,6 +13,7 @@ # limitations under the License. from net_tests_utils.host.python import mdns_utils, multi_devices_test_base, tether_utils +from net_tests_utils.host.python import wifip2p_utils from net_tests_utils.host.python.tether_utils import UpstreamType @@ -68,3 +69,21 @@ class ConnectivityMultiDevicesTest( tether_utils.cleanup_tethering_for_upstream_type( self.serverDevice, UpstreamType.NONE ) + + def test_mdns_via_wifip2p(self): + wifip2p_utils.assume_wifi_p2p_test_preconditions( + self.serverDevice, self.clientDevice + ) + mdns_utils.assume_mdns_test_preconditions( + self.clientDevice, self.serverDevice + ) + try: + wifip2p_utils.setup_wifi_p2p_server_and_client( + self.serverDevice, self.clientDevice + ) + mdns_utils.register_mdns_service_and_discover_resolve( + self.clientDevice, self.serverDevice + ) + finally: + mdns_utils.cleanup_mdns_service(self.clientDevice, self.serverDevice) + wifip2p_utils.cleanup_wifi_p2p(self.serverDevice, self.clientDevice)