Merge changes I3286aeb1,I060509de into main

* changes:
  Add Wi-Fi P2P mdns tests
  Setup Wi-Fi P2P connection
This commit is contained in:
Paul Hu 2024-08-14 07:14:01 +00:00 • committed by Gerrit Code Review
commit 71bba4e45f
3 changed files with 310 additions and 3 deletions

View file

@ -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(

View file

@ -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)

View file

@ -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<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 @@ 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<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)
}
}