Merge changes I3286aeb1,I060509de into main
* changes: Add Wi-Fi P2P mdns tests Setup Wi-Fi P2P connection
This commit is contained in:
commit
71bba4e45f
3 changed files with 310 additions and 3 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue