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
This commit is contained in:
Paul Hu
2022-12-27 08:48:48 +00:00
committed by Remi NGUYEN VAN
parent 9ba6190f9b
commit 18aecccc9d
8 changed files with 589 additions and 48 deletions

View File

@@ -83,6 +83,7 @@ import java.nio.charset.StandardCharsets;
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 @@ public class NsdService extends INsdManager.Stub {
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 @@ public class NsdService extends INsdManager.Stub {
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 @@ public class NsdService extends INsdManager.Stub {
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 @@ public class NsdService extends INsdManager.Stub {
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 @@ public class NsdService extends INsdManager.Stub {
// 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 @@ public class NsdService extends INsdManager.Stub {
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 @@ public class NsdService extends INsdManager.Stub {
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 @@ public class NsdService extends INsdManager.Stub {
/* 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 @@ public class NsdService extends INsdManager.Stub {
// 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:
@@ -1342,6 +1443,20 @@ public class NsdService extends INsdManager.Stub {
NsdManager.STOP_RESOLUTION, 0, listenerKey, new ListenerArgs(this, null)));
}
@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(
@@ -1503,6 +1618,11 @@ public class NsdService extends INsdManager.Stub {
// 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 @@ public class NsdService extends INsdManager.Stub {
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 @@ public class NsdService extends INsdManager.Stub {
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);
}
}
}
}