From 97d6aeb3a27caec884cf2bbdb1a2c71e4403c0ef Mon Sep 17 00:00:00 2001 From: Kangping Dong Date: Fri, 8 Dec 2023 15:51:09 +0800 Subject: [PATCH] [mdns] add API for discovery with subtype This commit adds support of discovering services with explicit subtype. With this change, a service can be discovered with subtype with: ``` nsdManager.discoverServices( new DiscoveryRequest.Builder("_http._tcp", PROTOCOL_DNS_SD) .setSubtype("_printer").build(), executor, listener); ``` Bug: 265095929 Test: atest CtsNetTestCases FrameworksNetTests Change-Id: Iba76283a003cf2d52a8c26e1de872c3e8e433350 --- framework-t/api/current.txt | 17 ++ .../src/android/net/nsd/DiscoveryRequest.java | 247 ++++++++++++++++++ .../android/net/nsd/INsdManagerCallback.aidl | 3 +- .../android/net/nsd/INsdServiceConnector.aidl | 5 +- .../src/android/net/nsd/NsdManager.java | 117 ++++++--- .../android/net/nsd/DiscoveryRequest.aidl | 19 ++ tests/cts/net/Android.bp | 1 + .../android/net/cts/DiscoveryRequestTest.kt | 90 +++++++ .../net/src/android/net/cts/NsdManagerTest.kt | 32 ++- .../java/android/net/nsd/NsdManagerTest.java | 5 +- 10 files changed, 498 insertions(+), 38 deletions(-) create mode 100644 framework-t/src/android/net/nsd/DiscoveryRequest.java create mode 100644 framework/aidl-export/android/net/nsd/DiscoveryRequest.aidl create mode 100644 tests/cts/net/src/android/net/cts/DiscoveryRequestTest.kt diff --git a/framework-t/api/current.txt b/framework-t/api/current.txt index 60a88c045b..7cd3d4f754 100644 --- a/framework-t/api/current.txt +++ b/framework-t/api/current.txt @@ -210,9 +210,26 @@ package android.net { package android.net.nsd { + @FlaggedApi("com.android.net.flags.nsd_subtypes_support_enabled") public final class DiscoveryRequest implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.net.Network getNetwork(); + method @NonNull public String getServiceType(); + method @Nullable public String getSubtype(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public static final class DiscoveryRequest.Builder { + ctor public DiscoveryRequest.Builder(@NonNull String); + method @NonNull public android.net.nsd.DiscoveryRequest build(); + method @NonNull public android.net.nsd.DiscoveryRequest.Builder setNetwork(@Nullable android.net.Network); + method @NonNull public android.net.nsd.DiscoveryRequest.Builder setSubtype(@Nullable String); + } + public final class NsdManager { method public void discoverServices(String, int, android.net.nsd.NsdManager.DiscoveryListener); method public void discoverServices(@NonNull String, int, @Nullable android.net.Network, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.DiscoveryListener); + method @FlaggedApi("com.android.net.flags.nsd_subtypes_support_enabled") public void discoverServices(@NonNull android.net.nsd.DiscoveryRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.DiscoveryListener); method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void discoverServices(@NonNull String, int, @NonNull android.net.NetworkRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.DiscoveryListener); method public void registerService(android.net.nsd.NsdServiceInfo, int, android.net.nsd.NsdManager.RegistrationListener); method public void registerService(@NonNull android.net.nsd.NsdServiceInfo, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.RegistrationListener); diff --git a/framework-t/src/android/net/nsd/DiscoveryRequest.java b/framework-t/src/android/net/nsd/DiscoveryRequest.java new file mode 100644 index 0000000000..b0b71eacd0 --- /dev/null +++ b/framework-t/src/android/net/nsd/DiscoveryRequest.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2023 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.net.nsd; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.Network; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import java.util.Objects; + +/** + * Encapsulates parameters for {@link NsdManager#discoverServices}. + */ +@FlaggedApi(NsdManager.Flags.NSD_SUBTYPES_SUPPORT_ENABLED) +public final class DiscoveryRequest implements Parcelable { + private final int mProtocolType; + + @NonNull + private final String mServiceType; + + @Nullable + private final String mSubtype; + + @Nullable + private final Network mNetwork; + + // TODO: add mDiscoveryConfig for more fine-grained discovery behavior control + + @NonNull + public static final Creator CREATOR = + new Creator<>() { + @Override + public DiscoveryRequest createFromParcel(Parcel in) { + int protocolType = in.readInt(); + String serviceType = in.readString(); + String subtype = in.readString(); + Network network = + in.readParcelable(Network.class.getClassLoader(), Network.class); + return new DiscoveryRequest(protocolType, serviceType, subtype, network); + } + + @Override + public DiscoveryRequest[] newArray(int size) { + return new DiscoveryRequest[size]; + } + }; + + private DiscoveryRequest(int protocolType, @NonNull String serviceType, + @Nullable String subtype, @Nullable Network network) { + mProtocolType = protocolType; + mServiceType = serviceType; + mSubtype = subtype; + mNetwork = network; + } + + /** + * Returns the service type in format of dot-joint string of two labels. + * + * For example, "_ipp._tcp" for internet printer and "_matter._tcp" for Matter operational device. + */ + @NonNull + public String getServiceType() { + return mServiceType; + } + + /** + * Returns the subtype without the trailing "._sub" label or {@code null} if no subtype is + * specified. + * + * For example, the return value will be "_printer" for subtype "_printer._sub". + */ + @Nullable + public String getSubtype() { + return mSubtype; + } + + /** + * Returns the service discovery protocol. + * + * @hide + */ + public int getProtocolType() { + return mProtocolType; + } + + /** + * Returns the {@link Network} on which the query should be sent or {@code null} if no + * network is specified. + */ + @Nullable + public Network getNetwork() { + return mNetwork; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(", protocolType: ").append(mProtocolType) + .append(", serviceType: ").append(mServiceType) + .append(", subtype: ").append(mSubtype) + .append(", network: ").append(mNetwork); + return sb.toString(); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (!(other instanceof DiscoveryRequest)) { + return false; + } else { + DiscoveryRequest otherRequest = (DiscoveryRequest) other; + return mProtocolType == otherRequest.mProtocolType + && Objects.equals(mServiceType, otherRequest.mServiceType) + && Objects.equals(mSubtype, otherRequest.mSubtype) + && Objects.equals(mNetwork, otherRequest.mNetwork); + } + } + + @Override + public int hashCode() { + return Objects.hash(mProtocolType, mServiceType, mSubtype, mNetwork); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mProtocolType); + dest.writeString(mServiceType); + dest.writeString(mSubtype); + dest.writeParcelable(mNetwork, flags); + } + + /** The builder for creating new {@link DiscoveryRequest} objects. */ + public static final class Builder { + private final int mProtocolType; + + @NonNull + private String mServiceType; + + @Nullable + private String mSubtype; + + @Nullable + private Network mNetwork; + + /** + * Creates a new default {@link Builder} object with given service type. + * + * @throws IllegalArgumentException if {@code serviceType} is {@code null} or an empty + * string + */ + public Builder(@NonNull String serviceType) { + this(NsdManager.PROTOCOL_DNS_SD, serviceType); + } + + /** @hide */ + public Builder(int protocolType, @NonNull String serviceType) { + NsdManager.checkProtocol(protocolType); + mProtocolType = protocolType; + setServiceType(serviceType); + } + + /** + * Sets the service type to be discovered or {@code null} if no services should be queried. + * + * The {@code serviceType} must be a dot-joint string of two labels. For example, + * "_ipp._tcp" for internet printer. Additionally, the first label must start with + * underscore ('_') and the second label must be either "_udp" or "_tcp". Otherwise, {@link + * NsdManager#discoverServices} will fail with {@link NsdManager#FAILURE_BAD_PARAMETER}. + * + * @throws IllegalArgumentException if {@code serviceType} is {@code null} or an empty + * string + * + * @hide + */ + @NonNull + public Builder setServiceType(@NonNull String serviceType) { + if (TextUtils.isEmpty(serviceType)) { + throw new IllegalArgumentException("Service type cannot be empty"); + } + mServiceType = serviceType; + return this; + } + + /** + * Sets the optional subtype of the services to be discovered. + * + * If a non-empty {@code subtype} is specified, it must start with underscore ('_') and + * have the trailing "._sub" removed. Otherwise, {@link NsdManager#discoverServices} will + * fail with {@link NsdManager#FAILURE_BAD_PARAMETER}. For example, {@code subtype} should + * be "_printer" for DNS name "_printer._sub._http._tcp". In this case, only services with + * this {@code subtype} will be queried, rather than all services of the base service type. + * + * Note that a non-empty service type must be specified with {@link #setServiceType} if a + * non-empty subtype is specified by this method. + */ + @NonNull + public Builder setSubtype(@Nullable String subtype) { + mSubtype = subtype; + return this; + } + + /** + * Sets the {@link Network} on which the discovery queries should be sent. + * + * @param network the discovery network or {@code null} if the query should be sent on + * all supported networks + */ + @NonNull + public Builder setNetwork(@Nullable Network network) { + mNetwork = network; + return this; + } + + /** + * Creates a new {@link DiscoveryRequest} object. + */ + @NonNull + public DiscoveryRequest build() { + return new DiscoveryRequest(mProtocolType, mServiceType, mSubtype, mNetwork); + } + } +} diff --git a/framework-t/src/android/net/nsd/INsdManagerCallback.aidl b/framework-t/src/android/net/nsd/INsdManagerCallback.aidl index d89bfa9640..55820eca82 100644 --- a/framework-t/src/android/net/nsd/INsdManagerCallback.aidl +++ b/framework-t/src/android/net/nsd/INsdManagerCallback.aidl @@ -17,6 +17,7 @@ package android.net.nsd; import android.os.Messenger; +import android.net.nsd.DiscoveryRequest; import android.net.nsd.NsdServiceInfo; /** @@ -24,7 +25,7 @@ import android.net.nsd.NsdServiceInfo; * @hide */ oneway interface INsdManagerCallback { - void onDiscoverServicesStarted(int listenerKey, in NsdServiceInfo info); + void onDiscoverServicesStarted(int listenerKey, in DiscoveryRequest discoveryRequest); void onDiscoverServicesFailed(int listenerKey, int error); void onServiceFound(int listenerKey, in NsdServiceInfo info); void onServiceLost(int listenerKey, in NsdServiceInfo info); diff --git a/framework-t/src/android/net/nsd/INsdServiceConnector.aidl b/framework-t/src/android/net/nsd/INsdServiceConnector.aidl index b03eb297a2..9a3127843c 100644 --- a/framework-t/src/android/net/nsd/INsdServiceConnector.aidl +++ b/framework-t/src/android/net/nsd/INsdServiceConnector.aidl @@ -17,6 +17,7 @@ package android.net.nsd; import android.net.nsd.AdvertisingRequest; +import android.net.nsd.DiscoveryRequest; import android.net.nsd.INsdManagerCallback; import android.net.nsd.IOffloadEngine; import android.net.nsd.NsdServiceInfo; @@ -30,7 +31,7 @@ import android.os.Messenger; interface INsdServiceConnector { void registerService(int listenerKey, in AdvertisingRequest advertisingRequest); void unregisterService(int listenerKey); - void discoverServices(int listenerKey, in NsdServiceInfo serviceInfo); + void discoverServices(int listenerKey, in DiscoveryRequest discoveryRequest); void stopDiscovery(int listenerKey); void resolveService(int listenerKey, in NsdServiceInfo serviceInfo); void startDaemon(); @@ -39,4 +40,4 @@ interface INsdServiceConnector { void unregisterServiceInfoCallback(int listenerKey); void registerOffloadEngine(String ifaceName, in IOffloadEngine cb, long offloadCapabilities, long offloadType); void unregisterOffloadEngine(in IOffloadEngine cb); -} \ No newline at end of file +} diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java index b4f2be9de5..263acf27c9 100644 --- a/framework-t/src/android/net/nsd/NsdManager.java +++ b/framework-t/src/android/net/nsd/NsdManager.java @@ -57,6 +57,7 @@ import com.android.net.module.util.CollectionUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; import java.util.regex.Matcher; @@ -360,6 +361,8 @@ public final class NsdManager { @GuardedBy("mMapLock") private final SparseArray mServiceMap = new SparseArray<>(); @GuardedBy("mMapLock") + private final SparseArray mDiscoveryMap = new SparseArray<>(); + @GuardedBy("mMapLock") private final SparseArray mExecutorMap = new SparseArray<>(); private final Object mMapLock = new Object(); // Map of listener key sent by client -> per-network discovery tracker @@ -715,6 +718,12 @@ public final class NsdManager { mServHandler.sendMessage(mServHandler.obtainMessage(message, 0, listenerKey, info)); } + private void sendDiscoveryRequest( + int message, int listenerKey, DiscoveryRequest discoveryRequest) { + mServHandler.sendMessage( + mServHandler.obtainMessage(message, 0, listenerKey, discoveryRequest)); + } + private void sendError(int message, int listenerKey, int error) { mServHandler.sendMessage(mServHandler.obtainMessage(message, error, listenerKey)); } @@ -724,8 +733,8 @@ public final class NsdManager { } @Override - public void onDiscoverServicesStarted(int listenerKey, NsdServiceInfo info) { - sendInfo(DISCOVER_SERVICES_STARTED, listenerKey, info); + public void onDiscoverServicesStarted(int listenerKey, DiscoveryRequest discoveryRequest) { + sendDiscoveryRequest(DISCOVER_SERVICES_STARTED, listenerKey, discoveryRequest); } @Override @@ -1003,10 +1012,12 @@ public final class NsdManager { final Object obj = message.obj; final Object listener; final NsdServiceInfo ns; + final DiscoveryRequest discoveryRequest; final Executor executor; synchronized (mMapLock) { listener = mListenerMap.get(key); ns = mServiceMap.get(key); + discoveryRequest = mDiscoveryMap.get(key); executor = mExecutorMap.get(key); } if (listener == null) { @@ -1014,17 +1025,22 @@ public final class NsdManager { return; } if (DBG) { - Log.d(TAG, "received " + nameOf(what) + " for key " + key + ", service " + ns); + if (discoveryRequest != null) { + Log.d(TAG, "received " + nameOf(what) + " for key " + key + ", discovery " + + discoveryRequest); + } else { + Log.d(TAG, "received " + nameOf(what) + " for key " + key + ", service " + ns); + } } switch (what) { case DISCOVER_SERVICES_STARTED: - final String s = getNsdServiceInfoType((NsdServiceInfo) obj); + final String s = getNsdServiceInfoType((DiscoveryRequest) obj); executor.execute(() -> ((DiscoveryListener) listener).onDiscoveryStarted(s)); break; case DISCOVER_SERVICES_FAILED: removeListener(key); executor.execute(() -> ((DiscoveryListener) listener).onStartDiscoveryFailed( - getNsdServiceInfoType(ns), errorCode)); + getNsdServiceInfoType(discoveryRequest), errorCode)); break; case SERVICE_FOUND: executor.execute(() -> ((DiscoveryListener) listener).onServiceFound( @@ -1039,12 +1055,12 @@ public final class NsdManager { // the effect for the client is indistinguishable from STOP_DISCOVERY_SUCCEEDED removeListener(key); executor.execute(() -> ((DiscoveryListener) listener).onStopDiscoveryFailed( - getNsdServiceInfoType(ns), errorCode)); + getNsdServiceInfoType(discoveryRequest), errorCode)); break; case STOP_DISCOVERY_SUCCEEDED: removeListener(key); executor.execute(() -> ((DiscoveryListener) listener).onDiscoveryStopped( - getNsdServiceInfoType(ns))); + getNsdServiceInfoType(discoveryRequest))); break; case REGISTER_SERVICE_FAILED: removeListener(key); @@ -1117,21 +1133,33 @@ public final class NsdManager { return mListenerKey; } - // Assert that the listener is not in the map, then add it and returns its key - private int putListener(Object listener, Executor e, NsdServiceInfo s) { - checkListener(listener); - final int key; + private int putListener(Object listener, Executor e, NsdServiceInfo serviceInfo) { synchronized (mMapLock) { - int valueIndex = mListenerMap.indexOfValue(listener); + return putListener(listener, e, mServiceMap, serviceInfo); + } + } + + private int putListener(Object listener, Executor e, DiscoveryRequest discoveryRequest) { + synchronized (mMapLock) { + return putListener(listener, e, mDiscoveryMap, discoveryRequest); + } + } + + // Assert that the listener is not in the map, then add it and returns its key + private int putListener(Object listener, Executor e, SparseArray map, T value) { + synchronized (mMapLock) { + checkListener(listener); + final int key; + final int valueIndex = mListenerMap.indexOfValue(listener); if (valueIndex != -1) { throw new IllegalArgumentException("listener already in use"); } key = nextListenerKey(); mListenerMap.put(key, listener); - mServiceMap.put(key, s); + map.put(key, value); mExecutorMap.put(key, e); + return key; } - return key; } private int updateRegisteredListener(Object listener, Executor e, NsdServiceInfo s) { @@ -1148,6 +1176,7 @@ public final class NsdManager { synchronized (mMapLock) { mListenerMap.remove(key); mServiceMap.remove(key); + mDiscoveryMap.remove(key); mExecutorMap.remove(key); } } @@ -1163,9 +1192,9 @@ public final class NsdManager { } } - private static String getNsdServiceInfoType(NsdServiceInfo s) { - if (s == null) return "?"; - return s.getServiceType(); + private static String getNsdServiceInfoType(DiscoveryRequest r) { + if (r == null) return "?"; + return r.getServiceType(); } /** @@ -1406,15 +1435,44 @@ public final class NsdManager { if (TextUtils.isEmpty(serviceType)) { throw new IllegalArgumentException("Service type cannot be empty"); } - checkProtocol(protocolType); + DiscoveryRequest request = new DiscoveryRequest.Builder(protocolType, serviceType) + .setNetwork(network).build(); + discoverServices(request, executor, listener); + } - NsdServiceInfo s = new NsdServiceInfo(); - s.setServiceType(serviceType); - s.setNetwork(network); - - int key = putListener(listener, executor, s); + /** + * Initiates service discovery to browse for instances of a service type. Service discovery + * consumes network bandwidth and will continue until the application calls + * {@link #stopServiceDiscovery}. + * + *

The function call immediately returns after sending a request to start service + * discovery to the framework. The application is notified of a success to initiate + * discovery through the callback {@link DiscoveryListener#onDiscoveryStarted} or a failure + * through {@link DiscoveryListener#onStartDiscoveryFailed}. + * + *

Upon successful start, application is notified when a service is found with + * {@link DiscoveryListener#onServiceFound} or when a service is lost with + * {@link DiscoveryListener#onServiceLost}. + * + *

Upon failure to start, service discovery is not active and application does + * not need to invoke {@link #stopServiceDiscovery} + * + *

The application should call {@link #stopServiceDiscovery} when discovery of this + * service type is no longer required, and/or whenever the application is paused or + * stopped. + * + * @param discoveryRequest the {@link DiscoveryRequest} object which specifies the discovery + * parameters such as service type, subtype and network + * @param executor Executor to run listener callbacks with + * @param listener The listener notifies of a successful discovery and is used + * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}. + */ + @FlaggedApi(Flags.NSD_SUBTYPES_SUPPORT_ENABLED) + public void discoverServices(@NonNull DiscoveryRequest discoveryRequest, + @NonNull Executor executor, @NonNull DiscoveryListener listener) { + int key = putListener(listener, executor, discoveryRequest); try { - mService.discoverServices(key, s); + mService.discoverServices(key, discoveryRequest); } catch (RemoteException e) { e.rethrowFromSystemServer(); } @@ -1465,12 +1523,10 @@ public final class NsdManager { throw new IllegalArgumentException("Service type cannot be empty"); } Objects.requireNonNull(networkRequest, "NetworkRequest cannot be null"); - checkProtocol(protocolType); + DiscoveryRequest discoveryRequest = + new DiscoveryRequest.Builder(protocolType, serviceType).build(); - NsdServiceInfo s = new NsdServiceInfo(); - s.setServiceType(serviceType); - - final int baseListenerKey = putListener(listener, executor, s); + final int baseListenerKey = putListener(listener, executor, discoveryRequest); final PerNetworkDiscoveryTracker discoveryInfo = new PerNetworkDiscoveryTracker( serviceType, protocolType, executor, listener); @@ -1602,6 +1658,7 @@ public final class NsdManager { * @param executor Executor to run callbacks with * @param listener to receive callback upon service update */ + // TODO: use {@link DiscoveryRequest} to specify the service to be subscribed public void registerServiceInfoCallback(@NonNull NsdServiceInfo serviceInfo, @NonNull Executor executor, @NonNull ServiceInfoCallback listener) { checkServiceInfo(serviceInfo); @@ -1643,7 +1700,7 @@ public final class NsdManager { Objects.requireNonNull(listener, "listener cannot be null"); } - private static void checkProtocol(int protocolType) { + static void checkProtocol(int protocolType) { if (protocolType != PROTOCOL_DNS_SD) { throw new IllegalArgumentException("Unsupported protocol"); } diff --git a/framework/aidl-export/android/net/nsd/DiscoveryRequest.aidl b/framework/aidl-export/android/net/nsd/DiscoveryRequest.aidl new file mode 100644 index 0000000000..481a066bf6 --- /dev/null +++ b/framework/aidl-export/android/net/nsd/DiscoveryRequest.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2023 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.net.nsd; + +@JavaOnlyStableParcelable parcelable DiscoveryRequest; diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp index 3d53d6ca2c..f0e0ae87b4 100644 --- a/tests/cts/net/Android.bp +++ b/tests/cts/net/Android.bp @@ -54,6 +54,7 @@ java_defaults { "junit", "junit-params", "modules-utils-build", + "net-tests-utils", "net-utils-framework-common", "truth", "TetheringIntegrationTestsBaseLib", diff --git a/tests/cts/net/src/android/net/cts/DiscoveryRequestTest.kt b/tests/cts/net/src/android/net/cts/DiscoveryRequestTest.kt new file mode 100644 index 0000000000..909a5bc359 --- /dev/null +++ b/tests/cts/net/src/android/net/cts/DiscoveryRequestTest.kt @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2023 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.net.cts + +import android.net.Network +import android.net.nsd.DiscoveryRequest +import android.os.Build +import androidx.test.filters.SmallTest +import com.android.testutils.ConnectivityModuleTest +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo +import com.android.testutils.DevSdkIgnoreRunner +import com.android.testutils.assertParcelingIsLossless +import com.android.testutils.assertThrows +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals +import org.junit.Test +import org.junit.runner.RunWith + +/** CTS tests for {@link DiscoveryRequest}. */ +@IgnoreUpTo(Build.VERSION_CODES.S_V2) +@RunWith(DevSdkIgnoreRunner::class) +@SmallTest +@ConnectivityModuleTest +class DiscoveryRequestTest { + @Test + fun testParcelingIsLossLess() { + val requestWithNullFields = + DiscoveryRequest.Builder("_ipps._tcp").build() + val requestWithAllFields = + DiscoveryRequest.Builder("_ipps._tcp") + .setSubtype("_xyz") + .setNetwork(Network(1)) + .build() + + assertParcelingIsLossless(requestWithNullFields) + assertParcelingIsLossless(requestWithAllFields) + } + + @Test + fun testBuilder_success() { + val request = DiscoveryRequest.Builder("_ipps._tcp") + .setSubtype("_xyz") + .setNetwork(Network(1)) + .build() + + assertEquals("_ipps._tcp", request.serviceType) + assertEquals("_xyz", request.subtype) + assertEquals(Network(1), request.network) + } + + @Test + fun testBuilderConstructor_emptyServiceType_throwsIllegalArgument() { + assertThrows(IllegalArgumentException::class.java) { + DiscoveryRequest.Builder("") + } + } + + @Test + fun testEquality() { + val request1 = DiscoveryRequest.Builder("_ipps._tcp").build() + val request2 = DiscoveryRequest.Builder("_ipps._tcp").build() + val request3 = DiscoveryRequest.Builder("_ipps._tcp") + .setSubtype("_xyz") + .setNetwork(Network(1)) + .build() + val request4 = DiscoveryRequest.Builder("_ipps._tcp") + .setSubtype("_xyz") + .setNetwork(Network(1)) + .build() + + assertEquals(request1, request2) + assertEquals(request3, request4) + assertNotEquals(request1, request3) + assertNotEquals(request2, request4) + } +} diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt index e433442994..8f9f8c7043 100644 --- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt +++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt @@ -53,6 +53,7 @@ import android.net.cts.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.Ser import android.net.cts.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.ServiceUpdatedLost import android.net.cts.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.UnregisterCallbackSucceeded import android.net.cts.util.CtsNetUtils +import android.net.nsd.DiscoveryRequest import android.net.nsd.NsdManager import android.net.nsd.NsdServiceInfo import android.net.nsd.OffloadEngine @@ -113,6 +114,7 @@ import java.util.concurrent.Executor import kotlin.math.min import kotlin.test.assertEquals import kotlin.test.assertFailsWith +import kotlin.test.assertNotEquals import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.fail @@ -1042,9 +1044,11 @@ class NsdManagerTest { nsdManager.discoverServices("_subtype1.$serviceType", NsdManager.PROTOCOL_DNS_SD, testNetwork1.network, Executor { it.run() }, subtype1DiscoveryRecord) - nsdManager.discoverServices("_subtype2.$serviceType", - NsdManager.PROTOCOL_DNS_SD, - testNetwork1.network, Executor { it.run() }, subtype2DiscoveryRecord) + + nsdManager.discoverServices( + DiscoveryRequest.Builder(serviceType).setSubtype("_subtype2") + .setNetwork(testNetwork1.network).build(), + Executor { it.run() }, subtype2DiscoveryRecord) val info1 = subtype1DiscoveryRecord.waitForServiceDiscovered( serviceName, serviceType, testNetwork1.network) @@ -1098,6 +1102,28 @@ class NsdManagerTest { } } + @Test + fun testSubtypeDiscovery_typeMatchButSubtypeNotMatch_notDiscovered() { + val si1 = makeTestServiceInfo(network = testNetwork1.network).apply { + serviceType += ",_subtype1" + } + val registrationRecord = NsdRegistrationRecord() + val subtype2DiscoveryRecord = NsdDiscoveryRecord() + tryTest { + registerService(registrationRecord, si1) + val request = DiscoveryRequest.Builder(serviceType) + .setSubtype("_subtype2").setNetwork(testNetwork1.network).build() + nsdManager.discoverServices(request, { it.run() }, subtype2DiscoveryRecord) + subtype2DiscoveryRecord.expectCallback() + subtype2DiscoveryRecord.assertNoCallback(timeoutMs = 2000) + } cleanupStep { + nsdManager.stopServiceDiscovery(subtype2DiscoveryRecord) + subtype2DiscoveryRecord.expectCallback() + } cleanup { + nsdManager.unregisterService(registrationRecord) + } + } + @Test fun testSubtypeAdvertising_tooManySubtypes_returnsFailureBadParameters() { val si = makeTestServiceInfo(network = testNetwork1.network) diff --git a/tests/unit/java/android/net/nsd/NsdManagerTest.java b/tests/unit/java/android/net/nsd/NsdManagerTest.java index 461ead8ad6..aabe8d3423 100644 --- a/tests/unit/java/android/net/nsd/NsdManagerTest.java +++ b/tests/unit/java/android/net/nsd/NsdManagerTest.java @@ -285,6 +285,7 @@ public class NsdManagerTest { private void doTestDiscoverService() throws Exception { NsdManager manager = mManager; + DiscoveryRequest request1 = new DiscoveryRequest.Builder("a_type").build(); NsdServiceInfo reply1 = new NsdServiceInfo("a_name", "a_type"); NsdServiceInfo reply2 = new NsdServiceInfo("another_name", "a_type"); NsdServiceInfo reply3 = new NsdServiceInfo("a_third_name", "a_type"); @@ -305,7 +306,7 @@ public class NsdManagerTest { int key2 = getRequestKey(req -> verify(mServiceConn, times(2)).discoverServices(req.capture(), any())); - mCallback.onDiscoverServicesStarted(key2, reply1); + mCallback.onDiscoverServicesStarted(key2, request1); verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStarted("a_type"); @@ -345,7 +346,7 @@ public class NsdManagerTest { int key3 = getRequestKey(req -> verify(mServiceConn, times(3)).discoverServices(req.capture(), any())); - mCallback.onDiscoverServicesStarted(key3, reply1); + mCallback.onDiscoverServicesStarted(key3, request1); verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStarted("a_type"); // Client unregisters immediately, it fails