• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.thread;
18 
19 import static android.net.nsd.NsdManager.PROTOCOL_DNS_SD;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.Context;
24 import android.net.DnsResolver;
25 import android.net.InetAddresses;
26 import android.net.LinkProperties;
27 import android.net.Network;
28 import android.net.nsd.DiscoveryRequest;
29 import android.net.nsd.NsdManager;
30 import android.net.nsd.NsdServiceInfo;
31 import android.os.CancellationSignal;
32 import android.os.Handler;
33 import android.os.RemoteException;
34 import android.system.Os;
35 import android.text.TextUtils;
36 import android.util.SparseArray;
37 
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.net.module.util.SharedLog;
40 import com.android.server.thread.openthread.DnsTxtAttribute;
41 import com.android.server.thread.openthread.INsdDiscoverServiceCallback;
42 import com.android.server.thread.openthread.INsdPublisher;
43 import com.android.server.thread.openthread.INsdResolveHostCallback;
44 import com.android.server.thread.openthread.INsdResolveServiceCallback;
45 import com.android.server.thread.openthread.INsdStatusReceiver;
46 
47 import java.net.Inet6Address;
48 import java.net.InetAddress;
49 import java.util.ArrayList;
50 import java.util.Collections;
51 import java.util.HashSet;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.concurrent.Executor;
55 
56 /**
57  * Implementation of {@link INsdPublisher}.
58  *
59  * <p>This class provides API for service registration and discovery over mDNS. This class is a
60  * proxy between ot-daemon and NsdManager.
61  *
62  * <p>All the data members of this class MUST be accessed in the {@code mHandler}'s Thread except
63  * {@code mHandler} itself.
64  */
65 public final class NsdPublisher extends INsdPublisher.Stub {
66     private static final String TAG = NsdPublisher.class.getSimpleName();
67     private static final SharedLog LOG = ThreadNetworkLogger.forSubComponent(TAG);
68 
69     // TODO: b/321883491 - specify network for mDNS operations
70     @Nullable private Network mNetwork;
71     private final Map<Network, LinkProperties> mNetworkToLinkProperties;
72     private final NsdManager mNsdManager;
73     private final DnsResolver mDnsResolver;
74     private final Handler mHandler;
75     private final Executor mExecutor;
76     private final SparseArray<RegistrationListener> mRegistrationListeners = new SparseArray<>(0);
77     private final SparseArray<DiscoveryListener> mDiscoveryListeners = new SparseArray<>(0);
78     private final SparseArray<ServiceInfoListener> mServiceInfoListeners = new SparseArray<>(0);
79     private final SparseArray<HostInfoListener> mHostInfoListeners = new SparseArray<>(0);
80 
81     @VisibleForTesting
NsdPublisher( NsdManager nsdManager, DnsResolver dnsResolver, Handler handler, Map<Network, LinkProperties> networkToLinkProperties)82     public NsdPublisher(
83             NsdManager nsdManager,
84             DnsResolver dnsResolver,
85             Handler handler,
86             Map<Network, LinkProperties> networkToLinkProperties) {
87         mNetwork = null;
88         mNsdManager = nsdManager;
89         mDnsResolver = dnsResolver;
90         mHandler = handler;
91         mExecutor = runnable -> mHandler.post(runnable);
92         mNetworkToLinkProperties = networkToLinkProperties;
93     }
94 
newInstance( Context context, Handler handler, Map<Network, LinkProperties> networkToLinkProperties)95     public static NsdPublisher newInstance(
96             Context context,
97             Handler handler,
98             Map<Network, LinkProperties> networkToLinkProperties) {
99         return new NsdPublisher(
100                 context.getSystemService(NsdManager.class),
101                 DnsResolver.getInstance(),
102                 handler,
103                 networkToLinkProperties);
104     }
105 
106     // TODO: b/321883491 - NsdPublisher should be disabled when mNetwork is null
setNetworkForHostResolution(@ullable Network network)107     public void setNetworkForHostResolution(@Nullable Network network) {
108         mNetwork = network;
109     }
110 
111     @Override
registerService( String hostname, String name, String type, List<String> subTypeList, int port, List<DnsTxtAttribute> txt, INsdStatusReceiver receiver, int listenerId)112     public void registerService(
113             String hostname,
114             String name,
115             String type,
116             List<String> subTypeList,
117             int port,
118             List<DnsTxtAttribute> txt,
119             INsdStatusReceiver receiver,
120             int listenerId) {
121         NsdServiceInfo serviceInfo =
122                 buildServiceInfoForService(hostname, name, type, subTypeList, port, txt);
123         mHandler.post(() -> registerInternal(serviceInfo, receiver, listenerId, "service"));
124     }
125 
buildServiceInfoForService( String hostname, String name, String type, List<String> subTypeList, int port, List<DnsTxtAttribute> txt)126     private static NsdServiceInfo buildServiceInfoForService(
127             String hostname,
128             String name,
129             String type,
130             List<String> subTypeList,
131             int port,
132             List<DnsTxtAttribute> txt) {
133         NsdServiceInfo serviceInfo = new NsdServiceInfo();
134 
135         serviceInfo.setServiceName(name);
136         if (!TextUtils.isEmpty(hostname)) {
137             serviceInfo.setHostname(hostname);
138         }
139         serviceInfo.setServiceType(type);
140         serviceInfo.setPort(port);
141         serviceInfo.setSubtypes(new HashSet<>(subTypeList));
142         for (DnsTxtAttribute attribute : txt) {
143             serviceInfo.setAttribute(attribute.name, attribute.value);
144         }
145 
146         return serviceInfo;
147     }
148 
149     @Override
registerHost( String name, List<String> addresses, INsdStatusReceiver receiver, int listenerId)150     public void registerHost(
151             String name, List<String> addresses, INsdStatusReceiver receiver, int listenerId) {
152         NsdServiceInfo serviceInfo = buildServiceInfoForHost(name, addresses);
153         mHandler.post(() -> registerInternal(serviceInfo, receiver, listenerId, "host"));
154     }
155 
buildServiceInfoForHost( String name, List<String> addressStrings)156     private static NsdServiceInfo buildServiceInfoForHost(
157             String name, List<String> addressStrings) {
158         NsdServiceInfo serviceInfo = new NsdServiceInfo();
159 
160         serviceInfo.setHostname(name);
161         ArrayList<InetAddress> addresses = new ArrayList<>(addressStrings.size());
162         for (String addressString : addressStrings) {
163             addresses.add(InetAddresses.parseNumericAddress(addressString));
164         }
165         serviceInfo.setHostAddresses(addresses);
166 
167         return serviceInfo;
168     }
169 
registerInternal( NsdServiceInfo serviceInfo, INsdStatusReceiver receiver, int listenerId, String registrationType)170     private void registerInternal(
171             NsdServiceInfo serviceInfo,
172             INsdStatusReceiver receiver,
173             int listenerId,
174             String registrationType) {
175         checkOnHandlerThread();
176         LOG.i(
177                 "Registering "
178                         + registrationType
179                         + ". Listener ID: "
180                         + listenerId
181                         + ", serviceInfo: "
182                         + serviceInfo);
183         RegistrationListener listener = new RegistrationListener(serviceInfo, listenerId, receiver);
184         mRegistrationListeners.append(listenerId, listener);
185         try {
186             mNsdManager.registerService(serviceInfo, PROTOCOL_DNS_SD, mExecutor, listener);
187         } catch (IllegalArgumentException e) {
188             LOG.e("Failed to register service. serviceInfo: " + serviceInfo, e);
189             listener.onRegistrationFailed(serviceInfo, NsdManager.FAILURE_INTERNAL_ERROR);
190         }
191     }
192 
unregister(INsdStatusReceiver receiver, int listenerId)193     public void unregister(INsdStatusReceiver receiver, int listenerId) {
194         mHandler.post(() -> unregisterInternal(receiver, listenerId));
195     }
196 
unregisterInternal(INsdStatusReceiver receiver, int listenerId)197     public void unregisterInternal(INsdStatusReceiver receiver, int listenerId) {
198         checkOnHandlerThread();
199         RegistrationListener registrationListener = mRegistrationListeners.get(listenerId);
200         if (registrationListener == null) {
201             LOG.w(
202                     "Failed to unregister service."
203                             + " Listener ID: "
204                             + listenerId
205                             + " The registrationListener is empty.");
206 
207             return;
208         }
209         LOG.i(
210                 "Unregistering service."
211                         + " Listener ID: "
212                         + listenerId
213                         + " serviceInfo: "
214                         + registrationListener.mServiceInfo);
215         registrationListener.addUnregistrationReceiver(receiver);
216         mNsdManager.unregisterService(registrationListener);
217     }
218 
219     @Override
discoverService(String type, INsdDiscoverServiceCallback callback, int listenerId)220     public void discoverService(String type, INsdDiscoverServiceCallback callback, int listenerId) {
221         mHandler.post(() -> discoverServiceInternal(type, callback, listenerId));
222     }
223 
discoverServiceInternal( String type, INsdDiscoverServiceCallback callback, int listenerId)224     private void discoverServiceInternal(
225             String type, INsdDiscoverServiceCallback callback, int listenerId) {
226         checkOnHandlerThread();
227         LOG.i("Discovering services." + " Listener ID: " + listenerId + ", service type: " + type);
228 
229         DiscoveryListener listener = new DiscoveryListener(listenerId, type, callback);
230         mDiscoveryListeners.append(listenerId, listener);
231         DiscoveryRequest discoveryRequest =
232                 new DiscoveryRequest.Builder(type).setNetwork(null).build();
233         mNsdManager.discoverServices(discoveryRequest, mExecutor, listener);
234     }
235 
236     @Override
stopServiceDiscovery(int listenerId)237     public void stopServiceDiscovery(int listenerId) {
238         mHandler.post(() -> stopServiceDiscoveryInternal(listenerId));
239     }
240 
stopServiceDiscoveryInternal(int listenerId)241     private void stopServiceDiscoveryInternal(int listenerId) {
242         checkOnHandlerThread();
243 
244         DiscoveryListener listener = mDiscoveryListeners.get(listenerId);
245         if (listener == null) {
246             LOG.w(
247                     "Failed to stop service discovery. Listener ID "
248                             + listenerId
249                             + ". The listener is null.");
250             return;
251         }
252 
253         LOG.i("Stopping service discovery. Listener: " + listener);
254         mNsdManager.stopServiceDiscovery(listener);
255     }
256 
257     @Override
resolveService( String name, String type, INsdResolveServiceCallback callback, int listenerId)258     public void resolveService(
259             String name, String type, INsdResolveServiceCallback callback, int listenerId) {
260         mHandler.post(() -> resolveServiceInternal(name, type, callback, listenerId));
261     }
262 
resolveServiceInternal( String name, String type, INsdResolveServiceCallback callback, int listenerId)263     private void resolveServiceInternal(
264             String name, String type, INsdResolveServiceCallback callback, int listenerId) {
265         checkOnHandlerThread();
266 
267         NsdServiceInfo serviceInfo = new NsdServiceInfo();
268         serviceInfo.setServiceName(name);
269         serviceInfo.setServiceType(type);
270         serviceInfo.setNetwork(null);
271         LOG.i(
272                 "Resolving service."
273                         + " Listener ID: "
274                         + listenerId
275                         + ", service name: "
276                         + name
277                         + ", service type: "
278                         + type);
279 
280         ServiceInfoListener listener = new ServiceInfoListener(serviceInfo, listenerId, callback);
281         mServiceInfoListeners.append(listenerId, listener);
282         mNsdManager.registerServiceInfoCallback(serviceInfo, mExecutor, listener);
283     }
284 
285     @Override
stopServiceResolution(int listenerId)286     public void stopServiceResolution(int listenerId) {
287         mHandler.post(() -> stopServiceResolutionInternal(listenerId));
288     }
289 
stopServiceResolutionInternal(int listenerId)290     private void stopServiceResolutionInternal(int listenerId) {
291         checkOnHandlerThread();
292 
293         ServiceInfoListener listener = mServiceInfoListeners.get(listenerId);
294         if (listener == null) {
295             LOG.w(
296                     "Failed to stop service resolution. Listener ID: "
297                             + listenerId
298                             + ". The listener is null.");
299             return;
300         }
301 
302         LOG.i("Stopping service resolution. Listener: " + listener);
303 
304         try {
305             mNsdManager.unregisterServiceInfoCallback(listener);
306         } catch (IllegalArgumentException e) {
307             LOG.w(
308                     "Failed to stop the service resolution because it's already stopped. Listener: "
309                             + listener);
310         }
311     }
312 
313     @Override
resolveHost(String name, INsdResolveHostCallback callback, int listenerId)314     public void resolveHost(String name, INsdResolveHostCallback callback, int listenerId) {
315         mHandler.post(() -> resolveHostInternal(name, callback, listenerId));
316     }
317 
resolveHostInternal( String name, INsdResolveHostCallback callback, int listenerId)318     private void resolveHostInternal(
319             String name, INsdResolveHostCallback callback, int listenerId) {
320         checkOnHandlerThread();
321 
322         String fullHostname = name + ".local";
323         CancellationSignal cancellationSignal = new CancellationSignal();
324         HostInfoListener listener =
325                 new HostInfoListener(name, callback, cancellationSignal, listenerId);
326         mDnsResolver.query(
327                 mNetwork,
328                 fullHostname,
329                 DnsResolver.FLAG_NO_CACHE_LOOKUP,
330                 mExecutor,
331                 cancellationSignal,
332                 listener);
333         mHostInfoListeners.append(listenerId, listener);
334 
335         LOG.i("Resolving host." + " Listener ID: " + listenerId + ", hostname: " + name);
336     }
337 
338     @Override
stopHostResolution(int listenerId)339     public void stopHostResolution(int listenerId) {
340         mHandler.post(() -> stopHostResolutionInternal(listenerId));
341     }
342 
stopHostResolutionInternal(int listenerId)343     private void stopHostResolutionInternal(int listenerId) {
344         checkOnHandlerThread();
345 
346         HostInfoListener listener = mHostInfoListeners.get(listenerId);
347         if (listener == null) {
348             LOG.w(
349                     "Failed to stop host resolution. Listener ID: "
350                             + listenerId
351                             + ". The listener is null.");
352             return;
353         }
354         LOG.i("Stopping host resolution. Listener: " + listener);
355         listener.cancel();
356         mHostInfoListeners.remove(listenerId);
357     }
358 
checkOnHandlerThread()359     private void checkOnHandlerThread() {
360         if (mHandler.getLooper().getThread() != Thread.currentThread()) {
361             throw new IllegalStateException(
362                     "Not running on handler Thread: " + Thread.currentThread().getName());
363         }
364     }
365 
366     @Override
reset()367     public void reset() {
368         mHandler.post(this::resetInternal);
369     }
370 
resetInternal()371     private void resetInternal() {
372         checkOnHandlerThread();
373         for (int i = 0; i < mRegistrationListeners.size(); ++i) {
374             try {
375                 mNsdManager.unregisterService(mRegistrationListeners.valueAt(i));
376             } catch (IllegalArgumentException e) {
377                 LOG.i(
378                         "Failed to unregister."
379                                 + " Listener ID: "
380                                 + mRegistrationListeners.keyAt(i)
381                                 + " serviceInfo: "
382                                 + mRegistrationListeners.valueAt(i).mServiceInfo
383                                 + ", error: "
384                                 + e.getMessage());
385             }
386         }
387         mRegistrationListeners.clear();
388     }
389 
390     /** On ot-daemon died, reset. */
onOtDaemonDied()391     public void onOtDaemonDied() {
392         reset();
393     }
394 
395     private final class RegistrationListener implements NsdManager.RegistrationListener {
396         private final NsdServiceInfo mServiceInfo;
397         private final int mListenerId;
398         private final INsdStatusReceiver mRegistrationReceiver;
399         private final List<INsdStatusReceiver> mUnregistrationReceivers;
400 
RegistrationListener( @onNull NsdServiceInfo serviceInfo, int listenerId, @NonNull INsdStatusReceiver registrationReceiver)401         RegistrationListener(
402                 @NonNull NsdServiceInfo serviceInfo,
403                 int listenerId,
404                 @NonNull INsdStatusReceiver registrationReceiver) {
405             mServiceInfo = serviceInfo;
406             mListenerId = listenerId;
407             mRegistrationReceiver = registrationReceiver;
408             mUnregistrationReceivers = new ArrayList<>();
409         }
410 
addUnregistrationReceiver(@onNull INsdStatusReceiver unregistrationReceiver)411         void addUnregistrationReceiver(@NonNull INsdStatusReceiver unregistrationReceiver) {
412             mUnregistrationReceivers.add(unregistrationReceiver);
413         }
414 
415         @Override
onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode)416         public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
417             checkOnHandlerThread();
418             mRegistrationListeners.remove(mListenerId);
419             LOG.i(
420                     "Failed to register listener ID: "
421                             + mListenerId
422                             + " error code: "
423                             + errorCode
424                             + " serviceInfo: "
425                             + serviceInfo);
426             try {
427                 mRegistrationReceiver.onError(errorCode);
428             } catch (RemoteException ignored) {
429                 // do nothing if the client is dead
430             }
431         }
432 
433         @Override
onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode)434         public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
435             checkOnHandlerThread();
436             for (INsdStatusReceiver receiver : mUnregistrationReceivers) {
437                 LOG.i(
438                         "Failed to unregister."
439                                 + "Listener ID: "
440                                 + mListenerId
441                                 + ", error code: "
442                                 + errorCode
443                                 + ", serviceInfo: "
444                                 + serviceInfo);
445                 try {
446                     receiver.onError(errorCode);
447                 } catch (RemoteException ignored) {
448                     // do nothing if the client is dead
449                 }
450             }
451         }
452 
453         @Override
onServiceRegistered(NsdServiceInfo serviceInfo)454         public void onServiceRegistered(NsdServiceInfo serviceInfo) {
455             checkOnHandlerThread();
456             LOG.i(
457                     "Registered successfully. "
458                             + "Listener ID: "
459                             + mListenerId
460                             + ", serviceInfo: "
461                             + serviceInfo);
462             try {
463                 mRegistrationReceiver.onSuccess();
464             } catch (RemoteException ignored) {
465                 // do nothing if the client is dead
466             }
467         }
468 
469         @Override
onServiceUnregistered(NsdServiceInfo serviceInfo)470         public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
471             checkOnHandlerThread();
472             for (INsdStatusReceiver receiver : mUnregistrationReceivers) {
473                 LOG.i(
474                         "Unregistered successfully. "
475                                 + "Listener ID: "
476                                 + mListenerId
477                                 + ", serviceInfo: "
478                                 + serviceInfo);
479                 try {
480                     receiver.onSuccess();
481                 } catch (RemoteException ignored) {
482                     // do nothing if the client is dead
483                 }
484             }
485             mRegistrationListeners.remove(mListenerId);
486         }
487     }
488 
489     private final class DiscoveryListener implements NsdManager.DiscoveryListener {
490         private final int mListenerId;
491         private final String mType;
492         private final INsdDiscoverServiceCallback mDiscoverServiceCallback;
493 
DiscoveryListener( int listenerId, @NonNull String type, @NonNull INsdDiscoverServiceCallback discoverServiceCallback)494         DiscoveryListener(
495                 int listenerId,
496                 @NonNull String type,
497                 @NonNull INsdDiscoverServiceCallback discoverServiceCallback) {
498             mListenerId = listenerId;
499             mType = type;
500             mDiscoverServiceCallback = discoverServiceCallback;
501         }
502 
503         @Override
onStartDiscoveryFailed(String serviceType, int errorCode)504         public void onStartDiscoveryFailed(String serviceType, int errorCode) {
505             LOG.e(
506                     "Failed to start service discovery."
507                             + " Error code: "
508                             + errorCode
509                             + ", listener: "
510                             + this);
511             mDiscoveryListeners.remove(mListenerId);
512         }
513 
514         @Override
onStopDiscoveryFailed(String serviceType, int errorCode)515         public void onStopDiscoveryFailed(String serviceType, int errorCode) {
516             LOG.e(
517                     "Failed to stop service discovery."
518                             + " Error code: "
519                             + errorCode
520                             + ", listener: "
521                             + this);
522             mDiscoveryListeners.remove(mListenerId);
523         }
524 
525         @Override
onDiscoveryStarted(String serviceType)526         public void onDiscoveryStarted(String serviceType) {
527             LOG.i("Started service discovery. Listener: " + this);
528         }
529 
530         @Override
onDiscoveryStopped(String serviceType)531         public void onDiscoveryStopped(String serviceType) {
532             LOG.i("Stopped service discovery. Listener: " + this);
533             mDiscoveryListeners.remove(mListenerId);
534         }
535 
536         @Override
onServiceFound(NsdServiceInfo serviceInfo)537         public void onServiceFound(NsdServiceInfo serviceInfo) {
538             LOG.i("Found service: " + serviceInfo);
539             try {
540                 mDiscoverServiceCallback.onServiceDiscovered(
541                         serviceInfo.getServiceName(), mType, true);
542             } catch (RemoteException e) {
543                 // do nothing if the client is dead
544             }
545         }
546 
547         @Override
onServiceLost(NsdServiceInfo serviceInfo)548         public void onServiceLost(NsdServiceInfo serviceInfo) {
549             LOG.i("Lost service: " + serviceInfo);
550             try {
551                 mDiscoverServiceCallback.onServiceDiscovered(
552                         serviceInfo.getServiceName(), mType, false);
553             } catch (RemoteException e) {
554                 // do nothing if the client is dead
555             }
556         }
557 
558         @Override
toString()559         public String toString() {
560             return "ID: " + mListenerId + ", type: " + mType;
561         }
562     }
563 
564     private final class ServiceInfoListener implements NsdManager.ServiceInfoCallback {
565         private final String mName;
566         private final String mType;
567         private final INsdResolveServiceCallback mResolveServiceCallback;
568         private final int mListenerId;
569 
ServiceInfoListener( @onNull NsdServiceInfo serviceInfo, int listenerId, @NonNull INsdResolveServiceCallback resolveServiceCallback)570         ServiceInfoListener(
571                 @NonNull NsdServiceInfo serviceInfo,
572                 int listenerId,
573                 @NonNull INsdResolveServiceCallback resolveServiceCallback) {
574             mName = serviceInfo.getServiceName();
575             mType = serviceInfo.getServiceType();
576             mListenerId = listenerId;
577             mResolveServiceCallback = resolveServiceCallback;
578         }
579 
580         @Override
onServiceInfoCallbackRegistrationFailed(int errorCode)581         public void onServiceInfoCallbackRegistrationFailed(int errorCode) {
582             LOG.e(
583                     "Failed to register service info callback."
584                             + " Listener ID: "
585                             + mListenerId
586                             + ", error: "
587                             + errorCode
588                             + ", service name: "
589                             + mName
590                             + ", service type: "
591                             + mType);
592         }
593 
594         @Override
onServiceUpdated(@onNull NsdServiceInfo serviceInfo)595         public void onServiceUpdated(@NonNull NsdServiceInfo serviceInfo) {
596             LOG.i(
597                     "Service is resolved. "
598                             + " Listener ID: "
599                             + mListenerId
600                             + ", serviceInfo: "
601                             + serviceInfo);
602             List<String> addresses = new ArrayList<>();
603             int interfaceIndex = 0;
604             if (mNetworkToLinkProperties.containsKey(serviceInfo.getNetwork())) {
605                 interfaceIndex =
606                         Os.if_nametoindex(
607                                 mNetworkToLinkProperties
608                                         .get(serviceInfo.getNetwork())
609                                         .getInterfaceName());
610             }
611             for (InetAddress address : serviceInfo.getHostAddresses()) {
612                 if (address instanceof Inet6Address) {
613                     addresses.add(address.getHostAddress());
614                 }
615             }
616             List<DnsTxtAttribute> txtList = new ArrayList<>();
617             for (Map.Entry<String, byte[]> entry : serviceInfo.getAttributes().entrySet()) {
618                 DnsTxtAttribute attribute =
619                         new DnsTxtAttribute(entry.getKey(), entry.getValue().clone());
620                 txtList.add(attribute);
621             }
622             // TODO: b/329018320 - Use the serviceInfo.getExpirationTime to derive TTL.
623             int ttlSeconds = 10;
624             try {
625                 mResolveServiceCallback.onServiceResolved(
626                         serviceInfo.getHostname(),
627                         interfaceIndex,
628                         serviceInfo.getServiceName(),
629                         serviceInfo.getServiceType(),
630                         serviceInfo.getPort(),
631                         addresses,
632                         txtList,
633                         ttlSeconds);
634 
635             } catch (RemoteException e) {
636                 // do nothing if the client is dead
637             }
638         }
639 
640         @Override
onServiceLost()641         public void onServiceLost() {}
642 
643         @Override
onServiceInfoCallbackUnregistered()644         public void onServiceInfoCallbackUnregistered() {
645             LOG.i("The service info callback is unregistered. Listener: " + this);
646             mServiceInfoListeners.remove(mListenerId);
647         }
648 
649         @Override
toString()650         public String toString() {
651             return "ID: " + mListenerId + ", service name: " + mName + ", service type: " + mType;
652         }
653     }
654 
655     class HostInfoListener implements DnsResolver.Callback<List<InetAddress>> {
656         private final String mHostname;
657         private final INsdResolveHostCallback mResolveHostCallback;
658         private final CancellationSignal mCancellationSignal;
659         private final int mListenerId;
660 
HostInfoListener( @onNull String hostname, INsdResolveHostCallback resolveHostCallback, CancellationSignal cancellationSignal, int listenerId)661         HostInfoListener(
662                 @NonNull String hostname,
663                 INsdResolveHostCallback resolveHostCallback,
664                 CancellationSignal cancellationSignal,
665                 int listenerId) {
666             this.mHostname = hostname;
667             this.mResolveHostCallback = resolveHostCallback;
668             this.mCancellationSignal = cancellationSignal;
669             this.mListenerId = listenerId;
670         }
671 
672         @Override
onAnswer(@onNull List<InetAddress> answerList, int rcode)673         public void onAnswer(@NonNull List<InetAddress> answerList, int rcode) {
674             checkOnHandlerThread();
675 
676             LOG.i(
677                     "Host is resolved."
678                             + " Listener ID: "
679                             + mListenerId
680                             + ", hostname: "
681                             + mHostname
682                             + ", addresses: "
683                             + answerList
684                             + ", return code: "
685                             + rcode);
686             List<String> addressStrings = new ArrayList<>();
687             for (InetAddress address : answerList) {
688                 addressStrings.add(address.getHostAddress());
689             }
690             try {
691                 mResolveHostCallback.onHostResolved(mHostname, addressStrings);
692             } catch (RemoteException e) {
693                 // do nothing if the client is dead
694             }
695             mHostInfoListeners.remove(mListenerId);
696         }
697 
698         @Override
onError(@onNull DnsResolver.DnsException error)699         public void onError(@NonNull DnsResolver.DnsException error) {
700             checkOnHandlerThread();
701 
702             LOG.i(
703                     "Failed to resolve host."
704                             + " Listener ID: "
705                             + mListenerId
706                             + ", hostname: "
707                             + mHostname
708                             + ", error: "
709                             + error.getMessage());
710             try {
711                 mResolveHostCallback.onHostResolved(mHostname, Collections.emptyList());
712             } catch (RemoteException e) {
713                 // do nothing if the client is dead
714             }
715             mHostInfoListeners.remove(mListenerId);
716         }
717 
toString()718         public String toString() {
719             return "ID: " + mListenerId + ", hostname: " + mHostname;
720         }
721 
cancel()722         void cancel() {
723             mCancellationSignal.cancel();
724             mHostInfoListeners.remove(mListenerId);
725         }
726     }
727 }
728