diff options
24 files changed, 1136 insertions, 89 deletions
diff --git a/flags/Android.bp b/flags/Android.bp index 99ba39a590..3052a39aef 100644 --- a/flags/Android.bp +++ b/flags/Android.bp @@ -37,6 +37,7 @@ java_aconfig_library { "//frameworks/opt/net/wifi/libs/WifiTrackerLib:__subpackages__", "//packages/modules/Wifi:__subpackages__", "//cts/tests/tests/wifi:__subpackages__", + "//cts/tests/tests/security", ], } diff --git a/framework/java/android/net/wifi/SoftApConfiguration.java b/framework/java/android/net/wifi/SoftApConfiguration.java index 92efcf72cb..086c816b71 100644 --- a/framework/java/android/net/wifi/SoftApConfiguration.java +++ b/framework/java/android/net/wifi/SoftApConfiguration.java @@ -1426,6 +1426,45 @@ public final class SoftApConfiguration implements Parcelable { mIsClientIsolationEnabled = other.mIsClientIsolationEnabled; } + /** + * Builds the {@link SoftApConfiguration} without any check. + * + * @return A new {@link SoftApConfiguration}, as configured by previous method calls. + * + * @hide + */ + @VisibleForTesting + @NonNull + public SoftApConfiguration buildWithoutCheck() { + return new SoftApConfiguration( + mWifiSsid, + mBssid, + mPassphrase, + mHiddenSsid, + mChannels, + mSecurityType, + mMaxNumberOfClients, + mAutoShutdownEnabled, + mShutdownTimeoutMillis, + mClientControlByUser, + mBlockedClientList, + mAllowedClientList, + mMacRandomizationSetting, + mBridgedModeOpportunisticShutdownEnabled, + mIeee80211axEnabled, + mIeee80211beEnabled, + mIsUserConfiguration, + mBridgedModeOpportunisticShutdownTimeoutMillis, + mVendorElements, + mPersistentRandomizedMacAddress, + mAllowedAcsChannels2g, + mAllowedAcsChannels5g, + mAllowedAcsChannels6g, + mMaxChannelBandwidth, + mVendorData, + mIsClientIsolationEnabled); + } + /** * Builds the {@link SoftApConfiguration}. * @@ -1451,6 +1490,11 @@ public final class SoftApConfiguration implements Parcelable { REMOVE_ZERO_FOR_TIMEOUT_SETTING) && mShutdownTimeoutMillis == DEFAULT_TIMEOUT) { mShutdownTimeoutMillis = 0; // Use 0 for legacy app. } + + if (SdkLevel.isAtLeastB() && !mIeee80211axEnabled) { + // Force 11BE to false since 11ax has dependency with 11AX. + mIeee80211beEnabled = false; + } return new SoftApConfiguration( mWifiSsid, mBssid, diff --git a/framework/java/android/net/wifi/rtt/ResponderConfig.java b/framework/java/android/net/wifi/rtt/ResponderConfig.java index ffa98499ed..7d950eeabd 100644 --- a/framework/java/android/net/wifi/rtt/ResponderConfig.java +++ b/framework/java/android/net/wifi/rtt/ResponderConfig.java @@ -1074,7 +1074,7 @@ public final class ResponderConfig implements Parcelable { if (secureRangingConfig != null) { builder.setSecureRangingConfig(secureRangingConfig); } - return builder.build(); + return new ResponderConfig(builder); } }; diff --git a/framework/tests/src/android/net/wifi/SoftApConfigurationTest.java b/framework/tests/src/android/net/wifi/SoftApConfigurationTest.java index 7e4f5ea310..9938a6a990 100644 --- a/framework/tests/src/android/net/wifi/SoftApConfigurationTest.java +++ b/framework/tests/src/android/net/wifi/SoftApConfigurationTest.java @@ -962,4 +962,21 @@ public class SoftApConfigurationTest { OuiKeyedData unparceledOuiKeyedData = unparceled.getVendorData().get(0); assertEquals(bundle.getInt(fieldKey), unparceledOuiKeyedData.getData().getInt(fieldKey)); } + + @Test + public void testForce11BeToFalseWhen11AxIsFalse() { + assumeTrue(SdkLevel.isAtLeastB()); + // Only 11be is false, it should be ok. + SoftApConfiguration config = new SoftApConfiguration.Builder() + .setIeee80211axEnabled(true) + .setIeee80211beEnabled(false).build(); + assertTrue(config.isIeee80211axEnabled()); + assertFalse(config.isIeee80211beEnabled()); + // Only 11ax is false, 11be should force to false too. + config = new SoftApConfiguration.Builder() + .setIeee80211axEnabled(false) + .setIeee80211beEnabled(true).build(); + assertFalse(config.isIeee80211axEnabled()); + assertFalse(config.isIeee80211beEnabled()); + } } diff --git a/service/java/com/android/server/wifi/HalDeviceManager.java b/service/java/com/android/server/wifi/HalDeviceManager.java index 227ca28f13..9a0bf0ae64 100644 --- a/service/java/com/android/server/wifi/HalDeviceManager.java +++ b/service/java/com/android/server/wifi/HalDeviceManager.java @@ -1584,7 +1584,7 @@ public class HalDeviceManager { @Override public void onSubsystemRestart(int status) { Log.i(TAG, "onSubsystemRestart"); - mEventHandler.post(() -> { + mEventHandler.postAtFrontOfQueue(() -> { Log.i(TAG, "IWifiEventCallback.onSubsystemRestart. Status: " + status); synchronized (mLock) { Log.i(TAG, "Attempting to invoke mSubsystemRestartListener"); diff --git a/service/java/com/android/server/wifi/WifiApConfigStore.java b/service/java/com/android/server/wifi/WifiApConfigStore.java index c4a22f52ad..efe527a016 100644 --- a/service/java/com/android/server/wifi/WifiApConfigStore.java +++ b/service/java/com/android/server/wifi/WifiApConfigStore.java @@ -733,7 +733,7 @@ public class WifiApConfigStore { } // Hostapd requires 11AX to configure 11BE - if (SdkLevel.isAtLeastT() && apConfig.isIeee80211beEnabled() + if (SdkLevel.isAtLeastB() && apConfig.isIeee80211beEnabled() && !apConfig.isIeee80211axEnabledInternal()) { Log.d(TAG, "11AX is required when configuring 11BE"); return false; diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java index 15b7c508d3..300f850d51 100644 --- a/service/java/com/android/server/wifi/WifiInjector.java +++ b/service/java/com/android/server/wifi/WifiInjector.java @@ -356,7 +356,7 @@ public class WifiInjector { mSupplicantStaIfaceHal = new SupplicantStaIfaceHal( mContext, mWifiMonitor, mFrameworkFacade, mWifiHandler, mClock, mWifiMetrics, mWifiGlobals, mSsidTranslator, this); - mMainlineSupplicant = new MainlineSupplicant(); + mMainlineSupplicant = new MainlineSupplicant(mWifiThreadRunner); mHostapdHal = new HostapdHal(mContext, mWifiHandler); mWifiCondManager = (WifiNl80211Manager) mContext.getSystemService( Context.WIFI_NL80211_SERVICE); diff --git a/service/java/com/android/server/wifi/WifiShellCommand.java b/service/java/com/android/server/wifi/WifiShellCommand.java index aad4de03ca..bd49b590ab 100644 --- a/service/java/com/android/server/wifi/WifiShellCommand.java +++ b/service/java/com/android/server/wifi/WifiShellCommand.java @@ -1256,6 +1256,10 @@ public class WifiShellCommand extends BasicShellCommandHandler { new ParceledListSlice<>(Collections.emptyList()), SHELL_PACKAGE_NAME, WifiManager.ACTION_REMOVE_SUGGESTION_DISCONNECT); return 0; + case "clear-all-suggestions": + mWifiThreadRunner.post(() -> mWifiNetworkSuggestionsManager.clear(), + "shell#clear-all-suggestions"); + return 0; case "list-suggestions": { List<WifiNetworkSuggestion> suggestions = mWifiService.getNetworkSuggestions(SHELL_PACKAGE_NAME).getList(); @@ -3268,6 +3272,8 @@ public class WifiShellCommand extends BasicShellCommandHandler { pw.println(" Lists all suggested networks on this device"); pw.println(" list-suggestions-from-app <package name>"); pw.println(" Lists the suggested networks from the app"); + pw.println(" clear-all-suggestions"); + pw.println(" Clear all suggestions added into this device"); pw.println(" set-emergency-callback-mode enabled|disabled"); pw.println(" Sets whether Emergency Callback Mode (ECBM) is enabled."); pw.println(" Equivalent to receiving the " @@ -3442,15 +3448,36 @@ public class WifiShellCommand extends BasicShellCommandHandler { if (suggestions == null || suggestions.isEmpty()) { pw.println("No suggestions on this device"); } else { - pw.println("SSID Security type(s)"); - for (WifiNetworkSuggestion suggestion : suggestions) { - pw.println(String.format("%-32s %-4s", - WifiInfo.sanitizeSsid(suggestion.getWifiConfiguration().SSID), - suggestion.getWifiConfiguration().getSecurityParamsList().stream() - .map(p -> WifiConfiguration.getSecurityTypeName( - p.getSecurityType()) - + (p.isAddedByAutoUpgrade() ? "^" : "")) - .collect(Collectors.joining("/")))); + if (SdkLevel.isAtLeastS()) { + /* + * Print out SubId on S and above because WifiNetworkSuggestion.getSubscriptionId() + * is supported from Android S and above. + */ + String format = "%-24s %-24s %-12s %-12s"; + pw.println(String.format(format, "SSID", "Security type(s)", "CarrierId", "SubId")); + for (WifiNetworkSuggestion suggestion : suggestions) { + pw.println(String.format(format, + WifiInfo.sanitizeSsid(suggestion.getWifiConfiguration().SSID), + suggestion.getWifiConfiguration().getSecurityParamsList().stream() + .map(p -> WifiConfiguration.getSecurityTypeName( + p.getSecurityType()) + + (p.isAddedByAutoUpgrade() ? "^" : "")) + .collect(Collectors.joining("/")), + suggestion.getCarrierId(), suggestion.getSubscriptionId())); + } + } else { + String format = "%-24s %-24s %-12s"; + pw.println(String.format(format, "SSID", "Security type(s)", "CarrierId")); + for (WifiNetworkSuggestion suggestion : suggestions) { + pw.println(String.format(format, + WifiInfo.sanitizeSsid(suggestion.getWifiConfiguration().SSID), + suggestion.getWifiConfiguration().getSecurityParamsList().stream() + .map(p -> WifiConfiguration.getSecurityTypeName( + p.getSecurityType()) + + (p.isAddedByAutoUpgrade() ? "^" : "")) + .collect(Collectors.joining("/")), + suggestion.getCarrierId())); + } } } } diff --git a/service/java/com/android/server/wifi/mainline_supplicant/MainlineSupplicant.java b/service/java/com/android/server/wifi/mainline_supplicant/MainlineSupplicant.java index bc87d018b8..6310577077 100644 --- a/service/java/com/android/server/wifi/mainline_supplicant/MainlineSupplicant.java +++ b/service/java/com/android/server/wifi/mainline_supplicant/MainlineSupplicant.java @@ -24,10 +24,12 @@ import android.os.RemoteException; import android.os.ServiceSpecificException; import android.system.wifi.mainline_supplicant.IMainlineSupplicant; import android.system.wifi.mainline_supplicant.IStaInterface; +import android.system.wifi.mainline_supplicant.IStaInterfaceCallback; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.server.wifi.WifiNative; +import com.android.server.wifi.WifiThreadRunner; import com.android.wifi.flags.Flags; import java.util.HashMap; @@ -48,13 +50,16 @@ public class MainlineSupplicant { private IMainlineSupplicant mIMainlineSupplicant; private final Object mLock = new Object(); + private final WifiThreadRunner mWifiThreadRunner; private SupplicantDeathRecipient mServiceDeathRecipient; private WifiNative.SupplicantDeathEventHandler mFrameworkDeathHandler; private CountDownLatch mWaitForDeathLatch; private final boolean mIsServiceAvailable; private Map<String, IStaInterface> mActiveStaIfaces = new HashMap<>(); + private Map<String, IStaInterfaceCallback> mStaIfaceCallbacks = new HashMap<>(); - public MainlineSupplicant() { + public MainlineSupplicant(@NonNull WifiThreadRunner wifiThreadRunner) { + mWifiThreadRunner = wifiThreadRunner; mServiceDeathRecipient = new SupplicantDeathRecipient(); mIsServiceAvailable = canServiceBeAccessed(); } @@ -125,6 +130,7 @@ public class MainlineSupplicant { synchronized (mLock) { mIMainlineSupplicant = null; mActiveStaIfaces.clear(); + mStaIfaceCallbacks.clear(); } } @@ -195,7 +201,15 @@ public class MainlineSupplicant { try { IStaInterface staIface = mIMainlineSupplicant.addStaInterface(ifaceName); + IStaInterfaceCallback callback = new MainlineSupplicantStaIfaceCallback( + this, ifaceName, mWifiThreadRunner); + if (!registerStaIfaceCallback(staIface, callback)) { + Log.i(TAG, "Unable to register callback with interface " + ifaceName); + return false; + } mActiveStaIfaces.put(ifaceName, staIface); + // Keep callback in a store to avoid recycling by the garbage collector + mStaIfaceCallbacks.put(ifaceName, callback); Log.i(TAG, "Added STA interface " + ifaceName); return true; } catch (ServiceSpecificException e) { @@ -230,6 +244,7 @@ public class MainlineSupplicant { try { mIMainlineSupplicant.removeStaInterface(ifaceName); mActiveStaIfaces.remove(ifaceName); + mStaIfaceCallbacks.remove(ifaceName); Log.i(TAG, "Removed STA interface " + ifaceName); return true; } catch (ServiceSpecificException e) { @@ -242,6 +257,30 @@ public class MainlineSupplicant { } /** + * Register a callback with the provided STA interface. + * + * @return true if the registration was successful, false otherwise. + */ + private boolean registerStaIfaceCallback(@NonNull IStaInterface iface, + @NonNull IStaInterfaceCallback callback) { + synchronized (mLock) { + final String methodName = "registerStaIfaceCallback"; + if (iface == null || callback == null) { + return false; + } + try { + iface.registerCallback(callback); + return true; + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodName); + } catch (RemoteException e) { + handleRemoteException(e, methodName); + } + return false; + } + } + + /** * Stop the mainline supplicant process. */ public void stopService() { diff --git a/service/java/com/android/server/wifi/mainline_supplicant/MainlineSupplicantStaIfaceCallback.java b/service/java/com/android/server/wifi/mainline_supplicant/MainlineSupplicantStaIfaceCallback.java index 0f7510194a..20cda62861 100644 --- a/service/java/com/android/server/wifi/mainline_supplicant/MainlineSupplicantStaIfaceCallback.java +++ b/service/java/com/android/server/wifi/mainline_supplicant/MainlineSupplicantStaIfaceCallback.java @@ -17,23 +17,24 @@ package com.android.server.wifi.mainline_supplicant; import android.annotation.NonNull; -import android.os.Handler; import android.system.wifi.mainline_supplicant.IStaInterfaceCallback; import android.system.wifi.mainline_supplicant.UsdMessageInfo; +import com.android.server.wifi.WifiThreadRunner; + /** * Implementation of the mainline supplicant {@link IStaInterfaceCallback}. */ public class MainlineSupplicantStaIfaceCallback extends IStaInterfaceCallback.Stub { private final MainlineSupplicant mMainlineSupplicant; private final String mIfaceName; - private final Handler mHandler; + private final WifiThreadRunner mWifiThreadRunner; MainlineSupplicantStaIfaceCallback(@NonNull MainlineSupplicant mainlineSupplicant, - @NonNull String ifaceName, @NonNull Handler handler) { + @NonNull String ifaceName, @NonNull WifiThreadRunner wifiThreadRunner) { mMainlineSupplicant = mainlineSupplicant; mIfaceName = ifaceName; - mHandler = handler; + mWifiThreadRunner = wifiThreadRunner; } /** diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java index c939a44181..b7b86871d0 100644 --- a/service/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java +++ b/service/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java @@ -948,13 +948,13 @@ public class WifiApConfigStoreTest extends WifiBaseTest { @Test public void test11BERequires11AXConfigInValidateApWifiConfigurationCheck() { - assumeTrue(SdkLevel.isAtLeastT()); + assumeTrue(SdkLevel.isAtLeastB()); assertFalse(WifiApConfigStore.validateApWifiConfiguration( new SoftApConfiguration.Builder() .setSsid(TEST_DEFAULT_HOTSPOT_SSID) .setIeee80211axEnabled(false) .setIeee80211beEnabled(true) - .build(), true, mContext, mWifiNative)); + .buildWithoutCheck(), true, mContext, mWifiNative)); } /** diff --git a/service/tests/wifitests/src/com/android/server/wifi/mainline_supplicant/MainlineSupplicantTest.java b/service/tests/wifitests/src/com/android/server/wifi/mainline_supplicant/MainlineSupplicantTest.java index 7094446810..d1c25a5078 100644 --- a/service/tests/wifitests/src/com/android/server/wifi/mainline_supplicant/MainlineSupplicantTest.java +++ b/service/tests/wifitests/src/com/android/server/wifi/mainline_supplicant/MainlineSupplicantTest.java @@ -28,11 +28,14 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.net.wifi.util.Environment; +import android.os.Handler; import android.os.IBinder; +import android.os.test.TestLooper; import android.system.wifi.mainline_supplicant.IMainlineSupplicant; import android.system.wifi.mainline_supplicant.IStaInterface; import com.android.server.wifi.WifiNative; +import com.android.server.wifi.WifiThreadRunner; import org.junit.Before; import org.junit.Test; @@ -51,6 +54,7 @@ public class MainlineSupplicantTest { private @Mock WifiNative.SupplicantDeathEventHandler mFrameworkDeathHandler; private @Mock IStaInterface mIStaInterface; private MainlineSupplicantSpy mDut; + private TestLooper mLooper = new TestLooper(); private ArgumentCaptor<IBinder.DeathRecipient> mDeathRecipientCaptor = ArgumentCaptor.forClass(IBinder.DeathRecipient.class); @@ -58,7 +62,7 @@ public class MainlineSupplicantTest { // Spy version of this class allows us to override methods for testing. private class MainlineSupplicantSpy extends MainlineSupplicant { MainlineSupplicantSpy() { - super(); + super(new WifiThreadRunner(new Handler(mLooper.getLooper()))); } @Override diff --git a/tests/hostsidetests/multidevices/com.google.snippet.wifi/direct/WifiP2pManagerSnippet.java b/tests/hostsidetests/multidevices/com.google.snippet.wifi/direct/WifiP2pManagerSnippet.java index 0a82ca4bac..3bf57c246a 100644 --- a/tests/hostsidetests/multidevices/com.google.snippet.wifi/direct/WifiP2pManagerSnippet.java +++ b/tests/hostsidetests/multidevices/com.google.snippet.wifi/direct/WifiP2pManagerSnippet.java @@ -400,6 +400,52 @@ public class WifiP2pManagerSnippet implements Snippet { } /** + * Get p2p connect PIN code after calling {@link #wifiP2pConnect(JSONObject,Integer)} with + * WPS PIN. + * + * @param deviceName The name of the device to connect. + * @return The generated PIN as a String. + * @throws Throwable If failed to get PIN code. + */ + @Rpc(description = "Get p2p connect PIN code after calling wifiP2pConnect with WPS PIN.") + public String wifiP2pGetKeypadPinCode(String deviceName) throws Throwable { + // Wait for the 'Invitation sent' dialog to appear + if (!mUiDevice.wait(Until.hasObject(By.text("Invitation to connect")), + UI_ACTION_LONG_TIMEOUT_MS)) { + throw new WifiP2pManagerException( + "Invitation sent dialog did not appear within timeout."); + } + if (!mUiDevice.wait(Until.hasObject(By.text(deviceName)), UI_ACTION_SHORT_TIMEOUT_MS)) { + throw new WifiP2pManagerException( + "The connect invitation is not triggered by expected peer device."); + } + // Find the UI lement with text='PIN:' + UiObject2 pinLabel = mUiDevice.findObject(By.text("PIN:")); + if (pinLabel == null) { + throw new WifiP2pManagerException("PIN label not found."); + } + Log.d("pinLabel = " + pinLabel); + // Get the sibling UI element that contains the PIN code. Use regex pattern "\d+" as PIN + // code must be composed entirely of numbers. + Pattern pattern = Pattern.compile("\\d+"); + UiObject2 pinValue = pinLabel.getParent().findObject(By.text(pattern)); + if (pinValue == null) { + throw new WifiP2pManagerException("Failed to find Pin code UI element."); + } + String pinCode = pinValue.getText(); + Log.d("Retrieved PIN code: " + pinCode); + // Click 'OK' to close the PIN code alert + UiObject2 okButton = mUiDevice.findObject(By.text("Accept").clazz(Button.class)); + if (okButton == null) { + throw new WifiP2pManagerException( + "OK button not found in the p2p connection invitation pop-up window."); + } + okButton.click(); + Log.d("Closed the p2p connect invitation pop-up window."); + return pinCode; + } + + /** * Enters the given PIN code to accept a P2P connection invitation. * * @param pinCode The PIN to enter. diff --git a/tests/hostsidetests/multidevices/test/Android.bp b/tests/hostsidetests/multidevices/test/Android.bp index 2adbfb5739..3d3ef73ee5 100644 --- a/tests/hostsidetests/multidevices/test/Android.bp +++ b/tests/hostsidetests/multidevices/test/Android.bp @@ -118,14 +118,14 @@ python_test_host { } python_test_host { - name: "CtsWifiDirectTests", - main: "direct/cts_wifi_direct_test_suite.py", + name: "WifiDirectTests", + main: "direct/wifi_direct_test_suite.py", srcs: [ "direct/group_owner_negotiation_test.py", "direct/group_owner_test.py", "direct/group_owner_with_config_test.py", "direct/service_discovery_test.py", - "direct/cts_wifi_direct_test_suite.py", + "direct/wifi_direct_test_suite.py", ], test_config: "direct/AndroidTest.xml", libs: [ @@ -142,7 +142,6 @@ python_test_host { }, test_suites: [ "general-tests", - "cts-v-host", ], } diff --git a/tests/hostsidetests/multidevices/test/aware/AndroidTestNew.xml b/tests/hostsidetests/multidevices/test/aware/AndroidTestNew.xml index 244257efec..69de107885 100644 --- a/tests/hostsidetests/multidevices/test/aware/AndroidTestNew.xml +++ b/tests/hostsidetests/multidevices/test/aware/AndroidTestNew.xml @@ -21,18 +21,12 @@ <target_preparer class="AndroidDeviceFeaturesCheckDecorator"> <option name="required_feature" value="android.hardware.wifi.aware" /> </target_preparer> - <target_preparer class="AndroidMainlineModulesCheckDecorator"> - <option name="mainline_module_package_name" value="com.google.android.wifi" /> - </target_preparer> <target_preparer class="AndroidInstallAppsDecorator" /> </device> <device name="AndroidDevice"> <target_preparer class="AndroidDeviceFeaturesCheckDecorator"> <option name="required_feature" value="android.hardware.wifi.aware" /> </target_preparer> - <target_preparer class="AndroidMainlineModulesCheckDecorator"> - <option name="mainline_module_package_name" value="com.google.android.wifi" /> - </target_preparer> <target_preparer class="AndroidInstallAppsDecorator" /> </device> diff --git a/tests/hostsidetests/multidevices/test/aware/wifi_aware_discovery_ranging_test.py b/tests/hostsidetests/multidevices/test/aware/wifi_aware_discovery_ranging_test.py index 6e9a7b77d5..6951f36e5b 100644 --- a/tests/hostsidetests/multidevices/test/aware/wifi_aware_discovery_ranging_test.py +++ b/tests/hostsidetests/multidevices/test/aware/wifi_aware_discovery_ranging_test.py @@ -92,12 +92,16 @@ class WifiAwareDiscoveryRangingTest(base_test.BaseTestClass): self.publisher = self.ads[0] self.subscriber = self.ads[1] - def setup_device(device: android_device.AndroidDevice): - device.load_snippet('wifi', _SNIPPET_PACKAGE_NAME) - device.wifi.wifiEnable() - wifi_test_utils.set_screen_on_and_unlock(device) - wifi_test_utils.enable_wifi_verbose_logging(device) - # Device capability check + # Device setup + utils.concurrent_exec( + self._setup_device, + ((self.publisher,), (self.subscriber,)), + max_workers=2, + raise_on_exception=True, + ) + + # Device capability check + for device in [self.publisher, self.subscriber]: asserts.abort_class_if( not device.wifi.wifiAwareIsSupported(), f'{device} does not support Wi-Fi Aware.', @@ -111,12 +115,11 @@ class WifiAwareDiscoveryRangingTest(base_test.BaseTestClass): f'{device} does not support Wi-Fi RTT.', ) - utils.concurrent_exec( - setup_device, - ((self.publisher,), (self.subscriber,)), - max_workers=2, - raise_on_exception=True, - ) + def _setup_device(self, device: android_device.AndroidDevice): + device.load_snippet('wifi', _SNIPPET_PACKAGE_NAME) + device.wifi.wifiEnable() + wifi_test_utils.set_screen_on_and_unlock(device) + wifi_test_utils.enable_wifi_verbose_logging(device) def test_discovery_ranging_to_peer_handle(self) -> None: """Test ranging to a Wi-Fi Aware peer handle. diff --git a/tests/hostsidetests/multidevices/test/aware/wifi_aware_network_test.py b/tests/hostsidetests/multidevices/test/aware/wifi_aware_network_test.py index f711b6dccc..c0d406603e 100644 --- a/tests/hostsidetests/multidevices/test/aware/wifi_aware_network_test.py +++ b/tests/hostsidetests/multidevices/test/aware/wifi_aware_network_test.py @@ -81,27 +81,30 @@ class WifiAwareNetworkTest(base_test.BaseTestClass): self.publisher = self.ads[0] self.subscriber = self.ads[1] - def setup_device(device: android_device.AndroidDevice): - device.load_snippet('wifi', _SNIPPET_PACKAGE_NAME) - device.wifi.wifiEnable() - wifi_test_utils.set_screen_on_and_unlock(device) - wifi_test_utils.enable_wifi_verbose_logging(device) - # Device capability check + # Device setup + utils.concurrent_exec( + self._setup_device, + ((self.publisher,), (self.subscriber,)), + max_workers=2, + raise_on_exception=True, + ) + + # Device capability check + for device in [self.publisher, self.subscriber]: asserts.abort_class_if( not device.wifi.wifiAwareIsSupported(), f'{device} does not support Wi-Fi Aware.', ) asserts.abort_class_if( not device.wifi.wifiAwareIsAvailable(), - f'{device} Wi-Fi Aware is not available.', + f'Wi-Fi Aware is not available on {device}.', ) - utils.concurrent_exec( - setup_device, - ((self.publisher,), (self.subscriber,)), - max_workers=2, - raise_on_exception=True, - ) + def _setup_device(self, device: android_device.AndroidDevice): + device.load_snippet('wifi', _SNIPPET_PACKAGE_NAME) + device.wifi.wifiEnable() + wifi_test_utils.set_screen_on_and_unlock(device) + wifi_test_utils.enable_wifi_verbose_logging(device) @ApiTest( apis=[ diff --git a/tests/hostsidetests/multidevices/test/direct/AndroidTest.xml b/tests/hostsidetests/multidevices/test/direct/AndroidTest.xml index 63f3af4ada..6da3197442 100644 --- a/tests/hostsidetests/multidevices/test/direct/AndroidTest.xml +++ b/tests/hostsidetests/multidevices/test/direct/AndroidTest.xml @@ -10,7 +10,7 @@ See the License for the specific language governing permissions and limitations under the License. --> -<configuration description="Test config for Wi-Fi Direct multi-device CTS tests"> +<configuration description="Test config for Wi-Fi Direct multi-device tests"> <option name="test-suite-tag" value="cts-v-host" /> <option name="config-descriptor:metadata" key="component" value="wifi" /> <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" /> @@ -21,23 +21,17 @@ <target_preparer class="AndroidDeviceFeaturesCheckDecorator"> <option name="required_feature" value="android.hardware.wifi.direct" /> </target_preparer> - <target_preparer class="AndroidMainlineModulesCheckDecorator"> - <option name="mainline_module_package_name" value="com.google.android.wifi" /> - </target_preparer> <target_preparer class="AndroidInstallAppsDecorator" /> </device> <device name="AndroidDevice"> <target_preparer class="AndroidDeviceFeaturesCheckDecorator"> <option name="required_feature" value="android.hardware.wifi.direct" /> </target_preparer> - <target_preparer class="AndroidMainlineModulesCheckDecorator"> - <option name="mainline_module_package_name" value="com.google.android.wifi" /> - </target_preparer> <target_preparer class="AndroidInstallAppsDecorator" /> </device> <test class="MoblyAospPackageTest" /> - <option name="mobly_pkg" key="file" value="CtsWifiDirectTests" /> + <option name="mobly_pkg" key="file" value="WifiDirectTests" /> <option name="build_apk" key="file" value="wifi_mobly_snippet.apk" /> </configuration>
\ No newline at end of file diff --git a/tests/hostsidetests/multidevices/test/direct/integration/Android.bp b/tests/hostsidetests/multidevices/test/direct/integration/Android.bp index 95d120633a..6ee454d34a 100644 --- a/tests/hostsidetests/multidevices/test/direct/integration/Android.bp +++ b/tests/hostsidetests/multidevices/test/direct/integration/Android.bp @@ -43,3 +43,30 @@ python_test_host { }, test_suites: ["general-tests"], } + +python_test_host { + name: "WifiP2pManagerTestCases", + main: "wifi_p2p_manager_test.py", + srcs: [ + "wifi_p2p_manager_test.py", + ], + libs: [ + "mobly", + "wifi_direct_constants", + "wifi_direct_test_utils", + "wifi_p2p_lib", + "wifi_test_utils", + "platform-test-py-annotations", + ], + device_common_data: [":wifi_mobly_snippet"], + test_options: { + unit_test: false, + tags: ["mobly"], + }, + test_suites: ["general-tests"], + version: { + py3: { + embedded_launcher: true, + }, + }, +} diff --git a/tests/hostsidetests/multidevices/test/direct/integration/wifi_p2p_lib.py b/tests/hostsidetests/multidevices/test/direct/integration/wifi_p2p_lib.py index 566145c651..c6a50e6719 100644 --- a/tests/hostsidetests/multidevices/test/direct/integration/wifi_p2p_lib.py +++ b/tests/hostsidetests/multidevices/test/direct/integration/wifi_p2p_lib.py @@ -27,6 +27,7 @@ # Lint as: python3 +from collections.abc import Sequence import datetime import time @@ -35,19 +36,74 @@ from direct import p2p_utils from mobly import asserts from mobly.controllers import android_device from mobly.controllers.android_device_lib import adb +from mobly.controllers.android_device_lib import callback_handler_v2 +from mobly.snippet import errors _DEFAULT_TIMEOUT = datetime.timedelta(seconds=30) _DEFAULT_SLEEPTIME = 5 +_DEFAULT_FUNCTION_SWITCH_TIME = 10 +_DEFAULT_SERVICE_WAITING_TIME = 20 +_NORMAL_TIMEOUT = datetime.timedelta(seconds=20) P2P_CONNECT_NEGOTIATION = 0 P2P_CONNECT_JOIN = 1 P2P_CONNECT_INVITATION = 2 +###################################################### +# Wifi P2p local service type +#################################################### +P2P_LOCAL_SERVICE_UPNP = 0 +P2P_LOCAL_SERVICE_IPP = 1 +P2P_LOCAL_SERVICE_AFP = 2 + +###################################################### +# Wifi P2p local service event +#################################################### + +DNSSD_EVENT = 'WifiP2pOnDnsSdServiceAvailable' +DNSSD_TXRECORD_EVENT = 'WifiP2pOnDnsSdTxtRecordAvailable' +UPNP_EVENT = 'WifiP2pOnUpnpServiceAvailable' + +DNSSD_EVENT_INSTANCENAME_KEY = 'InstanceName' +DNSSD_EVENT_REGISTRATIONTYPE_KEY = 'RegistrationType' +DNSSD_TXRECORD_EVENT_FULLDOMAINNAME_KEY = 'FullDomainName' +DNSSD_TXRECORD_EVENT_TXRECORDMAP_KEY = 'TxtRecordMap' +UPNP_EVENT_SERVICELIST_KEY = 'ServiceList' + + +###################################################### +# Wifi P2p UPnP MediaRenderer local service +###################################################### +class UpnpTestData(): + av_transport = 'urn:schemas-upnp-org:service:AVTransport:1' + connection_manager = 'urn:schemas-upnp-org:service:ConnectionManager:1' + service_type = 'urn:schemas-upnp-org:device:MediaRenderer:1' + uuid = '6859dede-8574-59ab-9332-123456789011' + rootdevice = 'upnp:rootdevice' + + +###################################################### +# Wifi P2p Bonjour IPP & AFP local service +###################################################### +class IppTestData(): + ipp_instance_name = 'MyPrinter' + ipp_registration_type = '_ipp._tcp' + ipp_domain_name = 'myprinter._ipp._tcp.local.' + ipp_txt_record = {'txtvers': '1', 'pdl': 'application/postscript'} + + +class AfpTestData(): + afp_instance_name = 'Example' + afp_registration_type = '_afpovertcp._tcp' + afp_domain_name = 'example._afpovertcp._tcp.local.' + afp_txt_record = {} + # Trigger p2p connect to device_go from device_gc. def p2p_connect( device_gc: p2p_utils.DeviceState, device_go: p2p_utils.DeviceState, + is_reconnect, wps_setup, p2p_connect_type=P2P_CONNECT_NEGOTIATION, go_ad=None, @@ -57,15 +113,21 @@ def p2p_connect( Args: device_gc: The android device (Client) device_go: The android device (GO) + is_reconnect: boolean, if persist group is exist, is_reconnect is true, + otherswise is false. wps_setup: which wps connection would like to use p2p_connect_type: enumeration, which type this p2p connection is go_ad: The group owner android device which is used for the invitation connection """ device_gc.ad.log.info( - 'Create p2p connection from %s to %s via wps: %s type %d' - % (device_gc.ad.serial, device_go.ad.serial, wps_setup, p2p_connect_type) + 'Create p2p connection from %s to %s via wps: %s type %d', + device_gc.ad.serial, + device_go.ad.serial, + wps_setup, + p2p_connect_type, ) + if p2p_connect_type == P2P_CONNECT_INVITATION: if go_ad is None: go_ad = device_gc @@ -77,11 +139,11 @@ def p2p_connect( elif p2p_connect_type == P2P_CONNECT_JOIN: peer_p2p_device = p2p_utils.discover_group_owner( client=device_gc, - group_owner_address=device_go.p2p_device.device_address + group_owner_address=device_go.p2p_device.device_address, ) asserts.assert_true( peer_p2p_device.is_group_owner, - f"P2p device {peer_p2p_device} should be group owner.", + f'P2p device {peer_p2p_device} should be group owner.', ) else: p2p_utils.discover_p2p_peer(device_gc, device_go) @@ -95,7 +157,10 @@ def p2p_connect( device_address=device_go.p2p_device.device_address, wps_setup=wps_setup, ) - p2p_utils.p2p_connect(device_gc, device_go, p2p_config) + if not is_reconnect: + p2p_utils.p2p_connect(device_gc, device_go, p2p_config) + else: + p2p_utils.p2p_reconnect(device_gc, device_go, p2p_config) def is_go(ad): @@ -127,15 +192,14 @@ def p2p_go_ip(ad): Returns: GO IP address """ - ad.log.info('p2p go ip') event_handler = ad.wifi.wifiP2pRequestConnectionInfo() - ad.log.info(type(event_handler)) result = event_handler.waitAndGet( event_name=constants.ON_CONNECTION_INFO_AVAILABLE, timeout=_DEFAULT_TIMEOUT.total_seconds(), ) + go_flag = result.data['isGroupOwner'] ip = result.data['groupOwnerHostAddress'].replace('/', '') - ad.log.info('p2p go ip: %s' % ip) + ad.log.info('is_go:%s, p2p ip: %s', go_flag, ip) return ip @@ -175,6 +239,474 @@ def p2p_connection_ping_test(dut: android_device.AndroidDevice, peer_ip: str): dut.log.info(results) +def gen_test_data(service_category): + """Based on service category to generator Test Data. + + Args: + service_category: P2p local service type, Upnp or Bonjour + + Returns: + TestData + """ + test_data = [] + if service_category == P2P_LOCAL_SERVICE_UPNP: + test_data.append(UpnpTestData.uuid) + test_data.append(UpnpTestData.service_type) + test_data.append( + [UpnpTestData.av_transport, UpnpTestData.connection_manager] + ) + elif service_category == P2P_LOCAL_SERVICE_IPP: + test_data.append(IppTestData.ipp_instance_name) + test_data.append(IppTestData.ipp_registration_type) + test_data.append(IppTestData.ipp_txt_record) + elif service_category == P2P_LOCAL_SERVICE_AFP: + test_data.append(AfpTestData.afp_instance_name) + test_data.append(AfpTestData.afp_registration_type) + test_data.append(AfpTestData.afp_txt_record) + + return test_data + + +def create_p2p_local_service(ad, service_category): + """Based on service_category to create p2p local service on an Android device ad. + + Args: + ad: The android device + service_category: p2p local service type, UPNP / IPP / AFP, + """ + test_data = gen_test_data(service_category) + ad.log.info( + 'LocalService = %s, %s, %s', test_data[0], test_data[1], test_data[2] + ) + if service_category == P2P_LOCAL_SERVICE_UPNP: + ad.wifi.wifiP2pAddUpnpLocalService(test_data[0], test_data[1], test_data[2]) + elif ( + service_category == P2P_LOCAL_SERVICE_IPP + or service_category == P2P_LOCAL_SERVICE_AFP + ): + ad.wifi.wifiP2pAddBonjourLocalService( + test_data[0], test_data[1], test_data[2] + ) + + +def gen_expect_test_data(service_type, query_string1, query_string2): + """Based on serviceCategory to generator expect serviceList. + + Args: + service_type: P2p local service type, Upnp or Bonjour + query_string1: Query String, NonNull + query_string2: Query String, used for Bonjour, Nullable + + Returns: + expect_service_list: expect data generated. + """ + expect_service_list = {} + if ( + service_type + == WifiP2PEnums.WifiP2pServiceInfo.WIFI_P2P_SERVICE_TYPE_BONJOUR + ): + ipp_service = WifiP2PEnums.WifiP2pDnsSdServiceResponse() + afp_service = WifiP2PEnums.WifiP2pDnsSdServiceResponse() + if query_string1 == IppTestData.ipp_registration_type: + if query_string2 == IppTestData.ipp_instance_name: + ipp_service.instance_name = '' + ipp_service.registration_type = '' + ipp_service.full_domain_name = IppTestData.ipp_domain_name + ipp_service.txt_record_map = IppTestData.ipp_txt_record + expect_service_list[ipp_service.to_string()] = 1 + return expect_service_list + ipp_service.instance_name = IppTestData.ipp_instance_name + ipp_service.registration_type = ( + IppTestData.ipp_registration_type + '.local.' + ) + ipp_service.full_domain_name = '' + ipp_service.txt_record_map = '' + expect_service_list[ipp_service.to_string()] = 1 + return expect_service_list + elif query_string1 == AfpTestData.afp_registration_type: + if query_string2 == AfpTestData.afp_instance_name: + afp_service.instance_name = '' + afp_service.registration_type = '' + afp_service.full_domain_name = AfpTestData.afp_domain_name + afp_service.txt_record_map = AfpTestData.afp_txt_record + expect_service_list[afp_service.to_string()] = 1 + return expect_service_list + ipp_service.instance_name = IppTestData.ipp_instance_name + ipp_service.registration_type = ( + IppTestData.ipp_registration_type + '.local.' + ) + ipp_service.full_domain_name = '' + ipp_service.txt_record_map = '' + expect_service_list[ipp_service.to_string()] = 1 + + ipp_service.instance_name = '' + ipp_service.registration_type = '' + ipp_service.full_domain_name = IppTestData.ipp_domain_name + ipp_service.txt_record_map = IppTestData.ipp_txt_record + expect_service_list[ipp_service.to_string()] = 1 + + afp_service.instance_name = AfpTestData.afp_instance_name + afp_service.registration_type = ( + AfpTestData.afp_registration_type + '.local.' + ) + afp_service.full_domain_name = '' + afp_service.txt_record_map = '' + expect_service_list[afp_service.to_string()] = 1 + + afp_service.instance_name = '' + afp_service.registration_type = '' + afp_service.full_domain_name = AfpTestData.afp_domain_name + afp_service.txt_record_map = AfpTestData.afp_txt_record + expect_service_list[afp_service.to_string()] = 1 + + return expect_service_list + elif ( + service_type == WifiP2PEnums.WifiP2pServiceInfo.WIFI_P2P_SERVICE_TYPE_UPNP + ): + upnp_service = ( + 'uuid:' + UpnpTestData.uuid + '::' + (UpnpTestData.rootdevice) + ) + expect_service_list[upnp_service] = 1 + if query_string1 != 'upnp:rootdevice': + upnp_service = ( + 'uuid:' + UpnpTestData.uuid + ('::' + UpnpTestData.av_transport) + ) + expect_service_list[upnp_service] = 1 + upnp_service = ( + 'uuid:' + UpnpTestData.uuid + ('::' + UpnpTestData.connection_manager) + ) + expect_service_list[upnp_service] = 1 + upnp_service = ( + 'uuid:' + UpnpTestData.uuid + ('::' + UpnpTestData.service_type) + ) + expect_service_list[upnp_service] = 1 + upnp_service = 'uuid:' + UpnpTestData.uuid + expect_service_list[upnp_service] = 1 + + return expect_service_list + + +def check_service_query_result(service_list, expect_service_list): + """Check serviceList same as expectServiceList or not. + + Args: + service_list: ServiceList which get from query result + expect_service_list: ServiceList which hardcode in genExpectTestData + + Returns: + True: serviceList same as expectServiceList + False:Exist discrepancy between serviceList and expectServiceList + """ + temp_service_list = service_list.copy() + temp_expect_service_list = expect_service_list.copy() + for service in service_list.keys(): + if service in expect_service_list: + del temp_service_list[service] + del temp_expect_service_list[service] + return not temp_expect_service_list and not temp_service_list + + +def _check_all_expect_data(expect_data: dict[str, int]) -> bool: + for _, v in expect_data.items(): + if v == 1: + return False + return True + + +def request_service_and_check_result( + ad_service_provider: p2p_utils.DeviceState, + ad_service_receiver: p2p_utils.DeviceState, + service_type: int, + query_string1, + query_string2, +): + """Based on service type and query info, check service request result. + + Check same as expect or not on an Android device ad_service_receiver. + And remove p2p service request after result check. + + Args: + ad_service_provider: The android device which provide p2p local service + ad_service_receiver: The android device which query p2p local service + service_type: P2p local service type, Upnp or Bonjour + query_string1: Query String, NonNull + query_string2: Query String, used for Bonjour, Nullable + + Returns: + 0: if service request result is as expected. + """ + expect_data = gen_expect_test_data(service_type, query_string1, query_string2) + p2p_utils.discover_p2p_peer(ad_service_receiver, ad_service_provider) + ad_service_receiver.ad.wifi.wifiP2pStopPeerDiscovery() + ad_service_receiver.ad.wifi.wifiP2pClearServiceRequests() + time.sleep(_DEFAULT_FUNCTION_SWITCH_TIME) + + service_id = 0 + if ( + service_type + == WifiP2PEnums.WifiP2pServiceInfo.WIFI_P2P_SERVICE_TYPE_BONJOUR + ): + ad_service_receiver.ad.log.info( + 'Request bonjour service in %s with Query String %s and %s ' + % (ad_service_receiver.ad.serial, query_string1, query_string2) + ) + ad_service_receiver.ad.log.info('expectData 1st %s' % expect_data) + if query_string1: + service_id = ad_service_receiver.ad.wifi.wifiP2pAddBonjourServiceRequest( + query_string2, # instanceName + query_string1, # serviceType + ) + else: + service_id = ad_service_receiver.ad.wifi.wifiP2pAddServiceRequest( + service_type + ) + time.sleep(_DEFAULT_FUNCTION_SWITCH_TIME) + ad_service_receiver.ad.log.info('service request id %s' % service_id) + p2p_utils.set_dns_sd_response_listeners(ad_service_receiver) + ad_service_receiver.ad.wifi.wifiP2pDiscoverServices() + ad_service_receiver.ad.log.info('Check Service Listener') + time.sleep(_DEFAULT_SERVICE_WAITING_TIME) + check_discovered_dns_sd_response( + ad_service_receiver, + expected_responses=expect_data, + expected_src_device_address=( + ad_service_provider.p2p_device.device_address + ), + channel_id=ad_service_receiver.channel_ids[0], + timeout=_NORMAL_TIMEOUT, + ) + ad_service_receiver.ad.log.info('expectData 2nd %s' % expect_data) + check_discovered_dns_sd_txt_record( + ad_service_receiver, + expected_records=expect_data, + expected_src_device_address=( + ad_service_provider.p2p_device.device_address + ), + channel_id=ad_service_receiver.channel_ids[0], + timeout=_NORMAL_TIMEOUT, + ) + got_all_expects = _check_all_expect_data(expect_data) + ad_service_receiver.ad.log.info( + 'Got all the expect data : %s', got_all_expects + ) + asserts.assert_true( + got_all_expects, + "Don't got all the expect data.", + ) + elif ( + service_type == WifiP2PEnums.WifiP2pServiceInfo.WIFI_P2P_SERVICE_TYPE_UPNP + ): + ad_service_receiver.ad.log.info( + 'Request upnp service in %s with Query String %s ' + % (ad_service_receiver.ad.serial, query_string1) + ) + ad_service_receiver.ad.log.info('expectData %s' % expect_data) + if query_string1: + service_id = ad_service_receiver.ad.wifi.wifiP2pAddUpnpServiceRequest( + query_string1 + ) + else: + service_id = ad_service_receiver.ad.wifi.wifiP2pAddServiceRequest( + service_type + ) + p2p_utils.set_upnp_response_listener(ad_service_receiver) + ad_service_receiver.ad.wifi.wifiP2pDiscoverServices() + ad_service_receiver.ad.log.info('Check Service Listener') + time.sleep(_DEFAULT_FUNCTION_SWITCH_TIME) + p2p_utils.check_discovered_services( + ad_service_receiver, + ad_service_provider.p2p_device.device_address, + expected_dns_sd_sequence=None, + expected_dns_txt_sequence=None, + expected_upnp_sequence=expect_data, + ) + ad_service_receiver.ad.wifi.wifiP2pRemoveServiceRequest(service_id) + return 0 + + +def request_service_and_check_result_with_retry( + ad_service_provider, + ad_service_receiver, + service_type, + query_string1, + query_string2, + retry_count=3, +): + """allow failures for requestServiceAndCheckResult. + + Service + + discovery might fail unexpectedly because the request packet might not be + received by the service responder due to p2p state switch. + + Args: + ad_service_provider: The android device which provide p2p local service + ad_service_receiver: The android device which query p2p local service + service_type: P2p local service type, Upnp or Bonjour + query_string1: Query String, NonNull + query_string2: Query String, used for Bonjour, Nullable + retry_count: maximum retry count, default is 3 + """ + ret = 0 + while retry_count > 0: + ret = request_service_and_check_result( + ad_service_provider, + ad_service_receiver, + service_type, + query_string1, + query_string2, + ) + if ret == 0: + break + retry_count -= 1 + + asserts.assert_equal(0, ret, 'cannot find any services with retries.') + + +def _check_no_discovered_service( + ad: android_device.AndroidDevice, + callback_handler: callback_handler_v2.CallbackHandlerV2, + event_name: str, + expected_src_device_address: str, + timeout: datetime.timedelta = _DEFAULT_TIMEOUT, +): + """Checks that no service is received from the specified source device.""" + def _is_expected_event(event): + src_device = constants.WifiP2pDevice.from_dict( + event.data['sourceDevice'] + ) + return src_device.device_address == expected_src_device_address + + # Set to a small timeout to allow pulling all received events + if timeout.total_seconds() <= 1: + timeout = datetime.timedelta(seconds=1) + try: + event = callback_handler.waitForEvent( + event_name=event_name, + predicate=_is_expected_event, + timeout=timeout.total_seconds(), + ) + except errors.CallbackHandlerTimeoutError: + # Timeout error is expected as there should not be any qualified service + return + asserts.assert_is_none( + event, + f'{ad} should not discover p2p service. Discovered: {event}', + ) + + +def check_discovered_dns_sd_response( + device: p2p_utils.DeviceState, + expected_responses: Sequence[Sequence[str, str]], + expected_src_device_address: str, + channel_id: int | None = None, + timeout: datetime.timedelta = _DEFAULT_TIMEOUT, +): + """Check discovered DNS SD responses. + + If no responses are expected, check that no DNS SD response appear within + timeout. Otherwise, wait for all expected responses within timeout. + + This assumes that Bonjour service listener is set by + `set_dns_sd_response_listeners`. + + Args: + device: The device that is discovering DNS SD responses. + expected_responses: The expected DNS SD responses. + expected_src_device_address: This only checks services that are from the + expected source device. + channel_id: The channel to check for expected responses. + timeout: The wait timeout. + """ + channel_id = channel_id or device.channel_ids[0] + callback_handler = device.dns_sd_response_listeners[channel_id] + + def _all_service_received(event): + nonlocal expected_responses + src_device = constants.WifiP2pDevice.from_dict( + event.data['sourceDevice'] + ) + if src_device.device_address != expected_src_device_address: + return False + registration_type = event.data['registrationType'] + instance_name = event.data['instanceName'] + service_item = instance_name + registration_type + device.ad.log.info('Received DNS SD response: %s', service_item) + if service_item in expected_responses: + expected_responses[service_item] = 0 + _check_all_expect_data(expected_responses) + + device.ad.log.info('Waiting for DNS SD services: %s', expected_responses) + # Set to a small timeout to allow pulling all received events + if timeout.total_seconds() <= 1: + timeout = datetime.timedelta(seconds=1) + try: + callback_handler.waitForEvent( + event_name=constants.ON_DNS_SD_SERVICE_AVAILABLE, + predicate=_all_service_received, + timeout=timeout.total_seconds(), + ) + except errors.CallbackHandlerTimeoutError: + device.ad.log.info(f'need to wait for services: {expected_responses}') + + +def check_discovered_dns_sd_txt_record( + device: p2p_utils.DeviceState, + expected_records: Sequence[Sequence[str, dict[str, str]]], + expected_src_device_address: str, + channel_id: int | None = None, + timeout: datetime.timedelta = _DEFAULT_TIMEOUT, +): + """Check discovered DNS SD TXT records. + + If no records are expected, check that no DNS SD TXT record appear within + timeout. Otherwise, wait for all expected records within timeout. + + This assumes that Bonjour service listener is set by + `set_dns_sd_response_listeners`. + + Args: + device: The device that is discovering DNS SD TXT records. + expected_records: The expected DNS SD TXT records. + expected_src_device_address: This only checks services that are from the + expected source device. + channel_id: The channel to check for expected records. + timeout: The wait timeout. + """ + channel_id = channel_id or device.channel_ids[0] + idx = device.channel_ids.index(channel_id) + callback_handler = device.dns_sd_response_listeners[idx] + + device.ad.log.info('Expected DNS SD TXT records: %s', expected_records) + def _all_service_received(event): + nonlocal expected_records + src_device = constants.WifiP2pDevice.from_dict( + event.data['sourceDevice'] + ) + if src_device.device_address != expected_src_device_address: + return False + full_domain_name = event.data['fullDomainName'] + txt_record_map = event.data['txtRecordMap'] + record_to_remove = full_domain_name + str(txt_record_map) + device.ad.log.info('Received DNS SD TXT record: %s', record_to_remove) + if record_to_remove in expected_records: + expected_records[record_to_remove] = 0 + _check_all_expect_data(expected_records) + + device.ad.log.info('Waiting for DNS SD TXT records: %s', expected_records) + # Set to a small timeout to allow pulling all received events + if timeout.total_seconds() <= 1: + timeout = datetime.timedelta(seconds=1) + try: + callback_handler.waitForEvent( + event_name=constants.ON_DNS_SD_TXT_RECORD_AVAILABLE, + predicate=_all_service_received, + timeout=timeout.total_seconds(), + ) + except errors.CallbackHandlerTimeoutError: + device.ad.log.info(f'need to wait for services: {expected_records}') + + class WifiP2PEnums: """Enums for WifiP2p.""" @@ -205,17 +737,17 @@ class WifiP2PEnums: WIFI_P2P_SERVICE_TYPE_VENDOR_SPECIFIC = 255 class WifiP2pDnsSdServiceResponse: - InstanceName = '' - RegistrationType = '' - FullDomainName = '' - TxtRecordMap = {} + instance_name = '' + registration_type = '' + full_domain_name = '' + txt_record_map = {} def __init__(self): pass - def toString(self): + def to_string(self): return ( - self.InstanceName - + self.RegistrationType - + (self.FullDomainName + str(self.TxtRecordMap)) + self.instance_name + + self.registration_type + + (self.full_domain_name + str(self.txt_record_map)) ) diff --git a/tests/hostsidetests/multidevices/test/direct/integration/wifi_p2p_manager_test.py b/tests/hostsidetests/multidevices/test/direct/integration/wifi_p2p_manager_test.py new file mode 100644 index 0000000000..d4d6dab7f8 --- /dev/null +++ b/tests/hostsidetests/multidevices/test/direct/integration/wifi_p2p_manager_test.py @@ -0,0 +1,256 @@ +# 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. +# +# 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. + +# Lint as: python3 +"""ACTS Wifi P2pManager Test reimplemented in Mobly.""" + +from collections.abc import Sequence +import datetime +import logging +import time + +from android.platform.test.annotations import ApiTest +from direct import constants +from direct import p2p_utils +from mobly import base_test +from mobly import records +from mobly import test_runner +from mobly import utils +from mobly.controllers import android_device +import wifi_p2p_lib as wp2putils + + +_DEFAULT_TIMEOUT = datetime.timedelta(seconds=30) +DEFAULT_SLEEPTIME = 5 +_DEFAULT_FUNCTION_SWITCH_TIME = 10 +_DEFAULT_GROUP_CLIENT_LOST_TIME = 60 + +_WIFI_DIRECT_SNIPPET_KEY = 'wifi_direct_mobly_snippet' + +P2P_CONNECT_NEGOTIATION = 0 +P2P_CONNECT_JOIN = 1 +P2P_CONNECT_INVITATION = 2 + +WPS_PBC = wp2putils.WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_PBC +WPS_DISPLAY = wp2putils.WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_DISPLAY +WPS_KEYPAD = wp2putils.WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_KEYPAD + + +class WifiP2pManagerTest(base_test.BaseTestClass): + """Tests Wi-Fi Direct between 2 Android devices.""" + + ads: Sequence[android_device.AndroidDevice] + group_owner_ad: android_device.AndroidDevice + client_ad: android_device.AndroidDevice + network_name = 'DIRECT-xy-Hello' + passphrase = 'P2pWorld1234' + group_band = '2' + + def setup_class(self) -> None: + super().setup_class() + self.ads = self.register_controller(android_device, min_number=2) + utils.concurrent_exec( + self._setup_device, + param_list=[[ad] for ad in self.ads], + raise_on_exception=True, + ) + self.group_owner_ad, self.client_ad, *_ = self.ads + self.group_owner_ad.debug_tag = ( + f'{self.group_owner_ad.serial}(Group Owner)' + ) + self.client_ad.debug_tag = f'{self.client_ad.serial}(Client)' + + def _setup_device(self, ad: android_device.AndroidDevice) -> None: + ad.load_snippet('wifi', constants.WIFI_SNIPPET_PACKAGE_NAME) + # Clear all saved Wi-Fi networks. + ad.wifi.wifiDisable() + ad.wifi.wifiClearConfiguredNetworks() + ad.wifi.wifiEnable() + + def _teardown_wifi_p2p(self, ad: android_device.AndroidDevice): + try: + p2p_utils.teardown_wifi_p2p(ad) + finally: + ad.services.create_output_excerpts_all(self.current_test_info) + + def teardown_test(self) -> None: + utils.concurrent_exec( + self._teardown_wifi_p2p, + param_list=[[ad] for ad in self.ads], + raise_on_exception=True, + ) + + def on_fail(self, record: records.TestResult) -> None: + logging.info('Collecting bugreports...') + android_device.take_bug_reports( + self.ads, destination=self.current_test_info.output_path + ) + + @ApiTest( + apis=[ + 'android.net.wifi.p2p.WifiP2pManager#discoverPeers(android.net.wifi.p2p.WifiP2pManager.Channel channel, android.net.wifi.p2p.WifiP2pManager.ActionListener listener)', + ] + ) + def test_p2p_discovery(self): + """Verify the p2p discovery functionality. + + Steps: + 1. Discover the target device + 2. Check the target device in peer list + """ + self.ads[0].log.info('Device discovery') + responder = p2p_utils.setup_wifi_p2p(self.ads[0]) + requester = p2p_utils.setup_wifi_p2p(self.ads[1]) + + requester.ad.log.info('Searching for target device.') + responder_p2p_dev = p2p_utils.discover_p2p_peer(responder, requester) + self.ads[0].log.info('name= %s, address=%s, group_owner=%s', + responder_p2p_dev.device_name, + responder_p2p_dev.device_address, + responder_p2p_dev.is_group_owner) + requester_p2p_dev = p2p_utils.discover_p2p_peer(requester, responder) + self.ads[1].log.info('name= %s, address=%s, group_owner=%s', + requester_p2p_dev.device_name, + requester_p2p_dev.device_address, + requester_p2p_dev.is_group_owner) + + @ApiTest( + apis=[ + 'android.net.wifi.p2p.WifiP2pManager#requestConnectionInfo(android.net.wifi.p2p.WifiP2pManager.Channel channel, android.net.wifi.p2p.WifiP2pManager.ConnectionInfoListener listener)', + 'android.net.wifi.p2p.WifiP2pManager#connect(android.net.wifi.p2p.WifiP2pManager.Channel channel, android.net.wifi.p2p.WifiP2pConfig config, android.net.wifi.p2p.WifiP2pManager.ActionListener listener)', + ] + ) + def test_p2p_connect_via_pbc_and_ping_and_reconnect(self): + """Verify the p2p connect via pbc functionality. + + Steps: + 1. Request the connection which include discover the target device + 2. check which dut is GO and which dut is GC + 3. connection check via ping from GC to GO + 4. disconnect + 5. Trigger connect again from GO for reconnect test. + 6. GO trigger disconnect + 7. Trigger connect again from GC for reconnect test. + 8. GC trigger disconnect + """ + self.ads[0].log.info('Device initialize') + go_dut = self.ads[0] + gc_dut = self.ads[1] + + logging.info('GO: %s, GC: %s', go_dut.serial, gc_dut.serial) + device_go = p2p_utils.setup_wifi_p2p(go_dut) + device_gc = p2p_utils.setup_wifi_p2p(gc_dut) + self.run_p2p_connect_and_ping(device_gc, device_go, WPS_PBC, False) + self.run_p2p_connect_and_ping(device_go, device_gc, WPS_PBC, True) + self.run_p2p_connect_and_ping(device_gc, device_go, WPS_PBC, True) + + @ApiTest( + apis=[ + 'android.net.wifi.p2p.WifiP2pManager#requestConnectionInfo(android.net.wifi.p2p.WifiP2pManager.Channel channel, android.net.wifi.p2p.WifiP2pManager.ConnectionInfoListener listener)', + 'android.net.wifi.p2p.WifiP2pManager#connect(android.net.wifi.p2p.WifiP2pManager.Channel channel, android.net.wifi.p2p.WifiP2pConfig config, android.net.wifi.p2p.WifiP2pManager.ActionListener listener)', + ] + ) + def test_p2p_connect_via_display_and_ping_and_reconnect(self): + """Verify the p2p connect via display functionality. + + Steps: + 1. Request the connection which include discover the target device + 2. check which dut is GO and which dut is GC + 3. connection check via ping from GC to GO + 4. disconnect + 5. Trigger connect again from GO for reconnect test. + 6. GO trigger disconnect + 7. Trigger connect again from GC for reconnect test. + 8. GC trigger disconnect + """ + self.ads[0].log.info('Device initialize') + go_dut = self.ads[0] + gc_dut = self.ads[1] + + logging.info('GO: %s, GC: %s', go_dut.serial, gc_dut.serial) + device_go = p2p_utils.setup_wifi_p2p(go_dut) + device_gc = p2p_utils.setup_wifi_p2p(gc_dut) + self.run_p2p_connect_and_ping(device_gc, device_go, WPS_DISPLAY, False) + self.run_p2p_connect_and_ping(device_go, device_gc, WPS_DISPLAY, True) + self.run_p2p_connect_and_ping(device_gc, device_go, WPS_DISPLAY, True) + + @ApiTest( + apis=[ + 'android.net.wifi.p2p.WifiP2pManager#requestConnectionInfo(android.net.wifi.p2p.WifiP2pManager.Channel channel, android.net.wifi.p2p.WifiP2pManager.ConnectionInfoListener listener)', + 'android.net.wifi.p2p.WifiP2pManager#connect(android.net.wifi.p2p.WifiP2pManager.Channel channel, android.net.wifi.p2p.WifiP2pConfig config, android.net.wifi.p2p.WifiP2pManager.ActionListener listener)', + ] + ) + def test_p2p_connect_via_keypad_and_ping_and_reconnect(self): + """Verify the p2p connect via keypad functionality. + + Steps: + 1. Request the connection which include discover the target device + 2. check which dut is GO and which dut is GC + 3. connection check via ping from GC to GO + 4. disconnect + 5. Trigger connect again from GO for reconnect test. + 6. GO trigger disconnect + 7. Trigger connect again from GC for reconnect test. + 8. GC trigger disconnect + """ + self.ads[0].log.info('Device initialize') + go_dut = self.ads[0] + gc_dut = self.ads[1] + + logging.info('GO: %s, GC: %s', go_dut.serial, gc_dut.serial) + device_go = p2p_utils.setup_wifi_p2p(go_dut) + device_gc = p2p_utils.setup_wifi_p2p(gc_dut) + + self.run_p2p_connect_and_ping(device_gc, device_go, WPS_KEYPAD, False) + self.run_p2p_connect_and_ping(device_go, device_gc, WPS_KEYPAD, True) + self.run_p2p_connect_and_ping(device_gc, device_go, WPS_KEYPAD, True) + + def run_p2p_connect_and_ping( + self, + device1, + device2, + pws_method, + re_connect): + # Request the connection + wp2putils.p2p_connect(device1, device2, re_connect, pws_method) + + if wp2putils.is_go(device1.ad): + client_dut = device2.ad + else: + client_dut = device1.ad + logging.info('Client is : %s', client_dut.serial) + go_ip = wp2putils.p2p_go_ip(client_dut) + wp2putils.p2p_connection_ping_test(client_dut, go_ip) + + # trigger disconnect + p2p_utils.remove_group_and_verify_disconnected( + device1, device2, is_group_negotiation=False + ) + time.sleep(_DEFAULT_FUNCTION_SWITCH_TIME) + +if __name__ == '__main__': + test_runner.main() + diff --git a/tests/hostsidetests/multidevices/test/direct/p2p_utils.py b/tests/hostsidetests/multidevices/test/direct/p2p_utils.py index 45ff6d7c74..06584a0142 100644 --- a/tests/hostsidetests/multidevices/test/direct/p2p_utils.py +++ b/tests/hostsidetests/multidevices/test/direct/p2p_utils.py @@ -17,6 +17,7 @@ from collections.abc import Sequence import dataclasses import datetime import logging +import time from mobly import asserts from mobly.snippet import errors @@ -332,17 +333,29 @@ def p2p_connect( requester.ad.log.info('Sent P2P connect invitation to responder.') # Connect with WPS config requires user inetraction through UI. if config.wps_setup == constants.WpsInfo.PBC: + time.sleep(_DEFAULT_UI_RESPONSE_TIME.total_seconds()) responder.ad.wifi.wifiP2pAcceptInvitation( requester.p2p_device.device_name ) responder.ad.log.info('Accepted connect invitation.') elif config.wps_setup == constants.WpsInfo.DISPLAY: + time.sleep(_DEFAULT_UI_RESPONSE_TIME.total_seconds()) pin = requester.ad.wifi.wifiP2pGetPinCode( responder.p2p_device.device_name ) requester.ad.log.info('p2p connection PIN code: %s', pin) + time.sleep(_DEFAULT_UI_RESPONSE_TIME.total_seconds()) responder.ad.wifi.wifiP2pEnterPin(pin, requester.p2p_device.device_name) responder.ad.log.info('Enetered PIN code.') + elif config.wps_setup == constants.WpsInfo.KEYPAD: + time.sleep(_DEFAULT_UI_RESPONSE_TIME.total_seconds()) + pin = responder.ad.wifi.wifiP2pGetKeypadPinCode( + requester.p2p_device.device_name + ) + responder.ad.log.info('p2p connection Keypad PIN code: %s', pin) + time.sleep(_DEFAULT_UI_RESPONSE_TIME.total_seconds()) + requester.ad.wifi.wifiP2pEnterPin(pin, responder.p2p_device.device_name) + requester.ad.log.info('Enetered Keypad PIN code.') elif config.wps_setup is not None: asserts.fail(f'Unsupported WPS configuration: {config.wps_setup}') @@ -370,6 +383,59 @@ def p2p_connect( logging.info('Established wifi p2p connection.') +def p2p_reconnect( + requester: DeviceState, + responder: DeviceState, + config: constants.WifiP2pConfig, +) -> None: + """Establishes Wi-Fi p2p connection with WPS configuration. + + This method instructs the requester to initiate a connection request and the + responder to accept the connection. It then verifies the connection status + on both devices. + + Args: + requester: The requester device. + responder: The responder device. + config: The Wi-Fi p2p configuration. + """ + logging.info( + 'Establishing a p2p connection through p2p configuration %s.', config + ) + + # Clear events in broadcast receiver. + _clear_events(requester, constants.WIFI_P2P_PEERS_CHANGED_ACTION) + _clear_events(requester, constants.WIFI_P2P_CONNECTION_CHANGED_ACTION) + _clear_events(responder, constants.WIFI_P2P_PEERS_CHANGED_ACTION) + _clear_events(responder, constants.WIFI_P2P_CONNECTION_CHANGED_ACTION) + + requester.ad.wifi.wifiP2pConnect(config.to_dict()) + requester.ad.log.info('Sent P2P connect invitation to responder.') + + # Check p2p status on requester. + _wait_connection_notice(requester.broadcast_receiver) + _wait_peer_connected( + requester.broadcast_receiver, + responder.p2p_device.device_address, + ) + requester.ad.log.info( + 'Connected with device %s through wifi p2p.', + responder.p2p_device.device_address, + ) + + # Check p2p status on responder. + _wait_connection_notice(responder.broadcast_receiver) + _wait_peer_connected( + responder.broadcast_receiver, + requester.p2p_device.device_address, + ) + responder.ad.log.info( + 'Connected with device %s through wifi p2p.', + requester.p2p_device.device_address, + ) + logging.info('Established wifi p2p connection.') + + def _wait_peer_connected( broadcast_receiver: callback_handler_v2.CallbackHandlerV2, peer_address: str ): diff --git a/tests/hostsidetests/multidevices/test/direct/cts_wifi_direct_test_suite.py b/tests/hostsidetests/multidevices/test/direct/wifi_direct_test_suite.py index 80d12abe05..6598e39648 100644 --- a/tests/hostsidetests/multidevices/test/direct/cts_wifi_direct_test_suite.py +++ b/tests/hostsidetests/multidevices/test/direct/wifi_direct_test_suite.py @@ -13,7 +13,7 @@ # limitations under the License. # Lint as: python3 -"""CTS Wi-Fi Direct test suite.""" +"""Wi-Fi Direct test suite.""" from mobly import base_suite from mobly import suite_runner @@ -24,8 +24,8 @@ from direct import group_owner_with_config_test from direct import service_discovery_test -class CtsWifiDirectTestSuite(base_suite.BaseSuite): - """CTS Wi-Fi Direct test suite.""" +class WifiDirectTestSuite(base_suite.BaseSuite): + """Wi-Fi Direct test suite.""" def setup_suite(self, config): del config # unused diff --git a/tests/hostsidetests/multidevices/test/softap/AndroidTest.xml b/tests/hostsidetests/multidevices/test/softap/AndroidTest.xml index 19da4cca39..26ddcdd877 100644 --- a/tests/hostsidetests/multidevices/test/softap/AndroidTest.xml +++ b/tests/hostsidetests/multidevices/test/softap/AndroidTest.xml @@ -21,18 +21,12 @@ <target_preparer class="AndroidDeviceFeaturesCheckDecorator"> <option name="required_feature" value="android.hardware.wifi" /> </target_preparer> - <target_preparer class="AndroidMainlineModulesCheckDecorator"> - <option name="mainline_module_package_name" value="com.google.android.wifi" /> - </target_preparer> <target_preparer class="AndroidInstallAppsDecorator" /> </device> <device name="AndroidDevice"> <target_preparer class="AndroidDeviceFeaturesCheckDecorator"> <option name="required_feature" value="android.hardware.wifi" /> </target_preparer> - <target_preparer class="AndroidMainlineModulesCheckDecorator"> - <option name="mainline_module_package_name" value="com.google.android.wifi" /> - </target_preparer> <target_preparer class="AndroidInstallAppsDecorator" /> </device> |