diff --git a/common/mainline_beta.aconfig b/common/mainline_beta.aconfig index 4d493a5398..f0c913b355 100644 --- a/common/mainline_beta.aconfig +++ b/common/mainline_beta.aconfig @@ -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" } diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java index 3b73f82687..ebd5caa901 100644 --- a/service-t/src/com/android/server/NsdService.java +++ b/service-t/src/com/android/server/NsdService.java @@ -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; diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java index 4ff65dc725..bc154c1f7c 100644 --- a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java +++ b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java @@ -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 getMdnsServiceTypeClientByInterfaceName( diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java index 5ca65ca336..b8c961a154 100644 --- a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java +++ b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java @@ -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); } diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java index 6c9d25e2f1..7feaac36f1 100644 --- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java +++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java @@ -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 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)); + } } } diff --git a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java index 957fe0fa81..12650e27b1 100644 --- a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java +++ b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java @@ -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); + } } \ No newline at end of file diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java index a8116eb7d5..d19b74d1d3 100644 --- a/tests/unit/java/com/android/server/NsdServiceTest.java +++ b/tests/unit/java/com/android/server/NsdServiceTest.java @@ -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() { diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java index 30b3f44f8f..df9e544cc6 100644 --- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java +++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java @@ -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) { diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java index 79896ea29c..d5b5080e04 100644 --- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java +++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java @@ -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 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)); }