Merge changes I3286aeb1,I060509de into main

* changes:
  Add Wi-Fi P2P mdns tests
  Setup Wi-Fi P2P connection
diff --git a/staticlibs/testutils/host/python/wifip2p_utils.py b/staticlibs/testutils/host/python/wifip2p_utils.py
index 8b4ffa5..ef6af75 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 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/connectivity_multi_devices_test.py b/tests/cts/multidevices/connectivity_multi_devices_test.py
index eceb535..416a2e8 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 @@
       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)
diff --git a/tests/cts/multidevices/snippet/Wifip2pMultiDevicesSnippet.kt b/tests/cts/multidevices/snippet/Wifip2pMultiDevicesSnippet.kt
index e0929bb..f8c9351 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 @@
                 ?: fail("Could not get WifiP2pManager service")
     }
     private lateinit var wifip2pChannel: WifiP2pManager.Channel
+    private val wifip2pIntentReceiver = Wifip2pIntentReceiver()
+
+    private class Wifip2pIntentReceiver : BroadcastReceiver() {
+        val history = ArrayTrackRecord<IntentReceivedEvent>().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 <reified T : IntentReceivedEvent> 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 @@
             }
         }
         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 @@
             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<String>()
+        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<ConnectionChanged>() {
+            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<Boolean>()
+        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<Boolean>()
+        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<PeersChanged>() {
+            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<Boolean>()
+        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<Boolean>()
+            val attachedFuture = CompletableFuture<Boolean>()
+            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<Boolean>()
+            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<Boolean>()
+            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<Boolean>()
+        wifip2pManager.requestPeers(wifip2pChannel) { peers -> peers?.deviceList?.any {
+            it.deviceName == deviceName && it.status == WifiP2pDevice.CONNECTED }.let {
+                connectedFuture.complete(true)
+            }
+        }
+        connectedFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
     }
 }