Provide offload service information update from DiscoveryManager

With this change, the OffloadEngine can receive updates when
offload service information changes from DiscoveryManager. This
allows the OffloadEngine to maintain offload services and to
adjust its behavior accordingly.

Bug: 410734687
Test: atest ConnectivityCoverageTests
Flag: com.android.tethering.mainline.beta.nsd_selective_mdns_response_offload
Change-Id: I6c6ba4e76b4a2a94cbfef37bec9d6f87357bec5f
This commit is contained in:
Paul Hu 2025-05-13 07:28:59 +00:00
commit f1ae69a950
9 changed files with 198 additions and 46 deletions

View file

@ -27,4 +27,11 @@ flag {
namespace: "com_android_tethering"
description: "The flag enables the Tethering and WI-Fi P2p Group Owner mode local network agent."
bug: "420590461"
}
flag {
name: "nsd_selective_mdns_response_offload"
namespace: "com_android_tethering"
description: "A flag to control whether the selective mDNS response offload should be enabled."
bug: "410734687"
}

View file

@ -43,6 +43,7 @@ import static com.android.server.connectivity.mdns.MdnsRecord.MAX_LABEL_LENGTH;
import static com.android.server.connectivity.mdns.MdnsSearchOptions.AGGRESSIVE_QUERY_MODE;
import static com.android.server.connectivity.mdns.MdnsSearchOptions.PASSIVE_QUERY_MODE;
import static com.android.server.connectivity.mdns.util.MdnsUtils.Clock;
import static com.android.server.connectivity.mdns.util.MdnsUtils.createOffloadServiceInfoFromFilterReplies;
import android.annotation.NonNull;
import android.annotation.Nullable;
@ -116,6 +117,7 @@ import com.android.server.connectivity.mdns.MdnsServiceTypeClient.FilterRepliesI
import com.android.server.connectivity.mdns.MdnsSocketProvider;
import com.android.server.connectivity.mdns.OffloadCallback;
import com.android.server.connectivity.mdns.util.MdnsUtils;
import com.android.tethering.mainline.beta.Flags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@ -1991,6 +1993,8 @@ public class NsdService extends INsdManager.Stub {
mContext, MdnsFeatureFlags.NSD_CACHE_FLUSH_PER_ADDRESS_TYPE))
.setIsIgnoreTemporaryIPv6AddressesEnabled(mDeps.isTetheringFeatureNotChickenedOut(
mContext, MdnsFeatureFlags.NSD_IGNORE_TEMPORARY_IPV6_ADDRESSES))
.setIsSelectiveMdnsResponseOffloadEnabled(mDeps.isAconfigFlagEnabled(
Flags.FLAG_NSD_SELECTIVE_MDNS_RESPONSE_OFFLOAD))
.setOverrideProvider(new MdnsFeatureFlags.FlagOverrideProvider() {
@Override
public boolean isForceEnabledForTest(@NonNull String flag) {
@ -2006,16 +2010,17 @@ public class NsdService extends INsdManager.Stub {
}
})
.build();
final MdnsOffloadCallback offloadCallback = new MdnsOffloadCallback();
mMdnsSocketClient =
new MdnsMultinetworkSocketClient(handler.getLooper(), mMdnsSocketProvider,
LOGGER.forSubComponent("MdnsMultinetworkSocketClient"), mMdnsFeatureFlags);
mMdnsDiscoveryManager = deps.makeMdnsDiscoveryManager(new ExecutorProvider(),
mMdnsSocketClient, LOGGER.forSubComponent("MdnsDiscoveryManager"),
mMdnsFeatureFlags);
mMdnsFeatureFlags, offloadCallback);
handler.post(() -> mMdnsSocketClient.setCallback(mMdnsDiscoveryManager));
mAdvertiser = deps.makeMdnsAdvertiser(handler.getLooper(), mMdnsSocketProvider,
new AdvertiserCallback(), LOGGER.forSubComponent("MdnsAdvertiser"),
mMdnsFeatureFlags, mContext, new MdnsOffloadCallback());
mMdnsFeatureFlags, mContext, offloadCallback);
mClock = deps.makeClock();
}
@ -2070,6 +2075,15 @@ public class NsdService extends INsdManager.Stub {
return DeviceConfigUtils.isTetheringFeatureNotChickenedOut(context, feature);
}
/** Get whether a feature config is enabled. */
public boolean isAconfigFlagEnabled(String feature) {
return switch (feature) {
case Flags.FLAG_NSD_SELECTIVE_MDNS_RESPONSE_OFFLOAD ->
Flags.nsdSelectiveMdnsResponseOffload();
default -> throw new IllegalStateException("Unknown flag " + feature);
};
}
/**
* @see DeviceConfigUtils#getDeviceConfigPropertyInt
*/
@ -2084,9 +2098,9 @@ public class NsdService extends INsdManager.Stub {
public MdnsDiscoveryManager makeMdnsDiscoveryManager(
@NonNull ExecutorProvider executorProvider,
@NonNull MdnsMultinetworkSocketClient socketClient, @NonNull SharedLog sharedLog,
@NonNull MdnsFeatureFlags featureFlags) {
@NonNull MdnsFeatureFlags featureFlags, @NonNull OffloadCallback cb) {
return new MdnsDiscoveryManager(
executorProvider, socketClient, sharedLog, featureFlags);
executorProvider, socketClient, sharedLog, featureFlags, cb);
}
/**
@ -2224,27 +2238,6 @@ public class NsdService extends INsdManager.Stub {
}
}
/**
* Creates an {@link OffloadServiceInfo} object from a {@link FilterRepliesInfo} instance.
* This method facilitates the conversion of filtering criteria into a service information
* object suitable for offloading mechanisms.
*
* @param info The {@link FilterRepliesInfo} containing the filtering criteria.
* @return A new {@link OffloadServiceInfo} instance populated with data from the
* {@code FilterRepliesInfo}.
*/
@VisibleForTesting
static OffloadServiceInfo createOffloadServiceInfoFromFilterReplies(
@NonNull FilterRepliesInfo info) {
return new OffloadServiceInfo(
new OffloadServiceInfo.Key(info.serviceName, info.serviceType),
new ArrayList<>(info.subtypes),
info.hostname,
null /* offloadPayload */,
0 /* priority */,
OffloadEngine.OFFLOAD_TYPE_FILTER_REPLIES);
}
private void sendAllOffloadServiceInfos(@NonNull OffloadEngineInfo offloadEngineInfo) {
final String targetInterface = offloadEngineInfo.mInterfaceName;
final IOffloadEngine offloadEngine = offloadEngineInfo.mOffloadEngine;

View file

@ -51,6 +51,7 @@ public class MdnsDiscoveryManager implements MdnsSocketClientBase.Callback {
@NonNull private final PerSocketServiceTypeClients perSocketServiceTypeClients;
@NonNull private final DiscoveryExecutor discoveryExecutor;
@NonNull private final MdnsFeatureFlags mdnsFeatureFlags;
@NonNull private final OffloadCallback offloadCallback;
// Only accessed on the handler thread, initialized before first use
@Nullable
@ -131,13 +132,15 @@ public class MdnsDiscoveryManager implements MdnsSocketClientBase.Callback {
public MdnsDiscoveryManager(@NonNull ExecutorProvider executorProvider,
@NonNull MdnsSocketClientBase socketClient, @NonNull SharedLog sharedLog,
@NonNull MdnsFeatureFlags mdnsFeatureFlags) {
@NonNull MdnsFeatureFlags mdnsFeatureFlags,
@NonNull OffloadCallback offloadCallback) {
this.executorProvider = executorProvider;
this.socketClient = socketClient;
this.sharedLog = sharedLog;
this.perSocketServiceTypeClients = new PerSocketServiceTypeClients();
this.mdnsFeatureFlags = mdnsFeatureFlags;
this.discoveryExecutor = new DiscoveryExecutor(socketClient.getLooper(), mdnsFeatureFlags);
this.offloadCallback = offloadCallback;
}
/**
@ -387,7 +390,8 @@ public class MdnsDiscoveryManager implements MdnsSocketClientBase.Callback {
return new MdnsServiceTypeClient(
serviceType, socketClient,
executorProvider.newServiceTypeClientSchedulerExecutor(), socketKey,
sharedLog.forSubComponent(tag), looper, serviceCache, mdnsFeatureFlags);
sharedLog.forSubComponent(tag), looper, serviceCache, mdnsFeatureFlags,
offloadCallback);
}
private List<MdnsServiceTypeClient> getMdnsServiceTypeClientByInterfaceName(

View file

@ -17,6 +17,9 @@ package com.android.server.connectivity.mdns;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Build;
import androidx.annotation.ChecksSdkIntAtLeast;
/**
* The class that contains mDNS feature flags;
@ -180,6 +183,11 @@ public class MdnsFeatureFlags {
// Flag for ignoring temporary IPv6 addresses in advertising
public final boolean mIsIgnoreTemporaryIPv6AddressesEnabled;
// Flag for selective mDns response offload
// This feature can only be enabled if the OffloadServiceInfo system API is available
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU)
public final boolean mIsSelectiveMdnsResponseOffloadEnabled;
// Thread stats tag for MdnsSocketClient
public final int mMdnsSocketThreadStatsTag;
@ -319,6 +327,7 @@ public class MdnsFeatureFlags {
boolean isCacheFlushPerAddressTypeEnabled,
boolean isOptimizedExpiredServiceRemovalEnabled,
boolean isIgnoreTemporaryIPv6AddressesEnabled,
boolean isSelectiveMdnsResponseOffloadEnabled,
int mdnsSocketThreadStatsTag,
@Nullable FlagOverrideProvider overrideProvider) {
mIsMdnsOffloadFeatureEnabled = isOffloadFeatureEnabled;
@ -339,6 +348,7 @@ public class MdnsFeatureFlags {
mIsOptimizedExpiredServiceRemovalEnabled = isOptimizedExpiredServiceRemovalEnabled;
mMdnsSocketThreadStatsTag = mdnsSocketThreadStatsTag;
mIsIgnoreTemporaryIPv6AddressesEnabled = isIgnoreTemporaryIPv6AddressesEnabled;
mIsSelectiveMdnsResponseOffloadEnabled = isSelectiveMdnsResponseOffloadEnabled;
mOverrideProvider = overrideProvider;
}
@ -368,6 +378,7 @@ public class MdnsFeatureFlags {
private boolean mIsCacheFlushPerAddressTypeEnabled;
private boolean mIsOptimizedExpiredServiceRemovalEnabled;
private boolean mIsIgnoreTemporaryIPv6AddressesEnabled;
private boolean mIsSelectiveMdnsResponseOffloadEnabled;
private int mMdnsSocketThreadStatsTag;
private FlagOverrideProvider mOverrideProvider;
@ -392,6 +403,7 @@ public class MdnsFeatureFlags {
mIsCacheFlushPerAddressTypeEnabled = true; // Default enabled.
mIsOptimizedExpiredServiceRemovalEnabled = false;
mIsIgnoreTemporaryIPv6AddressesEnabled = true; // Default enabled.
mIsSelectiveMdnsResponseOffloadEnabled = false;
mMdnsSocketThreadStatsTag = MDNS_SOCKET_THREAD_STATS_TAG_NONE;
mOverrideProvider = null;
}
@ -588,6 +600,17 @@ public class MdnsFeatureFlags {
return this;
}
/**
* Set whether the selective mDns response offload is enabled.
*
* @see #NSD_SELECTIVE_MDNS_RESPONSE_OFFLOAD
*/
public Builder setIsSelectiveMdnsResponseOffloadEnabled(
boolean isSelectiveMdnsResponseOffloadEnabled) {
mIsSelectiveMdnsResponseOffloadEnabled = isSelectiveMdnsResponseOffloadEnabled;
return this;
}
/**
* Builds a {@link MdnsFeatureFlags} with the arguments supplied to this builder.
*/
@ -609,6 +632,7 @@ public class MdnsFeatureFlags {
mIsCacheFlushPerAddressTypeEnabled,
mIsOptimizedExpiredServiceRemovalEnabled,
mIsIgnoreTemporaryIPv6AddressesEnabled,
mIsSelectiveMdnsResponseOffloadEnabled,
mMdnsSocketThreadStatsTag,
mOverrideProvider);
}

View file

@ -23,6 +23,7 @@ import static com.android.server.connectivity.mdns.MdnsServiceCache.findMatchedR
import static com.android.server.connectivity.mdns.MdnsQueryScheduler.ScheduledQueryTaskArgs;
import static com.android.server.connectivity.mdns.util.MdnsUtils.Clock;
import static com.android.server.connectivity.mdns.util.MdnsUtils.buildMdnsServiceInfoFromResponse;
import static com.android.server.connectivity.mdns.util.MdnsUtils.createOffloadServiceInfoFromFilterReplies;
import static com.android.server.connectivity.mdns.util.MdnsUtils.responseMatchesInstanceNameAndSubtypes;
import android.annotation.NonNull;
@ -97,6 +98,7 @@ public class MdnsServiceTypeClient {
}
};
@NonNull private final MdnsFeatureFlags featureFlags;
@NonNull private final OffloadCallback offloadCallback;
private final ArrayMap<MdnsServiceBrowserListener, ListenerInfo> listeners =
new ArrayMap<>();
/**
@ -444,9 +446,10 @@ public class MdnsServiceTypeClient {
@NonNull SharedLog sharedLog,
@NonNull Looper looper,
@NonNull MdnsServiceCache serviceCache,
@NonNull MdnsFeatureFlags featureFlags) {
@NonNull MdnsFeatureFlags featureFlags,
@NonNull OffloadCallback offloadCallback) {
this(serviceType, socketClient, executor, new Clock(), socketKey, sharedLog, looper,
new Dependencies(), serviceCache, featureFlags);
new Dependencies(), serviceCache, featureFlags, offloadCallback);
}
@VisibleForTesting
@ -460,7 +463,8 @@ public class MdnsServiceTypeClient {
@NonNull Looper looper,
@NonNull Dependencies dependencies,
@NonNull MdnsServiceCache serviceCache,
@NonNull MdnsFeatureFlags featureFlags) {
@NonNull MdnsFeatureFlags featureFlags,
@NonNull OffloadCallback offloadCallback) {
this.serviceType = serviceType;
this.socketClient = socketClient;
this.executor = executor;
@ -477,6 +481,7 @@ public class MdnsServiceTypeClient {
this.featureFlags = featureFlags;
this.scheduler = featureFlags.isAccurateDelayCallbackEnabled()
? dependencies.createScheduler(handler) : null;
this.offloadCallback = offloadCallback;
}
/**
@ -535,10 +540,22 @@ public class MdnsServiceTypeClient {
combinedInfo = getDiscoveryFilterRepliesInfo();
}
final FilterRepliesInfo oldInfo = offloadInfo.get(serviceName);
if (combinedInfo == null) {
offloadInfo.remove(serviceName);
if (featureFlags.mIsSelectiveMdnsResponseOffloadEnabled && oldInfo != null) {
offloadCallback.onOffloadStop(
socketKey.getInterfaceName(),
createOffloadServiceInfoFromFilterReplies(oldInfo));
}
} else {
offloadInfo.put(serviceName, combinedInfo);
if (featureFlags.mIsSelectiveMdnsResponseOffloadEnabled
&& (oldInfo == null || !oldInfo.equals(combinedInfo))) {
offloadCallback.onOffloadStartOrUpdate(
socketKey.getInterfaceName(),
createOffloadServiceInfoFromFilterReplies(combinedInfo));
}
}
}

View file

@ -22,7 +22,10 @@ import static com.android.server.connectivity.mdns.MdnsConstants.FLAG_TRUNCATED;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresApi;
import android.net.Network;
import android.net.nsd.OffloadEngine;
import android.net.nsd.OffloadServiceInfo;
import android.os.Build;
import android.os.SystemClock;
import android.util.ArraySet;
@ -37,6 +40,7 @@ import com.android.server.connectivity.mdns.MdnsPacketWriter;
import com.android.server.connectivity.mdns.MdnsRecord;
import com.android.server.connectivity.mdns.MdnsResponse;
import com.android.server.connectivity.mdns.MdnsServiceInfo;
import com.android.server.connectivity.mdns.MdnsServiceTypeClient;
import java.io.IOException;
import java.net.DatagramPacket;
@ -393,4 +397,31 @@ public class MdnsUtils {
MdnsConstants.SUBTYPE_PREFIX + requiredSub, actualSub)));
return matchesInstanceName && matchesSubtype;
}
/**
* Creates an {@link OffloadServiceInfo} object from a
* {@link MdnsServiceTypeClient.FilterRepliesInfo} instance.
*
* This method facilitates the conversion of filtering criteria into a service information
* object suitable for offloading mechanisms.
*
* @param info The {@link MdnsServiceTypeClient.FilterRepliesInfo} containing the filtering
* criteria.
* @return A new {@link OffloadServiceInfo} instance populated with data from the
* {@code FilterRepliesInfo}.
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public static OffloadServiceInfo createOffloadServiceInfoFromFilterReplies(
@NonNull MdnsServiceTypeClient.FilterRepliesInfo info) {
return new OffloadServiceInfo(
new OffloadServiceInfo.Key(info.serviceName, info.serviceType),
new ArrayList<>(info.subtypes),
info.hostname,
null /* offloadPayload */,
// Set the priority to 0 because APF will not prioritize some services. Instead, it
// would simply allow every mDNS reply to pass through if there are too many
// offloaded services, so there is no point in setting priorities.
0 /* priority */,
OffloadEngine.OFFLOAD_TYPE_FILTER_REPLIES);
}
}

View file

@ -46,8 +46,8 @@ import static com.android.server.NsdService.DEFAULT_RUNNING_APP_ACTIVE_IMPORTANC
import static com.android.server.NsdService.MdnsListener;
import static com.android.server.NsdService.NO_TRANSACTION;
import static com.android.server.NsdService.checkHostname;
import static com.android.server.NsdService.createOffloadServiceInfoFromFilterReplies;
import static com.android.server.NsdService.parseTypeAndSubtype;
import static com.android.server.connectivity.mdns.util.MdnsUtils.createOffloadServiceInfoFromFilterReplies;
import static com.android.testutils.ContextUtils.mockService;
import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
@ -249,8 +249,10 @@ public class NsdServiceTest {
doReturn(true).when(mMockMDnsM).resolve(
anyInt(), anyString(), anyString(), anyString(), anyInt());
doReturn(false).when(mDeps).isMdnsDiscoveryManagerEnabled(any(Context.class));
doReturn(mDiscoveryManager).when(mDeps)
.makeMdnsDiscoveryManager(any(), any(), any(), any());
doAnswer(inv -> {
mOffloadCallback = (OffloadCallback) inv.getArguments()[4];
return mDiscoveryManager;
}).when(mDeps).makeMdnsDiscoveryManager(any(), any(), any(), any(), any());
doReturn(mMulticastLock).when(mWifiManager).createMulticastLock(any());
doReturn(mSocketProvider).when(mDeps).makeMdnsSocketProvider(any(), any(), any(), any());
doReturn(DEFAULT_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF).when(mDeps).getDeviceConfigInt(
@ -2141,10 +2143,22 @@ public class NsdServiceTest {
verify(offloadEngine).onOffloadServiceUpdated(discoveryInfo);
}
private static void verifyOffloadServiceUpdatedAndRemoved(String interfaceName,
OffloadServiceInfo info, OffloadCallback cb, OffloadEngine offloadEngine) {
// onOffloadStartOrUpdate callback triggered. The OffloadServiceInfo update should be sent
// to the OffloadEngine.
cb.onOffloadStartOrUpdate(interfaceName, info);
verify(offloadEngine).onOffloadServiceUpdated(info);
// onOffloadStop callback triggered. The OffloadServiceInfo removal should be sent to the
// OffloadEngine.
cb.onOffloadStop(interfaceName, info);
verify(offloadEngine).onOffloadServiceRemoved(info);
}
@Test
@EnableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public void testRegisterOffloadEngine_OffloadServiceUpdatedAndRemoved() {
public void testRegisterOffloadEngine_OffloadServiceUpdatedAndRemoved_Advertiser() {
final String interfaceName = "iface";
final OffloadServiceInfo info = new OffloadServiceInfo(
new OffloadServiceInfo.Key("_testService", "_testType"), List.of("_sub1", "_sub2"),
@ -2157,14 +2171,29 @@ public class NsdServiceTest {
// sent to the OffloadEngine.
verify(mAdvertiser).getAllInterfaceOffloadServiceInfos(interfaceName);
verify(offloadEngine, never()).onOffloadServiceUpdated(any());
// onOffloadStartOrUpdate callback triggered. The OffloadServiceInfo update should be sent
// to the OffloadEngine.
mOffloadCallback.onOffloadStartOrUpdate(interfaceName, info);
verify(offloadEngine).onOffloadServiceUpdated(info);
// onOffloadStop callback triggered. The OffloadServiceInfo removal should be sent to the
// OffloadEngine.
mOffloadCallback.onOffloadStop(interfaceName, info);
verify(offloadEngine).onOffloadServiceRemoved(info);
verifyOffloadServiceUpdatedAndRemoved(
interfaceName, info, mOffloadCallback, offloadEngine);
}
@Test
@EnableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public void testRegisterOffloadEngine_OffloadServiceUpdatedAndRemoved_DiscoveryManager() {
final String interfaceName = "iface";
final OffloadServiceInfo info = new OffloadServiceInfo(
new OffloadServiceInfo.Key("", "_testType"), List.of("_sub1", "_sub2"),
"Android.local", new byte[]{0x1, 0x2, 0x3}, 1 /* priority */,
OFFLOAD_TYPE_FILTER_REPLIES);
doReturn(Collections.emptyList()).when(mDiscoveryManager)
.notifyOffloadStart(eq(interfaceName));
final OffloadEngine offloadEngine = registerOffloadEngine(interfaceName);
// Verify that the OffloadServiceInfo retrieves from the DiscoveryManager and that no info
// is sent to the OffloadEngine.
verify(mDiscoveryManager).notifyOffloadStart(eq(interfaceName));
verify(offloadEngine, never()).onOffloadServiceUpdated(any());
verifyOffloadServiceUpdatedAndRemoved(
interfaceName, info, mOffloadCallback, offloadEngine);
}
private void waitForIdle() {

View file

@ -100,6 +100,7 @@ public class MdnsDiscoveryManagerTests {
@Mock MdnsServiceBrowserListener mockListenerTwo;
@Mock SharedLog sharedLog;
@Mock MdnsServiceCache mockServiceCache;
@Mock OffloadCallback mockCallback;
private MdnsDiscoveryManager discoveryManager;
private HandlerThread thread;
private Handler handler;
@ -117,7 +118,7 @@ public class MdnsDiscoveryManagerTests {
doReturn(true).when(socketClient).supportsRequestingSpecificNetworks();
createdServiceTypeClientCount = 0;
discoveryManager = new MdnsDiscoveryManager(executorProvider, socketClient,
sharedLog, MdnsFeatureFlags.newBuilder().build()) {
sharedLog, MdnsFeatureFlags.newBuilder().build(), mockCallback) {
@Override
MdnsServiceTypeClient createServiceTypeClient(@NonNull String serviceType,
@NonNull SocketKey socketKey) {
@ -157,7 +158,8 @@ public class MdnsDiscoveryManagerTests {
}
private MdnsDiscoveryManager makeDiscoveryManager(@NonNull MdnsFeatureFlags featureFlags) {
return new MdnsDiscoveryManager(executorProvider, socketClient, sharedLog, featureFlags) {
return new MdnsDiscoveryManager(executorProvider, socketClient, sharedLog, featureFlags,
mockCallback) {
@Override
MdnsServiceTypeClient createServiceTypeClient(@NonNull String serviceType,
@NonNull SocketKey socketKey) {

View file

@ -28,6 +28,7 @@ import static com.android.server.connectivity.mdns.MdnsServiceTypeClient.EVENT_S
import static com.android.server.connectivity.mdns.MdnsServiceTypeClient.NO_HOSTNAME;
import static com.android.server.connectivity.mdns.MdnsServiceTypeClient.REMOVE_SERVICE_AFTER_QUERY_SENT_TIME;
import static com.android.server.connectivity.mdns.MdnsServiceTypeClient.SERVICE_NAME_DISCOVERY;
import static com.android.server.connectivity.mdns.util.MdnsUtils.createOffloadServiceInfoFromFilterReplies;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@ -58,6 +59,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.InetAddresses;
import android.net.Network;
import android.net.nsd.OffloadServiceInfo;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
@ -138,6 +140,8 @@ public class MdnsServiceTypeClientTests {
private MdnsServiceTypeClient.Dependencies mockDeps;
@Mock
private Scheduler mockScheduler;
@Mock
private OffloadCallback mockCallback;
@Captor
private ArgumentCaptor<MdnsServiceInfo> serviceInfoCaptor;
@ -277,7 +281,7 @@ public class MdnsServiceTypeClientTests {
private MdnsServiceTypeClient makeMdnsServiceTypeClient(MdnsFeatureFlags featureFlags) {
return new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps,
serviceCache, featureFlags);
serviceCache, featureFlags, mockCallback);
}
@After
@ -2357,7 +2361,7 @@ public class MdnsServiceTypeClientTests {
final FilterRepliesInfo resolveInfo = new FilterRepliesInfo(
instanceName, SERVICE_TYPE, List.of(), "hostname");
final FilterRepliesInfo discoverInfo = new FilterRepliesInfo(
SERVICE_NAME_DISCOVERY, SERVICE_TYPE, List.of(subtype), "" /* hostname */);
SERVICE_NAME_DISCOVERY, SERVICE_TYPE, List.of(subtype), NO_HOSTNAME);
assertTrue(offloadInfo.containsAll(Set.of(resolveInfo, discoverInfo)));
// Stop the resolution listener
@ -2423,6 +2427,47 @@ public class MdnsServiceTypeClientTests {
SERVICE_NAME_DISCOVERY, SERVICE_TYPE, List.of(subtype2), NO_HOSTNAME)));
}
@Test
public void testOffloadServiceInfoUpdate() {
final MdnsFeatureFlags flags = MdnsFeatureFlags.newBuilder()
.setIsSelectiveMdnsResponseOffloadEnabled(true).build();
client = makeMdnsServiceTypeClient(flags);
final String instanceName = "instance1";
final String subtype = "subtype";
final MdnsSearchOptions resolveOptions = MdnsSearchOptions.newBuilder()
.setResolveInstanceName(instanceName).build();
final MdnsSearchOptions discoverOptions = MdnsSearchOptions.newBuilder()
.addSubtype(subtype).build();
// Register two listener, one is for service resolution and one is for service discovery.
startSendAndReceive(mockListenerOne, resolveOptions);
startSendAndReceive(mockListenerTwo, discoverOptions);
final OffloadServiceInfo resolveInfo1 = createOffloadServiceInfoFromFilterReplies(
new FilterRepliesInfo(
instanceName, SERVICE_TYPE, List.of(), NO_HOSTNAME));
verify(mockCallback).onOffloadStartOrUpdate(socketKey.getInterfaceName(), resolveInfo1);
final OffloadServiceInfo discoverInfo = createOffloadServiceInfoFromFilterReplies(
new FilterRepliesInfo(
SERVICE_NAME_DISCOVERY, SERVICE_TYPE, List.of(subtype), NO_HOSTNAME));
verify(mockCallback).onOffloadStartOrUpdate(socketKey.getInterfaceName(), discoverInfo);
// Get a service response
processResponse(createResponse(instanceName, "192.0.2.0", 5353,
MdnsUtils.constructFullSubtype(SERVICE_TYPE_LABELS, SUBTYPE),
Collections.emptyMap() /* textAttributes */, TEST_TTL), socketKey);
final OffloadServiceInfo resolveInfo2 = createOffloadServiceInfoFromFilterReplies(
new FilterRepliesInfo(
instanceName, SERVICE_TYPE, List.of(), "hostname"));
verify(mockCallback).onOffloadStartOrUpdate(socketKey.getInterfaceName(), resolveInfo2);
stopSendAndReceive(mockListenerOne);
verify(mockCallback).onOffloadStop(socketKey.getInterfaceName(), resolveInfo2);
stopSendAndReceive(mockListenerTwo);
verify(mockCallback).onOffloadStop(socketKey.getInterfaceName(), discoverInfo);
}
private static MdnsServiceInfo matchServiceName(String name) {
return argThat(info -> info.getServiceInstanceName().equals(name));
}