Merge "Implement service resolved callback"
This commit is contained in:
@@ -243,9 +243,7 @@ public final class NsdManager {
|
|||||||
public static final int UNREGISTER_CLIENT = 22;
|
public static final int UNREGISTER_CLIENT = 22;
|
||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
public static final int MDNS_MONITORING_SOCKETS_CLEANUP = 23;
|
public static final int MDNS_DISCOVERY_MANAGER_EVENT = 23;
|
||||||
/** @hide */
|
|
||||||
public static final int MDNS_DISCOVERY_MANAGER_EVENT = 24;
|
|
||||||
|
|
||||||
/** Dns based service discovery protocol */
|
/** Dns based service discovery protocol */
|
||||||
public static final int PROTOCOL_DNS_SD = 0x0001;
|
public static final int PROTOCOL_DNS_SD = 0x0001;
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.net.INetd;
|
import android.net.INetd;
|
||||||
|
import android.net.InetAddresses;
|
||||||
import android.net.LinkProperties;
|
import android.net.LinkProperties;
|
||||||
import android.net.Network;
|
import android.net.Network;
|
||||||
import android.net.mdns.aidl.DiscoveryInfo;
|
import android.net.mdns.aidl.DiscoveryInfo;
|
||||||
@@ -75,6 +76,7 @@ import java.net.SocketException;
|
|||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@@ -196,6 +198,21 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
* Data class of mdns service callback information.
|
||||||
*/
|
*/
|
||||||
@@ -382,9 +399,6 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
maybeStartDaemon();
|
maybeStartDaemon();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case NsdManager.MDNS_MONITORING_SOCKETS_CLEANUP:
|
|
||||||
maybeStopMonitoringSockets();
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
Log.e(TAG, "Unhandled " + msg);
|
Log.e(TAG, "Unhandled " + msg);
|
||||||
return NOT_HANDLED;
|
return NOT_HANDLED;
|
||||||
@@ -439,7 +453,6 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
clientInfo.mClientIds.put(clientId, transactionId);
|
clientInfo.mClientIds.put(clientId, transactionId);
|
||||||
clientInfo.mListeners.put(clientId, listener);
|
clientInfo.mListeners.put(clientId, listener);
|
||||||
mIdToClientInfoMap.put(transactionId, clientInfo);
|
mIdToClientInfoMap.put(transactionId, clientInfo);
|
||||||
removeMessages(NsdManager.MDNS_MONITORING_SOCKETS_CLEANUP);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeListenerMap(int clientId, int transactionId, ClientInfo clientInfo) {
|
private void removeListenerMap(int clientId, int transactionId, ClientInfo clientInfo) {
|
||||||
@@ -472,7 +485,7 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
if (!matcher.matches()) return null;
|
if (!matcher.matches()) return null;
|
||||||
return matcher.group(1) == null
|
return matcher.group(1) == null
|
||||||
? serviceType + ".local"
|
? serviceType + ".local"
|
||||||
: matcher.group(1) + "._sub" + matcher.group(2) + ".local";
|
: matcher.group(1) + "_sub." + matcher.group(2) + ".local";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -482,7 +495,7 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
final int clientId = msg.arg2;
|
final int clientId = msg.arg2;
|
||||||
final ListenerArgs args;
|
final ListenerArgs args;
|
||||||
switch (msg.what) {
|
switch (msg.what) {
|
||||||
case NsdManager.DISCOVER_SERVICES:
|
case NsdManager.DISCOVER_SERVICES: {
|
||||||
if (DBG) Log.d(TAG, "Discover services");
|
if (DBG) Log.d(TAG, "Discover services");
|
||||||
args = (ListenerArgs) msg.obj;
|
args = (ListenerArgs) msg.obj;
|
||||||
clientInfo = mClients.get(args.connector);
|
clientInfo = mClients.get(args.connector);
|
||||||
@@ -536,6 +549,7 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case NsdManager.STOP_DISCOVERY:
|
case NsdManager.STOP_DISCOVERY:
|
||||||
if (DBG) Log.d(TAG, "Stop service discovery");
|
if (DBG) Log.d(TAG, "Stop service discovery");
|
||||||
args = (ListenerArgs) msg.obj;
|
args = (ListenerArgs) msg.obj;
|
||||||
@@ -626,7 +640,7 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
clientId, NsdManager.FAILURE_INTERNAL_ERROR);
|
clientId, NsdManager.FAILURE_INTERNAL_ERROR);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case NsdManager.RESOLVE_SERVICE:
|
case NsdManager.RESOLVE_SERVICE: {
|
||||||
if (DBG) Log.d(TAG, "Resolve service");
|
if (DBG) Log.d(TAG, "Resolve service");
|
||||||
args = (ListenerArgs) msg.obj;
|
args = (ListenerArgs) msg.obj;
|
||||||
clientInfo = mClients.get(args.connector);
|
clientInfo = mClients.get(args.connector);
|
||||||
@@ -638,22 +652,43 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clientInfo.mResolvedService != null) {
|
final NsdServiceInfo info = args.serviceInfo;
|
||||||
clientInfo.onResolveServiceFailed(
|
|
||||||
clientId, NsdManager.FAILURE_ALREADY_ACTIVE);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
maybeStartDaemon();
|
|
||||||
id = getUniqueId();
|
id = getUniqueId();
|
||||||
if (resolveService(id, args.serviceInfo)) {
|
if (mMdnsDiscoveryManager != null) {
|
||||||
clientInfo.mResolvedService = new NsdServiceInfo();
|
final String serviceType = constructServiceType(info.getServiceType());
|
||||||
storeRequestMap(clientId, id, clientInfo, msg.what);
|
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 {
|
} else {
|
||||||
clientInfo.onResolveServiceFailed(
|
if (clientInfo.mResolvedService != null) {
|
||||||
clientId, NsdManager.FAILURE_INTERNAL_ERROR);
|
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;
|
break;
|
||||||
|
}
|
||||||
case MDNS_SERVICE_EVENT:
|
case MDNS_SERVICE_EVENT:
|
||||||
if (!handleMDnsServiceEvent(msg.arg1, msg.arg2, msg.obj)) {
|
if (!handleMDnsServiceEvent(msg.arg1, msg.arg2, msg.obj)) {
|
||||||
return NOT_HANDLED;
|
return NOT_HANDLED;
|
||||||
@@ -863,6 +898,43 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
case NsdManager.SERVICE_LOST:
|
case NsdManager.SERVICE_LOST:
|
||||||
clientInfo.onServiceLost(clientId, info);
|
clientInfo.onServiceLost(clientId, info);
|
||||||
break;
|
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:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,9 +23,11 @@ import static com.android.testutils.ContextUtils.mockService;
|
|||||||
import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
|
import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
|
||||||
import static libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
|
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.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.ArgumentMatchers.argThat;
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
@@ -91,6 +93,7 @@ import org.mockito.ArgumentCaptor;
|
|||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
import java.net.UnknownHostException;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
@@ -111,6 +114,8 @@ public class NsdServiceTest {
|
|||||||
private static final String DOMAIN_NAME = "mytestdevice.local";
|
private static final String DOMAIN_NAME = "mytestdevice.local";
|
||||||
private static final int PORT = 2201;
|
private static final int PORT = 2201;
|
||||||
private static final int IFACE_IDX_ANY = 0;
|
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.
|
// Records INsdManagerCallback created when NsdService#connect is called.
|
||||||
// Only accessed on the test thread, since NsdService#connect is called by the NsdManager
|
// Only accessed on the test thread, since NsdService#connect is called by the NsdManager
|
||||||
@@ -613,8 +618,8 @@ public class NsdServiceTest {
|
|||||||
List.of(), /* subtypes */
|
List.of(), /* subtypes */
|
||||||
new String[] {"android", "local"}, /* hostName */
|
new String[] {"android", "local"}, /* hostName */
|
||||||
12345, /* port */
|
12345, /* port */
|
||||||
"192.0.2.0", /* ipv4Address */
|
IPV4_ADDRESS,
|
||||||
"2001:db8::", /* ipv6Address */
|
IPV6_ADDRESS,
|
||||||
List.of(), /* textStrings */
|
List.of(), /* textStrings */
|
||||||
List.of(), /* textEntries */
|
List.of(), /* textEntries */
|
||||||
1234, /* interfaceIndex */
|
1234, /* interfaceIndex */
|
||||||
@@ -682,6 +687,61 @@ public class NsdServiceTest {
|
|||||||
.onStartDiscoveryFailed(serviceTypeWithoutTcpOrUdpEnding, FAILURE_INTERNAL_ERROR);
|
.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() {
|
private void waitForIdle() {
|
||||||
HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);
|
HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user