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