Merge "Introduce NsdManager API for in-place service updates without re-announcement." into main

This commit is contained in:
Remi NGUYEN VAN 2023-12-27 08:26:54 +00:00 • committed by Gerrit Code Review
commit 8c22b9a37c
8 changed files with 490 additions and 43 deletions

View file

@ -0,0 +1,180 @@
/*
* 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.LongDef;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
* Encapsulates parameters for {@link NsdManager#registerService}.
* @hide
*/
//@FlaggedApi(NsdManager.Flags.ADVERTISE_REQUEST_API)
public final class AdvertisingRequest implements Parcelable {
/**
* Only update the registration without sending exit and re-announcement.
*/
public static final int NSD_ADVERTISING_UPDATE_ONLY = 1;
@NonNull
public static final Creator<AdvertisingRequest> CREATOR =
new Creator<>() {
@Override
public AdvertisingRequest createFromParcel(Parcel in) {
final NsdServiceInfo serviceInfo = in.readParcelable(
NsdServiceInfo.class.getClassLoader(), NsdServiceInfo.class);
final int protocolType = in.readInt();
final long advertiseConfig = in.readLong();
return new AdvertisingRequest(serviceInfo, protocolType, advertiseConfig);
}
@Override
public AdvertisingRequest[] newArray(int size) {
return new AdvertisingRequest[size];
}
};
@NonNull
private final NsdServiceInfo mServiceInfo;
private final int mProtocolType;
// Bitmask of @AdvertisingConfig flags. Uses a long to allow 64 possible flags in the future.
private final long mAdvertisingConfig;
/**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@LongDef(flag = true, prefix = {"NSD_ADVERTISING"}, value = {
NSD_ADVERTISING_UPDATE_ONLY,
})
@interface AdvertisingConfig {}
/**
* The constructor for the advertiseRequest
*/
private AdvertisingRequest(@NonNull NsdServiceInfo serviceInfo, int protocolType,
long advertisingConfig) {
mServiceInfo = serviceInfo;
mProtocolType = protocolType;
mAdvertisingConfig = advertisingConfig;
}
/**
* Returns the {@link NsdServiceInfo}
*/
@NonNull
public NsdServiceInfo getServiceInfo() {
return mServiceInfo;
}
/**
* Returns the service advertise protocol
*/
public int getProtocolType() {
return mProtocolType;
}
/**
* Returns the advertising config.
*/
public long getAdvertisingConfig() {
return mAdvertisingConfig;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("serviceInfo: ").append(mServiceInfo)
.append(", protocolType: ").append(mProtocolType)
.append(", advertisingConfig: ").append(mAdvertisingConfig);
return sb.toString();
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
} else if (!(other instanceof AdvertisingRequest)) {
return false;
} else {
final AdvertisingRequest otherRequest = (AdvertisingRequest) other;
return mServiceInfo.equals(otherRequest.mServiceInfo)
&& mProtocolType == otherRequest.mProtocolType
&& mAdvertisingConfig == otherRequest.mAdvertisingConfig;
}
}
@Override
public int hashCode() {
return Objects.hash(mServiceInfo, mProtocolType, mAdvertisingConfig);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeParcelable(mServiceInfo, flags);
dest.writeInt(mProtocolType);
dest.writeLong(mAdvertisingConfig);
}
// @FlaggedApi(NsdManager.Flags.ADVERTISE_REQUEST_API)
/**
* The builder for creating new {@link AdvertisingRequest} objects.
* @hide
*/
public static final class Builder {
@NonNull
private final NsdServiceInfo mServiceInfo;
private final int mProtocolType;
private long mAdvertisingConfig;
/**
* Creates a new {@link Builder} object.
*/
public Builder(@NonNull NsdServiceInfo serviceInfo, int protocolType) {
mServiceInfo = serviceInfo;
mProtocolType = protocolType;
}
/**
* Sets advertising configuration flags.
*
* @param advertisingConfigFlags Bitmask of {@code AdvertisingConfig} flags.
*/
@NonNull
public Builder setAdvertisingConfig(long advertisingConfigFlags) {
mAdvertisingConfig = advertisingConfigFlags;
return this;
}
/** Creates a new {@link AdvertisingRequest} object. */
@NonNull
public AdvertisingRequest build() {
return new AdvertisingRequest(mServiceInfo, mProtocolType, mAdvertisingConfig);
}
}
}

View file

@ -16,6 +16,7 @@
package android.net.nsd;
import android.net.nsd.AdvertisingRequest;
import android.net.nsd.INsdManagerCallback;
import android.net.nsd.IOffloadEngine;
import android.net.nsd.NsdServiceInfo;
@ -27,7 +28,7 @@ import android.os.Messenger;
* {@hide}
*/
interface INsdServiceConnector {
void registerService(int listenerKey, in NsdServiceInfo serviceInfo);
void registerService(int listenerKey, in AdvertisingRequest advertisingRequest);
void unregisterService(int listenerKey);
void discoverServices(int listenerKey, in NsdServiceInfo serviceInfo);
void stopDiscovery(int listenerKey);

View file

@ -46,6 +46,7 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@ -57,6 +58,8 @@ import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* The Network Service Discovery Manager class provides the API to discover services
@ -152,8 +155,37 @@ public final class NsdManager {
"com.android.net.flags.register_nsd_offload_engine_api";
static final String NSD_SUBTYPES_SUPPORT_ENABLED =
"com.android.net.flags.nsd_subtypes_support_enabled";
static final String ADVERTISE_REQUEST_API =
"com.android.net.flags.advertise_request_api";
}
/**
* A regex for the acceptable format of a type or subtype label.
* @hide
*/
public static final String TYPE_SUBTYPE_LABEL_REGEX = "_[a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]";
/**
* A regex for the acceptable format of a service type specification.
*
* When it matches, matcher group 1 is an optional leading subtype when using legacy dot syntax
* (_subtype._type._tcp). Matcher group 2 is the actual type, and matcher group 3 contains
* optional comma-separated subtypes.
* @hide
*/
public static final String TYPE_REGEX =
// Optional leading subtype (_subtype._type._tcp)
// (?: xxx) is a non-capturing parenthesis, don't capture the dot
"^(?:(" + TYPE_SUBTYPE_LABEL_REGEX + ")\\.)?"
// Actual type (_type._tcp.local)
+ "(" + TYPE_SUBTYPE_LABEL_REGEX + "\\._(?:tcp|udp))"
// Drop '.' at the end of service type that is compatible with old backend.
// e.g. allow "_type._tcp.local."
+ "\\.?"
// Optional subtype after comma, for "_type._tcp,_subtype1,_subtype2" format
+ "((?:," + TYPE_SUBTYPE_LABEL_REGEX + ")*)"
+ "$";
/**
* Broadcast intent action to indicate whether network service discovery is
* enabled or disabled. An extra {@link #EXTRA_NSD_STATE} provides the state
@ -1098,6 +1130,16 @@ public final class NsdManager {
return key;
}
private int updateRegisteredListener(Object listener, Executor e, NsdServiceInfo s) {
final int key;
synchronized (mMapLock) {
key = getListenerKey(listener);
mServiceMap.put(key, s);
mExecutorMap.put(key, e);
}
return key;
}
private void removeListener(int key) {
synchronized (mMapLock) {
mListenerMap.remove(key);
@ -1162,14 +1204,111 @@ public final class NsdManager {
*/
public void registerService(@NonNull NsdServiceInfo serviceInfo, int protocolType,
@NonNull Executor executor, @NonNull RegistrationListener listener) {
checkServiceInfo(serviceInfo);
checkProtocol(protocolType);
final AdvertisingRequest.Builder builder = new AdvertisingRequest.Builder(serviceInfo,
protocolType);
// Optionally assume that the request is an update request if it uses subtypes and the same
// listener. This is not documented behavior as support for advertising subtypes via
// "_servicename,_sub1,_sub2" has never been documented in the first place, and using
// multiple subtypes was broken in T until a later module update. Subtype registration is
// documented in the NsdServiceInfo.setSubtypes API instead, but this provides a limited
// option for users of the older undocumented behavior, only for subtype changes.
if (isSubtypeUpdateRequest(serviceInfo, listener)) {
builder.setAdvertisingConfig(AdvertisingRequest.NSD_ADVERTISING_UPDATE_ONLY);
}
registerService(builder.build(), executor, listener);
}
private boolean isSubtypeUpdateRequest(@NonNull NsdServiceInfo serviceInfo, @NonNull
RegistrationListener listener) {
// If the listener is the same object, serviceInfo is for the same service name and
// type (outside of subtypes), and either of them use subtypes, treat the request as a
// subtype update request.
synchronized (mMapLock) {
int valueIndex = mListenerMap.indexOfValue(listener);
if (valueIndex == -1) {
return false;
}
final int key = mListenerMap.keyAt(valueIndex);
NsdServiceInfo existingService = mServiceMap.get(key);
if (existingService == null) {
return false;
}
final Pair<String, String> existingTypeSubtype = getTypeAndSubtypes(
existingService.getServiceType());
final Pair<String, String> newTypeSubtype = getTypeAndSubtypes(
serviceInfo.getServiceType());
if (existingTypeSubtype == null || newTypeSubtype == null) {
return false;
}
final boolean existingHasNoSubtype = TextUtils.isEmpty(existingTypeSubtype.second);
final boolean updatedHasNoSubtype = TextUtils.isEmpty(newTypeSubtype.second);
if (existingHasNoSubtype && updatedHasNoSubtype) {
// Only allow subtype changes when subtypes are used. This ensures that this
// behavior does not affect most requests.
return false;
}
return Objects.equals(existingService.getServiceName(), serviceInfo.getServiceName())
&& Objects.equals(existingTypeSubtype.first, newTypeSubtype.first);
}
}
/**
* Get the base type from a type specification with "_type._tcp,sub1,sub2" syntax.
*
* <p>This rejects specifications using dot syntax to specify subtypes ("_sub1._type._tcp").
*
* @return Type and comma-separated list of subtypes, or null if invalid format.
*/
@Nullable
private static Pair<String, String> getTypeAndSubtypes(@NonNull String typeWithSubtype) {
final Matcher matcher = Pattern.compile(TYPE_REGEX).matcher(typeWithSubtype);
if (!matcher.matches()) return null;
// Reject specifications using leading subtypes with a dot
if (!TextUtils.isEmpty(matcher.group(1))) return null;
return new Pair<>(matcher.group(2), matcher.group(3));
}
/**
* Register a service to be discovered by other services.
*
* <p> The function call immediately returns after sending a request to register service
* to the framework. The application is notified of a successful registration
* through the callback {@link RegistrationListener#onServiceRegistered} or a failure
* through {@link RegistrationListener#onRegistrationFailed}.
*
* <p> The application should call {@link #unregisterService} when the service
* registration is no longer required, and/or whenever the application is stopped.
* @param advertisingRequest service being registered
* @param executor Executor to run listener callbacks with
* @param listener The listener notifies of a successful registration and is used to
* unregister this service through a call on {@link #unregisterService}. Cannot be null.
*
* @hide
*/
// @FlaggedApi(Flags.ADVERTISE_REQUEST_API)
public void registerService(@NonNull AdvertisingRequest advertisingRequest,
@NonNull Executor executor,
@NonNull RegistrationListener listener) {
final NsdServiceInfo serviceInfo = advertisingRequest.getServiceInfo();
final int protocolType = advertisingRequest.getProtocolType();
if (serviceInfo.getPort() <= 0) {
throw new IllegalArgumentException("Invalid port number");
}
checkServiceInfo(serviceInfo);
checkProtocol(protocolType);
int key = putListener(listener, executor, serviceInfo);
final int key;
// For update only request, the old listener has to be reused
if ((advertisingRequest.getAdvertisingConfig()
& AdvertisingRequest.NSD_ADVERTISING_UPDATE_ONLY) > 0) {
key = updateRegisteredListener(listener, executor, serviceInfo);
} else {
key = putListener(listener, executor, serviceInfo);
}
try {
mService.registerService(key, serviceInfo);
mService.registerService(key, advertisingRequest);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}

View file

@ -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 AdvertisingRequest;

View file

@ -26,6 +26,8 @@ import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.net.nsd.NsdManager.MDNS_DISCOVERY_MANAGER_EVENT;
import static android.net.nsd.NsdManager.MDNS_SERVICE_EVENT;
import static android.net.nsd.NsdManager.RESOLVE_SERVICE_SUCCEEDED;
import static android.net.nsd.NsdManager.TYPE_REGEX;
import static android.net.nsd.NsdManager.TYPE_SUBTYPE_LABEL_REGEX;
import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
import static com.android.modules.utils.build.SdkLevel.isAtLeastU;
@ -51,6 +53,7 @@ import android.net.mdns.aidl.GetAddressInfo;
import android.net.mdns.aidl.IMDnsEventListener;
import android.net.mdns.aidl.RegistrationInfo;
import android.net.mdns.aidl.ResolutionInfo;
import android.net.nsd.AdvertisingRequest;
import android.net.nsd.INsdManager;
import android.net.nsd.INsdManagerCallback;
import android.net.nsd.INsdServiceConnector;
@ -173,7 +176,7 @@ public class NsdService extends INsdManager.Stub {
"mdns_advertiser_allowlist_";
private static final String MDNS_ALLOWLIST_FLAG_SUFFIX = "_version";
private static final String TYPE_SUBTYPE_LABEL_REGEX = "_[a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]";
@VisibleForTesting
static final String MDNS_CONFIG_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF =
@ -728,12 +731,11 @@ public class NsdService extends INsdManager.Stub {
final ClientInfo clientInfo;
final int transactionId;
final int clientRequestId = msg.arg2;
final ListenerArgs args;
final OffloadEngineInfo offloadEngineInfo;
switch (msg.what) {
case NsdManager.DISCOVER_SERVICES: {
if (DBG) Log.d(TAG, "Discover services");
args = (ListenerArgs) msg.obj;
final ListenerArgs args = (ListenerArgs) msg.obj;
clientInfo = mClients.get(args.connector);
// If the binder death notification for a INsdManagerCallback was received
// before any calls are received by NsdService, the clientInfo would be
@ -809,7 +811,7 @@ public class NsdService extends INsdManager.Stub {
}
case NsdManager.STOP_DISCOVERY: {
if (DBG) Log.d(TAG, "Stop service discovery");
args = (ListenerArgs) msg.obj;
final ListenerArgs args = (ListenerArgs) msg.obj;
clientInfo = mClients.get(args.connector);
// If the binder death notification for a INsdManagerCallback was received
// before any calls are received by NsdService, the clientInfo would be
@ -847,7 +849,7 @@ public class NsdService extends INsdManager.Stub {
}
case NsdManager.REGISTER_SERVICE: {
if (DBG) Log.d(TAG, "Register service");
args = (ListenerArgs) msg.obj;
final AdvertisingArgs args = (AdvertisingArgs) msg.obj;
clientInfo = mClients.get(args.connector);
// If the binder death notification for a INsdManagerCallback was received
// before any calls are received by NsdService, the clientInfo would be
@ -862,9 +864,12 @@ public class NsdService extends INsdManager.Stub {
NsdManager.FAILURE_MAX_LIMIT, true /* isLegacy */);
break;
}
transactionId = getUniqueId();
final NsdServiceInfo serviceInfo = args.serviceInfo;
final AdvertisingRequest advertisingRequest = args.advertisingRequest;
if (advertisingRequest == null) {
Log.e(TAG, "Unknown advertisingRequest in registration");
break;
}
final NsdServiceInfo serviceInfo = advertisingRequest.getServiceInfo();
final String serviceType = serviceInfo.getServiceType();
final Pair<String, List<String>> typeSubtype = parseTypeAndSubtype(
serviceType);
@ -879,6 +884,23 @@ public class NsdService extends INsdManager.Stub {
NsdManager.FAILURE_INTERNAL_ERROR, false /* isLegacy */);
break;
}
boolean isUpdateOnly = (advertisingRequest.getAdvertisingConfig()
& AdvertisingRequest.NSD_ADVERTISING_UPDATE_ONLY) > 0;
// If it is an update request, then reuse the old transactionId
if (isUpdateOnly) {
final ClientRequest existingClientRequest =
clientInfo.mClientRequests.get(clientRequestId);
if (existingClientRequest == null) {
Log.e(TAG, "Invalid update on requestId: " + clientRequestId);
clientInfo.onRegisterServiceFailedImmediately(clientRequestId,
NsdManager.FAILURE_INTERNAL_ERROR,
false /* isLegacy */);
break;
}
transactionId = existingClientRequest.mTransactionId;
} else {
transactionId = getUniqueId();
}
serviceInfo.setServiceType(registerServiceType);
serviceInfo.setServiceName(truncateServiceName(
serviceInfo.getServiceName()));
@ -899,12 +921,16 @@ public class NsdService extends INsdManager.Stub {
serviceInfo.setSubtypes(subtypes);
maybeStartMonitoringSockets();
final MdnsAdvertisingOptions mdnsAdvertisingOptions =
MdnsAdvertisingOptions.newBuilder().setIsOnlyUpdate(
isUpdateOnly).build();
mAdvertiser.addOrUpdateService(transactionId, serviceInfo,
MdnsAdvertisingOptions.newBuilder().build());
mdnsAdvertisingOptions);
storeAdvertiserRequestMap(clientRequestId, transactionId, clientInfo,
serviceInfo.getNetwork());
} else {
maybeStartDaemon();
transactionId = getUniqueId();
if (registerService(transactionId, serviceInfo)) {
if (DBG) {
Log.d(TAG, "Register " + clientRequestId
@ -924,7 +950,7 @@ public class NsdService extends INsdManager.Stub {
}
case NsdManager.UNREGISTER_SERVICE: {
if (DBG) Log.d(TAG, "unregister service");
args = (ListenerArgs) msg.obj;
final ListenerArgs args = (ListenerArgs) msg.obj;
clientInfo = mClients.get(args.connector);
// If the binder death notification for a INsdManagerCallback was received
// before any calls are received by NsdService, the clientInfo would be
@ -967,7 +993,7 @@ public class NsdService extends INsdManager.Stub {
}
case NsdManager.RESOLVE_SERVICE: {
if (DBG) Log.d(TAG, "Resolve service");
args = (ListenerArgs) msg.obj;
final ListenerArgs args = (ListenerArgs) msg.obj;
clientInfo = mClients.get(args.connector);
// If the binder death notification for a INsdManagerCallback was received
// before any calls are received by NsdService, the clientInfo would be
@ -1029,7 +1055,7 @@ public class NsdService extends INsdManager.Stub {
}
case NsdManager.STOP_RESOLUTION: {
if (DBG) Log.d(TAG, "Stop service resolution");
args = (ListenerArgs) msg.obj;
final ListenerArgs args = (ListenerArgs) msg.obj;
clientInfo = mClients.get(args.connector);
// If the binder death notification for a INsdManagerCallback was received
// before any calls are received by NsdService, the clientInfo would be
@ -1068,7 +1094,7 @@ public class NsdService extends INsdManager.Stub {
}
case NsdManager.REGISTER_SERVICE_CALLBACK: {
if (DBG) Log.d(TAG, "Register a service callback");
args = (ListenerArgs) msg.obj;
final ListenerArgs args = (ListenerArgs) msg.obj;
clientInfo = mClients.get(args.connector);
// If the binder death notification for a INsdManagerCallback was received
// before any calls are received by NsdService, the clientInfo would be
@ -1111,7 +1137,7 @@ public class NsdService extends INsdManager.Stub {
}
case NsdManager.UNREGISTER_SERVICE_CALLBACK: {
if (DBG) Log.d(TAG, "Unregister a service callback");
args = (ListenerArgs) msg.obj;
final ListenerArgs args = (ListenerArgs) msg.obj;
clientInfo = mClients.get(args.connector);
// If the binder death notification for a INsdManagerCallback was received
// before any calls are received by NsdService, the clientInfo would be
@ -1638,20 +1664,7 @@ public class NsdService extends INsdManager.Stub {
@Nullable
public static Pair<String, List<String>> parseTypeAndSubtype(String serviceType) {
if (TextUtils.isEmpty(serviceType)) return null;
final String regexString =
// Optional leading subtype (_subtype._type._tcp)
// (?: xxx) is a non-capturing parenthesis, don't capture the dot
"^(?:(" + TYPE_SUBTYPE_LABEL_REGEX + ")\\.)?"
// Actual type (_type._tcp.local)
+ "(" + TYPE_SUBTYPE_LABEL_REGEX + "\\._(?:tcp|udp))"
// Drop '.' at the end of service type that is compatible with old backend.
// e.g. allow "_type._tcp.local."
+ "\\.?"
// Optional subtype after comma, for "_type._tcp,_subtype1,_subtype2" format
+ "((?:," + TYPE_SUBTYPE_LABEL_REGEX + ")*)"
+ "$";
final Pattern serviceTypePattern = Pattern.compile(regexString);
final Pattern serviceTypePattern = Pattern.compile(TYPE_REGEX);
final Matcher matcher = serviceTypePattern.matcher(serviceType);
if (!matcher.matches()) return null;
final String queryType = matcher.group(2);
@ -2079,20 +2092,33 @@ public class NsdService extends INsdManager.Stub {
}
}
private static class AdvertisingArgs {
public final NsdServiceConnector connector;
public final AdvertisingRequest advertisingRequest;
AdvertisingArgs(NsdServiceConnector connector, AdvertisingRequest advertisingRequest) {
this.connector = connector;
this.advertisingRequest = advertisingRequest;
}
}
private class NsdServiceConnector extends INsdServiceConnector.Stub
implements IBinder.DeathRecipient {
@Override
public void registerService(int listenerKey, NsdServiceInfo serviceInfo) {
public void registerService(int listenerKey, AdvertisingRequest advertisingRequest)
throws RemoteException {
mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
NsdManager.REGISTER_SERVICE, 0, listenerKey,
new ListenerArgs(this, serviceInfo)));
new AdvertisingArgs(this, advertisingRequest)
));
}
@Override
public void unregisterService(int listenerKey) {
mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
NsdManager.UNREGISTER_SERVICE, 0, listenerKey,
new ListenerArgs(this, null)));
new ListenerArgs(this, (NsdServiceInfo) null)));
}
@Override
@ -2104,8 +2130,8 @@ public class NsdService extends INsdManager.Stub {
@Override
public void stopDiscovery(int listenerKey) {
mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
NsdManager.STOP_DISCOVERY, 0, listenerKey, new ListenerArgs(this, null)));
mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(NsdManager.STOP_DISCOVERY,
0, listenerKey, new ListenerArgs(this, (NsdServiceInfo) null)));
}
@Override
@ -2117,8 +2143,8 @@ public class NsdService extends INsdManager.Stub {
@Override
public void stopResolution(int listenerKey) {
mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
NsdManager.STOP_RESOLUTION, 0, listenerKey, new ListenerArgs(this, null)));
mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(NsdManager.STOP_RESOLUTION,
0, listenerKey, new ListenerArgs(this, (NsdServiceInfo) null)));
}
@Override
@ -2132,13 +2158,13 @@ public class NsdService extends INsdManager.Stub {
public void unregisterServiceInfoCallback(int listenerKey) {
mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
NsdManager.UNREGISTER_SERVICE_CALLBACK, 0, listenerKey,
new ListenerArgs(this, null)));
new ListenerArgs(this, (NsdServiceInfo) null)));
}
@Override
public void startDaemon() {
mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
NsdManager.DAEMON_STARTUP, new ListenerArgs(this, null)));
mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(NsdManager.DAEMON_STARTUP,
new ListenerArgs(this, (NsdServiceInfo) null)));
}
@Override

View file

@ -541,6 +541,9 @@ public class MdnsServiceTypeClient {
}
if (response.isComplete()) {
// There is a bug here: the newServiceFound is global right now. The state needs
// to be per listener because of the responseMatchesOptions() filter.
// Otherwise, it won't handle the subType update properly.
if (newServiceFound || serviceBecomesComplete) {
sharedLog.log("onServiceFound: " + serviceInfo);
listener.onServiceFound(serviceInfo, false /* isServiceFromCache */);

View file

@ -1063,6 +1063,32 @@ class NsdManagerTest {
}
}
@Test
fun testMultipleSubTypeAdvertisingAndDiscovery_withUpdate() {
val si1 = makeTestServiceInfo(network = testNetwork1.network).apply {
serviceType += ",_subtype1"
}
val si2 = makeTestServiceInfo(network = testNetwork1.network).apply {
serviceType += ",_subtype2"
}
val registrationRecord = NsdRegistrationRecord()
val subtype3DiscoveryRecord = NsdDiscoveryRecord()
tryTest {
registerService(registrationRecord, si1)
updateService(registrationRecord, si2)
nsdManager.discoverServices("_subtype2.$serviceType",
NsdManager.PROTOCOL_DNS_SD, testNetwork1.network,
{ it.run() }, subtype3DiscoveryRecord)
subtype3DiscoveryRecord.waitForServiceDiscovered(serviceName,
serviceType, testNetwork1.network)
} cleanupStep {
nsdManager.stopServiceDiscovery(subtype3DiscoveryRecord)
subtype3DiscoveryRecord.expectCallback<DiscoveryStopped>()
} cleanup {
nsdManager.unregisterService(registrationRecord)
}
}
@Test
fun testSubtypeAdvertising_tooManySubtypes_returnsFailureBadParameters() {
val si = makeTestServiceInfo(network = testNetwork1.network)
@ -1404,6 +1430,18 @@ class NsdManagerTest {
return cb.serviceInfo
}
/**
* Update a service.
*/
private fun updateService(
record: NsdRegistrationRecord,
si: NsdServiceInfo,
executor: Executor = Executor { it.run() }
) {
nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, executor, record)
// TODO: add the callback check for the update.
}
private fun resolveService(discoveredInfo: NsdServiceInfo): NsdServiceInfo {
val record = NsdResolveRecord()
nsdManager.resolveService(discoveredInfo, Executor { it.run() }, record)

View file

@ -196,6 +196,22 @@ public class NsdManagerTest {
verify(listener2, timeout(mTimeoutMs).times(1)).onServiceResolved(reply);
}
@Test
public void testRegisterServiceWithAdvertisingRequest() throws Exception {
final NsdManager manager = mManager;
final NsdServiceInfo request = new NsdServiceInfo("another_name2", "another_type2");
request.setPort(2203);
final AdvertisingRequest advertisingRequest = new AdvertisingRequest.Builder(request,
PROTOCOL).build();
final NsdManager.RegistrationListener listener = mock(
NsdManager.RegistrationListener.class);
manager.registerService(advertisingRequest, Runnable::run, listener);
int key4 = getRequestKey(req -> verify(mServiceConn).registerService(req.capture(), any()));
mCallback.onRegisterServiceSucceeded(key4, request);
verify(listener, timeout(mTimeoutMs).times(1)).onServiceRegistered(request);
}
private void doTestRegisterService() throws Exception {
NsdManager manager = mManager;
@ -346,8 +362,19 @@ public class NsdManagerTest {
NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class);
NsdServiceInfo invalidService = new NsdServiceInfo(null, null);
NsdServiceInfo validService = new NsdServiceInfo("a_name", "a_type");
NsdServiceInfo validService = new NsdServiceInfo("a_name", "_a_type._tcp");
NsdServiceInfo otherServiceWithSubtype = new NsdServiceInfo("b_name", "_a_type._tcp,_sub1");
NsdServiceInfo validServiceDuplicate = new NsdServiceInfo("a_name", "_a_type._tcp");
NsdServiceInfo validServiceSubtypeUpdate = new NsdServiceInfo("a_name",
"_a_type._tcp,_sub1,_s2");
NsdServiceInfo otherSubtypeUpdate = new NsdServiceInfo("a_name", "_a_type._tcp,_sub1,_s3");
NsdServiceInfo dotSyntaxSubtypeUpdate = new NsdServiceInfo("a_name", "_sub1._a_type._tcp");
validService.setPort(2222);
otherServiceWithSubtype.setPort(2222);
validServiceDuplicate.setPort(2222);
validServiceSubtypeUpdate.setPort(2222);
otherSubtypeUpdate.setPort(2222);
dotSyntaxSubtypeUpdate.setPort(2222);
// Service registration
// - invalid arguments
@ -358,7 +385,21 @@ public class NsdManagerTest {
mustFail(() -> { manager.registerService(validService, -1, listener1); });
mustFail(() -> { manager.registerService(validService, PROTOCOL, null); });
manager.registerService(validService, PROTOCOL, listener1);
// - listener already registered
// - update without subtype is not allowed
mustFail(() -> { manager.registerService(validServiceDuplicate, PROTOCOL, listener1); });
// - update with subtype is allowed
manager.registerService(validServiceSubtypeUpdate, PROTOCOL, listener1);
// - re-updating to the same subtype is allowed
manager.registerService(validServiceSubtypeUpdate, PROTOCOL, listener1);
// - updating to other subtypes is allowed
manager.registerService(otherSubtypeUpdate, PROTOCOL, listener1);
// - update back to the service without subtype is allowed
manager.registerService(validService, PROTOCOL, listener1);
// - updating to a subtype with _sub._type syntax is not allowed
mustFail(() -> { manager.registerService(dotSyntaxSubtypeUpdate, PROTOCOL, listener1); });
// - updating to a different service name is not allowed
mustFail(() -> { manager.registerService(otherServiceWithSubtype, PROTOCOL, listener1); });
// - listener already registered, and not using subtypes
mustFail(() -> { manager.registerService(validService, PROTOCOL, listener1); });
manager.unregisterService(listener1);
// TODO: make listener immediately reusable