New API to listen service update
Currently, the resolution is a one shot query, it only notifies
the first finding service information. There is no way to listen
the service update. Thus, add a new API that can register a
callback to listen to the service updates continuously.
Bug: 245369943
Test: atest FrameworksNetTests CtsNetTestCases
Change-Id: I0e9d92b9028375feb3e344ab6c4acb515c5b2be9
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index ce105ce..b361720 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -83,6 +83,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -412,6 +413,13 @@
clientId, NsdManager.FAILURE_OPERATION_NOT_RUNNING);
}
break;
+ case NsdManager.REGISTER_SERVICE_CALLBACK:
+ cInfo = getClientInfoForReply(msg);
+ if (cInfo != null) {
+ cInfo.onServiceInfoCallbackRegistrationFailed(
+ clientId, NsdManager.FAILURE_BAD_PARAMETERS);
+ }
+ break;
case NsdManager.DAEMON_CLEANUP:
maybeStopDaemon();
break;
@@ -490,6 +498,11 @@
maybeStopMonitoringSocketsIfNoActiveRequest();
}
+ private void clearRegisteredServiceInfo(ClientInfo clientInfo) {
+ clientInfo.mRegisteredService = null;
+ clientInfo.mClientIdForServiceUpdates = 0;
+ }
+
/**
* Check the given service type is valid and construct it to a service type
* which can use for discovery / resolution service.
@@ -793,6 +806,56 @@
clientInfo.mResolvedService = null;
// TODO: Implement the stop resolution with MdnsDiscoveryManager.
break;
+ case NsdManager.REGISTER_SERVICE_CALLBACK:
+ if (DBG) Log.d(TAG, "Register a service callback");
+ 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
+ // cleared and cause NPE. Add a null check here to prevent this corner case.
+ if (clientInfo == null) {
+ Log.e(TAG, "Unknown connector in callback registration");
+ break;
+ }
+
+ if (clientInfo.mRegisteredService != null) {
+ clientInfo.onServiceInfoCallbackRegistrationFailed(
+ clientId, NsdManager.FAILURE_ALREADY_ACTIVE);
+ break;
+ }
+
+ maybeStartDaemon();
+ id = getUniqueId();
+ if (resolveService(id, args.serviceInfo)) {
+ clientInfo.mRegisteredService = new NsdServiceInfo();
+ clientInfo.mClientIdForServiceUpdates = clientId;
+ storeRequestMap(clientId, id, clientInfo, msg.what);
+ } else {
+ clientInfo.onServiceInfoCallbackRegistrationFailed(
+ clientId, NsdManager.FAILURE_BAD_PARAMETERS);
+ }
+ break;
+ case NsdManager.UNREGISTER_SERVICE_CALLBACK:
+ if (DBG) Log.d(TAG, "Unregister a service callback");
+ 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
+ // cleared and cause NPE. Add a null check here to prevent this corner case.
+ if (clientInfo == null) {
+ Log.e(TAG, "Unknown connector in callback unregistration");
+ break;
+ }
+
+ id = clientInfo.mClientIds.get(clientId);
+ removeRequestMap(clientId, id, clientInfo);
+ if (stopResolveService(id)) {
+ clientInfo.onServiceInfoCallbackUnregistered(clientId);
+ } else {
+ Log.e(TAG, "Failed to unregister service info callback");
+ }
+ clearRegisteredServiceInfo(clientInfo);
+ break;
case MDNS_SERVICE_EVENT:
if (!handleMDnsServiceEvent(msg.arg1, msg.arg2, msg.obj)) {
return NOT_HANDLED;
@@ -809,6 +872,19 @@
return HANDLED;
}
+ private void notifyResolveFailedResult(boolean isListenedToUpdates, int clientId,
+ ClientInfo clientInfo, int error) {
+ if (isListenedToUpdates) {
+ clientInfo.onServiceInfoCallbackRegistrationFailed(clientId, error);
+ clearRegisteredServiceInfo(clientInfo);
+ } else {
+ // The resolve API always returned FAILURE_INTERNAL_ERROR on error; keep it
+ // for backwards compatibility.
+ clientInfo.onResolveServiceFailed(clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+ clientInfo.mResolvedService = null;
+ }
+ }
+
private boolean handleMDnsServiceEvent(int code, int id, Object obj) {
NsdServiceInfo servInfo;
ClientInfo clientInfo = mIdToClientInfoMap.get(id);
@@ -859,6 +935,8 @@
// found services on the same interface index and their network at the time
setServiceNetworkForCallback(servInfo, lostNetId, info.interfaceIdx);
clientInfo.onServiceLost(clientId, servInfo);
+ // TODO: also support registered service lost when not discovering
+ clientInfo.maybeNotifyRegisteredServiceLost(servInfo);
break;
}
case IMDnsEventListener.SERVICE_DISCOVERY_FAILED:
@@ -895,10 +973,15 @@
String rest = fullName.substring(index);
String type = rest.replace(".local.", "");
- clientInfo.mResolvedService.setServiceName(name);
- clientInfo.mResolvedService.setServiceType(type);
- clientInfo.mResolvedService.setPort(info.port);
- clientInfo.mResolvedService.setTxtRecords(info.txtRecord);
+ final boolean isListenedToUpdates =
+ clientId == clientInfo.mClientIdForServiceUpdates;
+ final NsdServiceInfo serviceInfo = isListenedToUpdates
+ ? clientInfo.mRegisteredService : clientInfo.mResolvedService;
+
+ serviceInfo.setServiceName(name);
+ serviceInfo.setServiceType(type);
+ serviceInfo.setPort(info.port);
+ serviceInfo.setTxtRecords(info.txtRecord);
// Network will be added after SERVICE_GET_ADDR_SUCCESS
stopResolveService(id);
@@ -908,9 +991,8 @@
if (getAddrInfo(id2, info.hostname, info.interfaceIdx)) {
storeRequestMap(clientId, id2, clientInfo, NsdManager.RESOLVE_SERVICE);
} else {
- clientInfo.onResolveServiceFailed(
- clientId, NsdManager.FAILURE_INTERNAL_ERROR);
- clientInfo.mResolvedService = null;
+ notifyResolveFailedResult(isListenedToUpdates, clientId, clientInfo,
+ NsdManager.FAILURE_BAD_PARAMETERS);
}
break;
}
@@ -918,17 +1000,17 @@
/* NNN resolveId errorCode */
stopResolveService(id);
removeRequestMap(clientId, id, clientInfo);
- clientInfo.mResolvedService = null;
- clientInfo.onResolveServiceFailed(
- clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+ notifyResolveFailedResult(
+ clientId == clientInfo.mClientIdForServiceUpdates,
+ clientId, clientInfo, NsdManager.FAILURE_BAD_PARAMETERS);
break;
case IMDnsEventListener.SERVICE_GET_ADDR_FAILED:
/* NNN resolveId errorCode */
stopGetAddrInfo(id);
removeRequestMap(clientId, id, clientInfo);
- clientInfo.mResolvedService = null;
- clientInfo.onResolveServiceFailed(
- clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+ notifyResolveFailedResult(
+ clientId == clientInfo.mClientIdForServiceUpdates,
+ clientId, clientInfo, NsdManager.FAILURE_BAD_PARAMETERS);
break;
case IMDnsEventListener.SERVICE_GET_ADDR_SUCCESS: {
/* NNN resolveId hostname ttl addr interfaceIdx netId */
@@ -945,19 +1027,38 @@
// If the resolved service is on an interface without a network, consider it
// as a failure: it would not be usable by apps as they would need
// privileged permissions.
- if (netId != NETID_UNSET && serviceHost != null) {
- clientInfo.mResolvedService.setHost(serviceHost);
- setServiceNetworkForCallback(clientInfo.mResolvedService,
- netId, info.interfaceIdx);
- clientInfo.onResolveServiceSucceeded(
- clientId, clientInfo.mResolvedService);
+ if (clientId == clientInfo.mClientIdForServiceUpdates) {
+ if (netId != NETID_UNSET && serviceHost != null) {
+ setServiceNetworkForCallback(clientInfo.mRegisteredService,
+ netId, info.interfaceIdx);
+ final List<InetAddress> addresses =
+ clientInfo.mRegisteredService.getHostAddresses();
+ addresses.add(serviceHost);
+ clientInfo.mRegisteredService.setHostAddresses(addresses);
+ clientInfo.onServiceUpdated(
+ clientId, clientInfo.mRegisteredService);
+ } else {
+ stopGetAddrInfo(id);
+ removeRequestMap(clientId, id, clientInfo);
+ clearRegisteredServiceInfo(clientInfo);
+ clientInfo.onServiceInfoCallbackRegistrationFailed(
+ clientId, NsdManager.FAILURE_BAD_PARAMETERS);
+ }
} else {
- clientInfo.onResolveServiceFailed(
- clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+ if (netId != NETID_UNSET && serviceHost != null) {
+ clientInfo.mResolvedService.setHost(serviceHost);
+ setServiceNetworkForCallback(clientInfo.mResolvedService,
+ netId, info.interfaceIdx);
+ clientInfo.onResolveServiceSucceeded(
+ clientId, clientInfo.mResolvedService);
+ } else {
+ clientInfo.onResolveServiceFailed(
+ clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+ }
+ stopGetAddrInfo(id);
+ removeRequestMap(clientId, id, clientInfo);
+ clientInfo.mResolvedService = null;
}
- stopGetAddrInfo(id);
- removeRequestMap(clientId, id, clientInfo);
- clientInfo.mResolvedService = null;
break;
}
default:
@@ -1343,6 +1444,20 @@
}
@Override
+ public void registerServiceInfoCallback(int listenerKey, NsdServiceInfo serviceInfo) {
+ mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
+ NsdManager.REGISTER_SERVICE_CALLBACK, 0, listenerKey,
+ new ListenerArgs(this, serviceInfo)));
+ }
+
+ @Override
+ public void unregisterServiceInfoCallback(int listenerKey) {
+ mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
+ NsdManager.UNREGISTER_SERVICE_CALLBACK, 0, listenerKey,
+ new ListenerArgs(this, null)));
+ }
+
+ @Override
public void startDaemon() {
mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
NsdManager.DAEMON_STARTUP, new ListenerArgs(this, null)));
@@ -1503,6 +1618,11 @@
// The target SDK of this client < Build.VERSION_CODES.S
private boolean mIsLegacy = false;
+ /*** The service that is registered to listen to its updates */
+ private NsdServiceInfo mRegisteredService;
+ /*** The client id that listen to updates */
+ private int mClientIdForServiceUpdates;
+
private ClientInfo(INsdManagerCallback cb) {
mCb = cb;
if (DBG) Log.d(TAG, "New client");
@@ -1584,6 +1704,18 @@
return mClientIds.keyAt(idx);
}
+ private void maybeNotifyRegisteredServiceLost(@NonNull NsdServiceInfo info) {
+ if (mRegisteredService == null) return;
+ if (!Objects.equals(mRegisteredService.getServiceName(), info.getServiceName())) return;
+ // Resolved services have a leading dot appended at the beginning of their type, but in
+ // discovered info it's at the end
+ if (!Objects.equals(
+ mRegisteredService.getServiceType() + ".", "." + info.getServiceType())) {
+ return;
+ }
+ onServiceUpdatedLost(mClientIdForServiceUpdates);
+ }
+
void onDiscoverServicesStarted(int listenerKey, NsdServiceInfo info) {
try {
mCb.onDiscoverServicesStarted(listenerKey, info);
@@ -1695,5 +1827,37 @@
Log.e(TAG, "Error calling onStopResolutionSucceeded", e);
}
}
+
+ void onServiceInfoCallbackRegistrationFailed(int listenerKey, int error) {
+ try {
+ mCb.onServiceInfoCallbackRegistrationFailed(listenerKey, error);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling onServiceInfoCallbackRegistrationFailed", e);
+ }
+ }
+
+ void onServiceUpdated(int listenerKey, NsdServiceInfo info) {
+ try {
+ mCb.onServiceUpdated(listenerKey, info);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling onServiceUpdated", e);
+ }
+ }
+
+ void onServiceUpdatedLost(int listenerKey) {
+ try {
+ mCb.onServiceUpdatedLost(listenerKey);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling onServiceUpdatedLost", e);
+ }
+ }
+
+ void onServiceInfoCallbackUnregistered(int listenerKey) {
+ try {
+ mCb.onServiceInfoCallbackUnregistered(listenerKey);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling onServiceInfoCallbackUnregistered", e);
+ }
+ }
}
}