Implement service resolved callback
Service resolved should be notified when receive the
onServiceFound callbacks from MdnsServiceBrowserListener
Bug: 254166302
Test: atest FrameworksNetTests CtsNetTestCases
Change-Id: I681720065084bf3449c5b1ab44cd4ed6a659dcdb
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index d340384..45def36 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -243,9 +243,7 @@
public static final int UNREGISTER_CLIENT = 22;
/** @hide */
- public static final int MDNS_MONITORING_SOCKETS_CLEANUP = 23;
- /** @hide */
- public static final int MDNS_DISCOVERY_MANAGER_EVENT = 24;
+ public static final int MDNS_DISCOVERY_MANAGER_EVENT = 23;
/** Dns based service discovery protocol */
public static final int PROTOCOL_DNS_SD = 0x0001;
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 36c3cd4..84b9f12 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -27,6 +27,7 @@
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.INetd;
+import android.net.InetAddresses;
import android.net.LinkProperties;
import android.net.Network;
import android.net.mdns.aidl.DiscoveryInfo;
@@ -75,6 +76,7 @@
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -196,6 +198,21 @@
}
}
+ private class ResolutionListener extends MdnsListener {
+
+ ResolutionListener(int clientId, int transactionId, @NonNull NsdServiceInfo reqServiceInfo,
+ @NonNull String listenServiceType) {
+ super(clientId, transactionId, reqServiceInfo, listenServiceType);
+ }
+
+ @Override
+ public void onServiceFound(MdnsServiceInfo serviceInfo) {
+ mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
+ NsdManager.RESOLVE_SERVICE_SUCCEEDED,
+ new MdnsEvent(mClientId, mReqServiceInfo.getServiceType(), serviceInfo));
+ }
+ }
+
/**
* Data class of mdns service callback information.
*/
@@ -382,9 +399,6 @@
maybeStartDaemon();
}
break;
- case NsdManager.MDNS_MONITORING_SOCKETS_CLEANUP:
- maybeStopMonitoringSockets();
- break;
default:
Log.e(TAG, "Unhandled " + msg);
return NOT_HANDLED;
@@ -439,7 +453,6 @@
clientInfo.mClientIds.put(clientId, transactionId);
clientInfo.mListeners.put(clientId, listener);
mIdToClientInfoMap.put(transactionId, clientInfo);
- removeMessages(NsdManager.MDNS_MONITORING_SOCKETS_CLEANUP);
}
private void removeListenerMap(int clientId, int transactionId, ClientInfo clientInfo) {
@@ -472,7 +485,7 @@
if (!matcher.matches()) return null;
return matcher.group(1) == null
? serviceType + ".local"
- : matcher.group(1) + "._sub" + matcher.group(2) + ".local";
+ : matcher.group(1) + "_sub." + matcher.group(2) + ".local";
}
@Override
@@ -482,7 +495,7 @@
final int clientId = msg.arg2;
final ListenerArgs args;
switch (msg.what) {
- case NsdManager.DISCOVER_SERVICES:
+ case NsdManager.DISCOVER_SERVICES: {
if (DBG) Log.d(TAG, "Discover services");
args = (ListenerArgs) msg.obj;
clientInfo = mClients.get(args.connector);
@@ -536,6 +549,7 @@
}
}
break;
+ }
case NsdManager.STOP_DISCOVERY:
if (DBG) Log.d(TAG, "Stop service discovery");
args = (ListenerArgs) msg.obj;
@@ -626,7 +640,7 @@
clientId, NsdManager.FAILURE_INTERNAL_ERROR);
}
break;
- case NsdManager.RESOLVE_SERVICE:
+ case NsdManager.RESOLVE_SERVICE: {
if (DBG) Log.d(TAG, "Resolve service");
args = (ListenerArgs) msg.obj;
clientInfo = mClients.get(args.connector);
@@ -638,22 +652,43 @@
break;
}
- if (clientInfo.mResolvedService != null) {
- clientInfo.onResolveServiceFailed(
- clientId, NsdManager.FAILURE_ALREADY_ACTIVE);
- break;
- }
-
- maybeStartDaemon();
+ final NsdServiceInfo info = args.serviceInfo;
id = getUniqueId();
- if (resolveService(id, args.serviceInfo)) {
- clientInfo.mResolvedService = new NsdServiceInfo();
- storeRequestMap(clientId, id, clientInfo, msg.what);
+ if (mMdnsDiscoveryManager != null) {
+ final String serviceType = constructServiceType(info.getServiceType());
+ if (serviceType == null) {
+ clientInfo.onResolveServiceFailed(clientId,
+ NsdManager.FAILURE_INTERNAL_ERROR);
+ break;
+ }
+
+ maybeStartMonitoringSockets();
+ final MdnsListener listener =
+ new ResolutionListener(clientId, id, info, serviceType);
+ final MdnsSearchOptions options = MdnsSearchOptions.newBuilder()
+ .setNetwork(info.getNetwork())
+ .setIsPassiveMode(true)
+ .build();
+ mMdnsDiscoveryManager.registerListener(serviceType, listener, options);
+ storeListenerMap(clientId, id, listener, clientInfo);
} else {
- clientInfo.onResolveServiceFailed(
- clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+ if (clientInfo.mResolvedService != null) {
+ clientInfo.onResolveServiceFailed(
+ clientId, NsdManager.FAILURE_ALREADY_ACTIVE);
+ break;
+ }
+
+ maybeStartDaemon();
+ if (resolveService(id, args.serviceInfo)) {
+ clientInfo.mResolvedService = new NsdServiceInfo();
+ storeRequestMap(clientId, id, clientInfo, msg.what);
+ } else {
+ clientInfo.onResolveServiceFailed(
+ clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+ }
}
break;
+ }
case MDNS_SERVICE_EVENT:
if (!handleMDnsServiceEvent(msg.arg1, msg.arg2, msg.obj)) {
return NOT_HANDLED;
@@ -863,6 +898,43 @@
case NsdManager.SERVICE_LOST:
clientInfo.onServiceLost(clientId, info);
break;
+ case NsdManager.RESOLVE_SERVICE_SUCCEEDED: {
+ final MdnsServiceInfo serviceInfo = event.mMdnsServiceInfo;
+ // Add '.' in front of the service type that aligns with historical behavior
+ info.setServiceType("." + event.mRequestedServiceType);
+ info.setPort(serviceInfo.getPort());
+
+ Map<String, String> attrs = serviceInfo.getAttributes();
+ for (Map.Entry<String, String> kv : attrs.entrySet()) {
+ final String key = kv.getKey();
+ try {
+ info.setAttribute(key, serviceInfo.getAttributeAsBytes(key));
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Invalid attribute", e);
+ }
+ }
+ try {
+ if (serviceInfo.getIpv4Address() != null) {
+ info.setHost(InetAddresses.parseNumericAddress(
+ serviceInfo.getIpv4Address()));
+ } else {
+ info.setHost(InetAddresses.parseNumericAddress(
+ serviceInfo.getIpv6Address()));
+ }
+ clientInfo.onResolveServiceSucceeded(clientId, info);
+ } catch (IllegalArgumentException e) {
+ Log.wtf(TAG, "Invalid address in RESOLVE_SERVICE_SUCCEEDED", e);
+ clientInfo.onResolveServiceFailed(
+ clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+ }
+
+ // Unregister the listener immediately like IMDnsEventListener design
+ final MdnsListener listener = clientInfo.mListeners.get(clientId);
+ mMdnsDiscoveryManager.unregisterListener(
+ listener.getListenedServiceType(), listener);
+ removeListenerMap(clientId, transactionId, clientInfo);
+ break;
+ }
default:
return false;
}
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index a1c865f..1bd49a5 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -23,9 +23,11 @@
import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import static libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
@@ -91,6 +93,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.net.UnknownHostException;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
@@ -111,6 +114,8 @@
private static final String DOMAIN_NAME = "mytestdevice.local";
private static final int PORT = 2201;
private static final int IFACE_IDX_ANY = 0;
+ private static final String IPV4_ADDRESS = "192.0.2.0";
+ private static final String IPV6_ADDRESS = "2001:db8::";
// Records INsdManagerCallback created when NsdService#connect is called.
// Only accessed on the test thread, since NsdService#connect is called by the NsdManager
@@ -613,8 +618,8 @@
List.of(), /* subtypes */
new String[] {"android", "local"}, /* hostName */
12345, /* port */
- "192.0.2.0", /* ipv4Address */
- "2001:db8::", /* ipv6Address */
+ IPV4_ADDRESS,
+ IPV6_ADDRESS,
List.of(), /* textStrings */
List.of(), /* textEntries */
1234, /* interfaceIndex */
@@ -682,6 +687,61 @@
.onStartDiscoveryFailed(serviceTypeWithoutTcpOrUdpEnding, FAILURE_INTERNAL_ERROR);
}
+ @Test
+ public void testResolutionWithMdnsDiscoveryManager() throws UnknownHostException {
+ makeServiceWithMdnsDiscoveryManagerEnabled();
+
+ final NsdManager client = connectClient(mService);
+ final ResolveListener resolveListener = mock(ResolveListener.class);
+ final Network network = new Network(999);
+ final String serviceType = "_nsd._service._tcp";
+ final String constructedServiceType = "_nsd._sub._service._tcp.local";
+ final ArgumentCaptor<MdnsServiceBrowserListener> listenerCaptor =
+ ArgumentCaptor.forClass(MdnsServiceBrowserListener.class);
+ final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, serviceType);
+ request.setNetwork(network);
+ client.resolveService(request, resolveListener);
+ waitForIdle();
+ verify(mSocketProvider).startMonitoringSockets();
+ verify(mDiscoveryManager).registerListener(eq(constructedServiceType),
+ listenerCaptor.capture(), argThat(options -> network.equals(options.getNetwork())));
+
+ final MdnsServiceBrowserListener listener = listenerCaptor.getValue();
+ final MdnsServiceInfo mdnsServiceInfo = new MdnsServiceInfo(
+ SERVICE_NAME,
+ constructedServiceType.split("\\."),
+ List.of(), /* subtypes */
+ new String[]{"android", "local"}, /* hostName */
+ PORT,
+ IPV4_ADDRESS,
+ IPV6_ADDRESS,
+ List.of() /* textStrings */,
+ List.of(MdnsServiceInfo.TextEntry.fromBytes(new byte[]{
+ 'k', 'e', 'y', '=', (byte) 0xFF, (byte) 0xFE})) /* textEntries */,
+ 1234,
+ network);
+
+ // Verify onServiceFound callback
+ listener.onServiceFound(mdnsServiceInfo);
+ final ArgumentCaptor<NsdServiceInfo> infoCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ verify(resolveListener, timeout(TIMEOUT_MS)).onServiceResolved(infoCaptor.capture());
+ final NsdServiceInfo info = infoCaptor.getValue();
+ assertEquals(SERVICE_NAME, info.getServiceName());
+ assertEquals("." + serviceType, info.getServiceType());
+ assertEquals(PORT, info.getPort());
+ assertTrue(info.getAttributes().containsKey("key"));
+ assertEquals(1, info.getAttributes().size());
+ assertArrayEquals(new byte[]{(byte) 0xFF, (byte) 0xFE}, info.getAttributes().get("key"));
+ assertEquals(InetAddresses.parseNumericAddress(IPV4_ADDRESS), info.getHost());
+ assertEquals(network, info.getNetwork());
+
+ // Verify the listener has been unregistered.
+ verify(mDiscoveryManager, timeout(TIMEOUT_MS))
+ .unregisterListener(eq(constructedServiceType), any());
+ verify(mSocketProvider, timeout(CLEANUP_DELAY_MS + TIMEOUT_MS)).stopMonitoringSockets();
+ }
+
private void waitForIdle() {
HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);
}