Merge "Introduce NsdManager API for in-place service updates without re-announcement." into main
This commit is contained in:
commit
8c22b9a37c
8 changed files with 490 additions and 43 deletions
180
framework-t/src/android/net/nsd/AdvertisingRequest.java
Normal file
180
framework-t/src/android/net/nsd/AdvertisingRequest.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 */);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue