diff --git a/tests/cts/multidevices/snippet/Android.bp b/tests/cts/multidevices/snippet/Android.bp
index c94087e2e6..9e1878331e 100644
--- a/tests/cts/multidevices/snippet/Android.bp
+++ b/tests/cts/multidevices/snippet/Android.bp
@@ -16,6 +16,20 @@ package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
+java_library {
+ name: "ConnectivityWifiP2pMultiDevicesSnippetLib",
+ srcs: [
+ "Wifip2pMultiDevicesSnippet.kt",
+ ],
+ static_libs: [
+ "mobly-snippet-lib",
+ "cts-net-utils",
+ ],
+ visibility: [
+ "//packages/modules/Connectivity/tests/cts/tethering",
+ ],
+}
+
android_test_helper_app {
name: "connectivity_multi_devices_snippet",
defaults: [
diff --git a/tests/cts/tethering/Android.bp b/tests/cts/tethering/Android.bp
index a1e0797490..38042851a3 100644
--- a/tests/cts/tethering/Android.bp
+++ b/tests/cts/tethering/Android.bp
@@ -30,9 +30,11 @@ java_defaults {
srcs: [
"src/**/*.java",
+ "src/**/*.kt",
],
static_libs: [
+ "ConnectivityWifiP2pMultiDevicesSnippetLib",
"TetheringCommonTests",
"com.android.net.flags-aconfig-java",
"compatibility-device-util-axt",
diff --git a/tests/cts/tethering/AndroidManifest.xml b/tests/cts/tethering/AndroidManifest.xml
index bad722bb0f..aede2d724e 100644
--- a/tests/cts/tethering/AndroidManifest.xml
+++ b/tests/cts/tethering/AndroidManifest.xml
@@ -20,6 +20,9 @@
+
+
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringWifiP2pTest.kt b/tests/cts/tethering/src/android/tethering/cts/TetheringWifiP2pTest.kt
new file mode 100644
index 0000000000..e7e9fdd9f2
--- /dev/null
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringWifiP2pTest.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.
+ */
+
+package android.tethering.cts
+
+import android.Manifest.permission.LOG_COMPAT_CHANGE
+import android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG
+import android.app.compat.CompatChanges
+import android.content.pm.PackageManager
+import android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
+import android.net.connectivity.ConnectivityCompatChanges
+import android.os.Build
+import android.provider.DeviceConfig
+import android.text.TextUtils
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.testutils.AutoReleaseNetworkCallbackRule
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.DeviceConfigRule
+import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
+import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
+import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.runAsShell
+import com.google.snippet.connectivity.Wifip2pMultiDevicesSnippet
+import java.util.Random
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Assume.assumeTrue
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+@RunWith(DevSdkIgnoreRunner::class)
+class TetheringWifiP2pTest {
+ // For manipulating feature flag before and after testing.
+ @get:Rule
+ val deviceConfigRule = DeviceConfigRule()
+
+ @get:Rule
+ val networkCallbackRule = AutoReleaseNetworkCallbackRule()
+
+ companion object {
+ // Shamelessly copied from TetheringConfiguration.
+ private const val TETHERING_LOCAL_NETWORK_AGENT = "tethering_local_network_agent"
+ private const val WIFIP2PGO_LOCAL_NETWORK_AGENT = "wifip2pgo_local_network_agent"
+ }
+
+ private val context = InstrumentationRegistry.getInstrumentation().getTargetContext()
+ private val pm = context.packageManager!!
+
+ private fun assumeMatchNonThreadLocalNetworksEnabled() {
+ assumeTrue(runAsShell(READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE) {
+ CompatChanges.isChangeEnabled(
+ ConnectivityCompatChanges.ENABLE_MATCH_NON_THREAD_LOCAL_NETWORKS
+ )
+ })
+ }
+
+ private fun getRandomString(): String {
+ return "%09d".format(Random().nextInt(1_000_000_000))
+ }
+
+ @Test
+ fun testWifiP2pGoNetworkAgent_networkCallbacks() {
+ // Check preconditions.
+ assumeMatchNonThreadLocalNetworksEnabled()
+ assumeTrue(pm.hasSystemFeature(PackageManager.FEATURE_WIFI))
+ assumeTrue(pm.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT))
+ deviceConfigRule.setConfig(
+ DeviceConfig.NAMESPACE_TETHERING,
+ TETHERING_LOCAL_NETWORK_AGENT,
+ "1"
+ )
+ deviceConfigRule.setConfig(
+ DeviceConfig.NAMESPACE_TETHERING,
+ WIFIP2PGO_LOCAL_NETWORK_AGENT,
+ "1"
+ )
+
+ val cb = TestableNetworkCallback()
+ val request = NetworkRequest.Builder().addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .addTransportType(TRANSPORT_WIFI).build()
+ networkCallbackRule.registerNetworkCallback(request, cb)
+
+ val wifiP2pSnippet = Wifip2pMultiDevicesSnippet()
+ try {
+ wifiP2pSnippet.startWifiP2p()
+ wifiP2pSnippet.createGroup("DIRECT-" + getRandomString(), getRandomString())
+
+ val availableEvent = cb.expect()
+ // Verify Capabilities.
+ val capEvent = cb.eventuallyExpect()
+ assertEquals(availableEvent.network, capEvent.network)
+ assertTrue(capEvent.caps.hasCapability(NET_CAPABILITY_LOCAL_NETWORK))
+ assertTrue(capEvent.caps.hasTransport(TRANSPORT_WIFI))
+
+ // Verify LinkProperties.
+ val lpEvent = cb.eventuallyExpect()
+ assertEquals(capEvent.network, lpEvent.network)
+ assertFalse(TextUtils.isEmpty(lpEvent.lp.interfaceName))
+ // At least 1 Ipv4 address and route.
+ assertTrue(lpEvent.lp.hasIPv4Address())
+ assertTrue(lpEvent.lp.routes.size >= 1)
+ } finally {
+ wifiP2pSnippet.stopWifiP2p()
+ }
+ }
+}