2 * Copyright (C) 2010 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.server;
19 import android.content.Context;
20 import android.content.ContentResolver;
21 import android.content.Intent;
22 import android.content.pm.PackageManager;
23 import android.database.ContentObserver;
24 import android.net.Uri;
25 import android.net.nsd.NsdServiceInfo;
26 import android.net.nsd.DnsSdTxtRecord;
27 import android.net.nsd.INsdManager;
28 import android.net.nsd.NsdManager;
29 import android.os.Binder;
30 import android.os.HandlerThread;
31 import android.os.Handler;
32 import android.os.Message;
33 import android.os.Messenger;
34 import android.os.UserHandle;
35 import android.provider.Settings;
36 import android.util.Base64;
37 import android.util.Slog;
38 import android.util.SparseArray;
39 import android.util.SparseIntArray;
41 import java.io.FileDescriptor;
42 import java.io.PrintWriter;
43 import java.net.InetAddress;
44 import java.util.Arrays;
45 import java.util.HashMap;
46 import java.util.concurrent.CountDownLatch;
48 import com.android.internal.annotations.VisibleForTesting;
49 import com.android.internal.util.AsyncChannel;
50 import com.android.internal.util.DumpUtils;
51 import com.android.internal.util.Protocol;
52 import com.android.internal.util.State;
53 import com.android.internal.util.StateMachine;
56 * Network Service Discovery Service handles remote service discovery operation requests by
57 * implementing the INsdManager interface.
61 public class NsdService extends INsdManager.Stub {
62 private static final String TAG = "NsdService";
63 private static final String MDNS_TAG = "mDnsConnector";
65 private static final boolean DBG = true;
67 private final Context mContext;
68 private final NsdSettings mNsdSettings;
69 private final NsdStateMachine mNsdStateMachine;
70 private final DaemonConnection mDaemon;
71 private final NativeCallbackReceiver mDaemonCallback;
74 * Clients receiving asynchronous messages
76 private final HashMap<Messenger, ClientInfo> mClients = new HashMap<>();
78 /* A map from unique id to client info */
79 private final SparseArray<ClientInfo> mIdToClientInfoMap= new SparseArray<>();
81 private final AsyncChannel mReplyChannel = new AsyncChannel();
83 private static final int INVALID_ID = 0;
84 private int mUniqueId = 1;
86 private class NsdStateMachine extends StateMachine {
88 private final DefaultState mDefaultState = new DefaultState();
89 private final DisabledState mDisabledState = new DisabledState();
90 private final EnabledState mEnabledState = new EnabledState();
93 protected String getWhatToString(int what) {
94 return NsdManager.nameOf(what);
98 * Observes the NSD on/off setting, and takes action when changed.
100 private void registerForNsdSetting() {
101 final ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
103 public void onChange(boolean selfChange) {
104 notifyEnabled(isNsdEnabled());
108 final Uri uri = Settings.Global.getUriFor(Settings.Global.NSD_ON);
109 mNsdSettings.registerContentObserver(uri, contentObserver);
112 NsdStateMachine(String name, Handler handler) {
113 super(name, handler);
114 addState(mDefaultState);
115 addState(mDisabledState, mDefaultState);
116 addState(mEnabledState, mDefaultState);
117 State initialState = isNsdEnabled() ? mEnabledState : mDisabledState;
118 setInitialState(initialState);
120 registerForNsdSetting();
123 class DefaultState extends State {
125 public boolean processMessage(Message msg) {
126 ClientInfo cInfo = null;
128 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
129 if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
130 AsyncChannel c = (AsyncChannel) msg.obj;
131 if (DBG) Slog.d(TAG, "New client listening to asynchronous messages");
132 c.sendMessage(AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED);
133 cInfo = new ClientInfo(c, msg.replyTo);
134 mClients.put(msg.replyTo, cInfo);
136 Slog.e(TAG, "Client connection failure, error=" + msg.arg1);
139 case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
141 case AsyncChannel.STATUS_SEND_UNSUCCESSFUL:
142 Slog.e(TAG, "Send failed, client connection lost");
144 case AsyncChannel.STATUS_REMOTE_DISCONNECTION:
145 if (DBG) Slog.d(TAG, "Client disconnected");
148 if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
151 cInfo = mClients.get(msg.replyTo);
153 cInfo.expungeAllRequests();
154 mClients.remove(msg.replyTo);
157 if (mClients.size() == 0) {
161 case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
162 AsyncChannel ac = new AsyncChannel();
163 ac.connect(mContext, getHandler(), msg.replyTo);
165 case NsdManager.DISCOVER_SERVICES:
166 replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
167 NsdManager.FAILURE_INTERNAL_ERROR);
169 case NsdManager.STOP_DISCOVERY:
170 replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
171 NsdManager.FAILURE_INTERNAL_ERROR);
173 case NsdManager.REGISTER_SERVICE:
174 replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
175 NsdManager.FAILURE_INTERNAL_ERROR);
177 case NsdManager.UNREGISTER_SERVICE:
178 replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
179 NsdManager.FAILURE_INTERNAL_ERROR);
181 case NsdManager.RESOLVE_SERVICE:
182 replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
183 NsdManager.FAILURE_INTERNAL_ERROR);
185 case NsdManager.NATIVE_DAEMON_EVENT:
187 Slog.e(TAG, "Unhandled " + msg);
194 class DisabledState extends State {
196 public void enter() {
197 sendNsdStateChangeBroadcast(false);
201 public boolean processMessage(Message msg) {
203 case NsdManager.ENABLE:
204 transitionTo(mEnabledState);
213 class EnabledState extends State {
215 public void enter() {
216 sendNsdStateChangeBroadcast(true);
217 if (mClients.size() > 0) {
224 if (mClients.size() > 0) {
229 private boolean requestLimitReached(ClientInfo clientInfo) {
230 if (clientInfo.mClientIds.size() >= ClientInfo.MAX_LIMIT) {
231 if (DBG) Slog.d(TAG, "Exceeded max outstanding requests " + clientInfo);
237 private void storeRequestMap(int clientId, int globalId, ClientInfo clientInfo, int what) {
238 clientInfo.mClientIds.put(clientId, globalId);
239 clientInfo.mClientRequests.put(clientId, what);
240 mIdToClientInfoMap.put(globalId, clientInfo);
243 private void removeRequestMap(int clientId, int globalId, ClientInfo clientInfo) {
244 clientInfo.mClientIds.delete(clientId);
245 clientInfo.mClientRequests.delete(clientId);
246 mIdToClientInfoMap.remove(globalId);
250 public boolean processMessage(Message msg) {
251 ClientInfo clientInfo;
252 NsdServiceInfo servInfo;
255 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
257 if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL &&
258 mClients.size() == 0) {
262 case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
264 case NsdManager.DISABLE:
265 //TODO: cleanup clients
266 transitionTo(mDisabledState);
268 case NsdManager.DISCOVER_SERVICES:
269 if (DBG) Slog.d(TAG, "Discover services");
270 servInfo = (NsdServiceInfo) msg.obj;
271 clientInfo = mClients.get(msg.replyTo);
273 if (requestLimitReached(clientInfo)) {
274 replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
275 NsdManager.FAILURE_MAX_LIMIT);
280 if (discoverServices(id, servInfo.getServiceType())) {
282 Slog.d(TAG, "Discover " + msg.arg2 + " " + id +
283 servInfo.getServiceType());
285 storeRequestMap(msg.arg2, id, clientInfo, msg.what);
286 replyToMessage(msg, NsdManager.DISCOVER_SERVICES_STARTED, servInfo);
288 stopServiceDiscovery(id);
289 replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
290 NsdManager.FAILURE_INTERNAL_ERROR);
293 case NsdManager.STOP_DISCOVERY:
294 if (DBG) Slog.d(TAG, "Stop service discovery");
295 clientInfo = mClients.get(msg.replyTo);
298 id = clientInfo.mClientIds.get(msg.arg2);
299 } catch (NullPointerException e) {
300 replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
301 NsdManager.FAILURE_INTERNAL_ERROR);
304 removeRequestMap(msg.arg2, id, clientInfo);
305 if (stopServiceDiscovery(id)) {
306 replyToMessage(msg, NsdManager.STOP_DISCOVERY_SUCCEEDED);
308 replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
309 NsdManager.FAILURE_INTERNAL_ERROR);
312 case NsdManager.REGISTER_SERVICE:
313 if (DBG) Slog.d(TAG, "Register service");
314 clientInfo = mClients.get(msg.replyTo);
315 if (requestLimitReached(clientInfo)) {
316 replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
317 NsdManager.FAILURE_MAX_LIMIT);
322 if (registerService(id, (NsdServiceInfo) msg.obj)) {
323 if (DBG) Slog.d(TAG, "Register " + msg.arg2 + " " + id);
324 storeRequestMap(msg.arg2, id, clientInfo, msg.what);
325 // Return success after mDns reports success
327 unregisterService(id);
328 replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
329 NsdManager.FAILURE_INTERNAL_ERROR);
332 case NsdManager.UNREGISTER_SERVICE:
333 if (DBG) Slog.d(TAG, "unregister service");
334 clientInfo = mClients.get(msg.replyTo);
336 id = clientInfo.mClientIds.get(msg.arg2);
337 } catch (NullPointerException e) {
338 replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
339 NsdManager.FAILURE_INTERNAL_ERROR);
342 removeRequestMap(msg.arg2, id, clientInfo);
343 if (unregisterService(id)) {
344 replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_SUCCEEDED);
346 replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
347 NsdManager.FAILURE_INTERNAL_ERROR);
350 case NsdManager.RESOLVE_SERVICE:
351 if (DBG) Slog.d(TAG, "Resolve service");
352 servInfo = (NsdServiceInfo) msg.obj;
353 clientInfo = mClients.get(msg.replyTo);
356 if (clientInfo.mResolvedService != null) {
357 replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
358 NsdManager.FAILURE_ALREADY_ACTIVE);
363 if (resolveService(id, servInfo)) {
364 clientInfo.mResolvedService = new NsdServiceInfo();
365 storeRequestMap(msg.arg2, id, clientInfo, msg.what);
367 replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
368 NsdManager.FAILURE_INTERNAL_ERROR);
371 case NsdManager.NATIVE_DAEMON_EVENT:
372 NativeEvent event = (NativeEvent) msg.obj;
373 if (!handleNativeEvent(event.code, event.raw, event.cooked)) {
383 private boolean handleNativeEvent(int code, String raw, String[] cooked) {
384 NsdServiceInfo servInfo;
385 int id = Integer.parseInt(cooked[1]);
386 ClientInfo clientInfo = mIdToClientInfoMap.get(id);
387 if (clientInfo == null) {
388 String name = NativeResponseCode.nameOf(code);
389 Slog.e(TAG, String.format("id %d for %s has no client mapping", id, name));
393 /* This goes in response as msg.arg2 */
394 int clientId = clientInfo.getClientId(id);
396 // This can happen because of race conditions. For example,
397 // SERVICE_FOUND may race with STOP_SERVICE_DISCOVERY,
398 // and we may get in this situation.
399 String name = NativeResponseCode.nameOf(code);
400 Slog.d(TAG, String.format(
401 "Notification %s for listener id %d that is no longer active",
406 String name = NativeResponseCode.nameOf(code);
407 Slog.d(TAG, String.format("Native daemon message %s: %s", name, raw));
410 case NativeResponseCode.SERVICE_FOUND:
411 /* NNN uniqueId serviceName regType domain */
412 servInfo = new NsdServiceInfo(cooked[2], cooked[3]);
413 clientInfo.mChannel.sendMessage(NsdManager.SERVICE_FOUND, 0,
416 case NativeResponseCode.SERVICE_LOST:
417 /* NNN uniqueId serviceName regType domain */
418 servInfo = new NsdServiceInfo(cooked[2], cooked[3]);
419 clientInfo.mChannel.sendMessage(NsdManager.SERVICE_LOST, 0,
422 case NativeResponseCode.SERVICE_DISCOVERY_FAILED:
423 /* NNN uniqueId errorCode */
424 clientInfo.mChannel.sendMessage(NsdManager.DISCOVER_SERVICES_FAILED,
425 NsdManager.FAILURE_INTERNAL_ERROR, clientId);
427 case NativeResponseCode.SERVICE_REGISTERED:
428 /* NNN regId serviceName regType */
429 servInfo = new NsdServiceInfo(cooked[2], null);
430 clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_SUCCEEDED,
431 id, clientId, servInfo);
433 case NativeResponseCode.SERVICE_REGISTRATION_FAILED:
434 /* NNN regId errorCode */
435 clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_FAILED,
436 NsdManager.FAILURE_INTERNAL_ERROR, clientId);
438 case NativeResponseCode.SERVICE_UPDATED:
441 case NativeResponseCode.SERVICE_UPDATE_FAILED:
442 /* NNN regId errorCode */
444 case NativeResponseCode.SERVICE_RESOLVED:
445 /* NNN resolveId fullName hostName port txtlen txtdata */
447 while (index < cooked[2].length() && cooked[2].charAt(index) != '.') {
448 if (cooked[2].charAt(index) == '\\') {
453 if (index >= cooked[2].length()) {
454 Slog.e(TAG, "Invalid service found " + raw);
457 String name = cooked[2].substring(0, index);
458 String rest = cooked[2].substring(index);
459 String type = rest.replace(".local.", "");
461 name = unescape(name);
463 clientInfo.mResolvedService.setServiceName(name);
464 clientInfo.mResolvedService.setServiceType(type);
465 clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4]));
466 clientInfo.mResolvedService.setTxtRecords(cooked[6]);
468 stopResolveService(id);
469 removeRequestMap(clientId, id, clientInfo);
471 int id2 = getUniqueId();
472 if (getAddrInfo(id2, cooked[3])) {
473 storeRequestMap(clientId, id2, clientInfo, NsdManager.RESOLVE_SERVICE);
475 clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
476 NsdManager.FAILURE_INTERNAL_ERROR, clientId);
477 clientInfo.mResolvedService = null;
480 case NativeResponseCode.SERVICE_RESOLUTION_FAILED:
481 /* NNN resolveId errorCode */
482 stopResolveService(id);
483 removeRequestMap(clientId, id, clientInfo);
484 clientInfo.mResolvedService = null;
485 clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
486 NsdManager.FAILURE_INTERNAL_ERROR, clientId);
488 case NativeResponseCode.SERVICE_GET_ADDR_FAILED:
489 /* NNN resolveId errorCode */
491 removeRequestMap(clientId, id, clientInfo);
492 clientInfo.mResolvedService = null;
493 clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
494 NsdManager.FAILURE_INTERNAL_ERROR, clientId);
496 case NativeResponseCode.SERVICE_GET_ADDR_SUCCESS:
497 /* NNN resolveId hostname ttl addr */
499 clientInfo.mResolvedService.setHost(InetAddress.getByName(cooked[4]));
500 clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_SUCCEEDED,
501 0, clientId, clientInfo.mResolvedService);
502 } catch (java.net.UnknownHostException e) {
503 clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
504 NsdManager.FAILURE_INTERNAL_ERROR, clientId);
507 removeRequestMap(clientId, id, clientInfo);
508 clientInfo.mResolvedService = null;
518 private String unescape(String s) {
519 StringBuilder sb = new StringBuilder(s.length());
520 for (int i = 0; i < s.length(); ++i) {
521 char c = s.charAt(i);
523 if (++i >= s.length()) {
524 Slog.e(TAG, "Unexpected end of escape sequence in: " + s);
528 if (c != '.' && c != '\\') {
529 if (i + 2 >= s.length()) {
530 Slog.e(TAG, "Unexpected end of escape sequence in: " + s);
533 c = (char) ((c-'0') * 100 + (s.charAt(i+1)-'0') * 10 + (s.charAt(i+2)-'0'));
539 return sb.toString();
543 NsdService(Context ctx, NsdSettings settings, Handler handler, DaemonConnectionSupplier fn) {
545 mNsdSettings = settings;
546 mNsdStateMachine = new NsdStateMachine(TAG, handler);
547 mNsdStateMachine.start();
548 mDaemonCallback = new NativeCallbackReceiver();
549 mDaemon = fn.get(mDaemonCallback);
552 public static NsdService create(Context context) throws InterruptedException {
553 NsdSettings settings = NsdSettings.makeDefault(context);
554 HandlerThread thread = new HandlerThread(TAG);
556 Handler handler = new Handler(thread.getLooper());
557 NsdService service = new NsdService(context, settings, handler, DaemonConnection::new);
558 service.mDaemonCallback.awaitConnection();
562 public Messenger getMessenger() {
563 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET, "NsdService");
564 return new Messenger(mNsdStateMachine.getHandler());
567 public void setEnabled(boolean isEnabled) {
568 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL,
570 mNsdSettings.putEnabledStatus(isEnabled);
571 notifyEnabled(isEnabled);
574 private void notifyEnabled(boolean isEnabled) {
575 mNsdStateMachine.sendMessage(isEnabled ? NsdManager.ENABLE : NsdManager.DISABLE);
578 private void sendNsdStateChangeBroadcast(boolean isEnabled) {
579 final Intent intent = new Intent(NsdManager.ACTION_NSD_STATE_CHANGED);
580 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
581 int nsdState = isEnabled ? NsdManager.NSD_STATE_ENABLED : NsdManager.NSD_STATE_DISABLED;
582 intent.putExtra(NsdManager.EXTRA_NSD_STATE, nsdState);
583 mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
586 private boolean isNsdEnabled() {
587 boolean ret = mNsdSettings.isEnabled();
589 Slog.d(TAG, "Network service discovery is " + (ret ? "enabled" : "disabled"));
594 private int getUniqueId() {
595 if (++mUniqueId == INVALID_ID) return ++mUniqueId;
599 /* These should be in sync with system/netd/server/ResponseCode.h */
600 static final class NativeResponseCode {
601 public static final int SERVICE_DISCOVERY_FAILED = 602;
602 public static final int SERVICE_FOUND = 603;
603 public static final int SERVICE_LOST = 604;
605 public static final int SERVICE_REGISTRATION_FAILED = 605;
606 public static final int SERVICE_REGISTERED = 606;
608 public static final int SERVICE_RESOLUTION_FAILED = 607;
609 public static final int SERVICE_RESOLVED = 608;
611 public static final int SERVICE_UPDATED = 609;
612 public static final int SERVICE_UPDATE_FAILED = 610;
614 public static final int SERVICE_GET_ADDR_FAILED = 611;
615 public static final int SERVICE_GET_ADDR_SUCCESS = 612;
617 private static final SparseArray<String> CODE_NAMES = new SparseArray<>();
619 CODE_NAMES.put(SERVICE_DISCOVERY_FAILED, "SERVICE_DISCOVERY_FAILED");
620 CODE_NAMES.put(SERVICE_FOUND, "SERVICE_FOUND");
621 CODE_NAMES.put(SERVICE_LOST, "SERVICE_LOST");
622 CODE_NAMES.put(SERVICE_REGISTRATION_FAILED, "SERVICE_REGISTRATION_FAILED");
623 CODE_NAMES.put(SERVICE_REGISTERED, "SERVICE_REGISTERED");
624 CODE_NAMES.put(SERVICE_RESOLUTION_FAILED, "SERVICE_RESOLUTION_FAILED");
625 CODE_NAMES.put(SERVICE_RESOLVED, "SERVICE_RESOLVED");
626 CODE_NAMES.put(SERVICE_UPDATED, "SERVICE_UPDATED");
627 CODE_NAMES.put(SERVICE_UPDATE_FAILED, "SERVICE_UPDATE_FAILED");
628 CODE_NAMES.put(SERVICE_GET_ADDR_FAILED, "SERVICE_GET_ADDR_FAILED");
629 CODE_NAMES.put(SERVICE_GET_ADDR_SUCCESS, "SERVICE_GET_ADDR_SUCCESS");
632 static String nameOf(int code) {
633 String name = CODE_NAMES.get(code);
635 return Integer.toString(code);
641 private class NativeEvent {
644 final String[] cooked;
646 NativeEvent(int code, String raw, String[] cooked) {
649 this.cooked = cooked;
653 class NativeCallbackReceiver implements INativeDaemonConnectorCallbacks {
654 private final CountDownLatch connected = new CountDownLatch(1);
656 public void awaitConnection() throws InterruptedException {
661 public void onDaemonConnected() {
662 connected.countDown();
666 public boolean onCheckHoldWakeLock(int code) {
671 public boolean onEvent(int code, String raw, String[] cooked) {
672 // TODO: NDC translates a message to a callback, we could enhance NDC to
673 // directly interact with a state machine through messages
674 NativeEvent event = new NativeEvent(code, raw, cooked);
675 mNsdStateMachine.sendMessage(NsdManager.NATIVE_DAEMON_EVENT, event);
680 interface DaemonConnectionSupplier {
681 DaemonConnection get(NativeCallbackReceiver callback);
685 public static class DaemonConnection {
686 final NativeDaemonConnector mNativeConnector;
688 DaemonConnection(NativeCallbackReceiver callback) {
689 mNativeConnector = new NativeDaemonConnector(callback, "mdns", 10, MDNS_TAG, 25, null);
690 new Thread(mNativeConnector, MDNS_TAG).start();
693 public boolean execute(Object... args) {
695 Slog.d(TAG, "mdnssd " + Arrays.toString(args));
698 mNativeConnector.execute("mdnssd", args);
699 } catch (NativeDaemonConnectorException e) {
700 Slog.e(TAG, "Failed to execute mdnssd " + Arrays.toString(args), e);
706 public void start() {
707 execute("start-service");
711 execute("stop-service");
715 private boolean registerService(int regId, NsdServiceInfo service) {
717 Slog.d(TAG, "registerService: " + regId + " " + service);
719 String name = service.getServiceName();
720 String type = service.getServiceType();
721 int port = service.getPort();
722 byte[] textRecord = service.getTxtRecord();
723 String record = Base64.encodeToString(textRecord, Base64.DEFAULT).replace("\n", "");
724 return mDaemon.execute("register", regId, name, type, port, record);
727 private boolean unregisterService(int regId) {
728 return mDaemon.execute("stop-register", regId);
731 private boolean updateService(int regId, DnsSdTxtRecord t) {
735 return mDaemon.execute("update", regId, t.size(), t.getRawData());
738 private boolean discoverServices(int discoveryId, String serviceType) {
739 return mDaemon.execute("discover", discoveryId, serviceType);
742 private boolean stopServiceDiscovery(int discoveryId) {
743 return mDaemon.execute("stop-discover", discoveryId);
746 private boolean resolveService(int resolveId, NsdServiceInfo service) {
747 String name = service.getServiceName();
748 String type = service.getServiceType();
749 return mDaemon.execute("resolve", resolveId, name, type, "local.");
752 private boolean stopResolveService(int resolveId) {
753 return mDaemon.execute("stop-resolve", resolveId);
756 private boolean getAddrInfo(int resolveId, String hostname) {
757 return mDaemon.execute("getaddrinfo", resolveId, hostname);
760 private boolean stopGetAddrInfo(int resolveId) {
761 return mDaemon.execute("stop-getaddrinfo", resolveId);
765 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
766 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
768 for (ClientInfo client : mClients.values()) {
769 pw.println("Client Info");
773 mNsdStateMachine.dump(fd, pw, args);
776 /* arg2 on the source message has an id that needs to be retained in replies
777 * see NsdManager for details */
778 private Message obtainMessage(Message srcMsg) {
779 Message msg = Message.obtain();
780 msg.arg2 = srcMsg.arg2;
784 private void replyToMessage(Message msg, int what) {
785 if (msg.replyTo == null) return;
786 Message dstMsg = obtainMessage(msg);
788 mReplyChannel.replyToMessage(msg, dstMsg);
791 private void replyToMessage(Message msg, int what, int arg1) {
792 if (msg.replyTo == null) return;
793 Message dstMsg = obtainMessage(msg);
796 mReplyChannel.replyToMessage(msg, dstMsg);
799 private void replyToMessage(Message msg, int what, Object obj) {
800 if (msg.replyTo == null) return;
801 Message dstMsg = obtainMessage(msg);
804 mReplyChannel.replyToMessage(msg, dstMsg);
807 /* Information tracked per client */
808 private class ClientInfo {
810 private static final int MAX_LIMIT = 10;
811 private final AsyncChannel mChannel;
812 private final Messenger mMessenger;
813 /* Remembers a resolved service until getaddrinfo completes */
814 private NsdServiceInfo mResolvedService;
816 /* A map from client id to unique id sent to mDns */
817 private final SparseIntArray mClientIds = new SparseIntArray();
819 /* A map from client id to the type of the request we had received */
820 private final SparseIntArray mClientRequests = new SparseIntArray();
822 private ClientInfo(AsyncChannel c, Messenger m) {
825 if (DBG) Slog.d(TAG, "New client, channel: " + c + " messenger: " + m);
829 public String toString() {
830 StringBuffer sb = new StringBuffer();
831 sb.append("mChannel ").append(mChannel).append("\n");
832 sb.append("mMessenger ").append(mMessenger).append("\n");
833 sb.append("mResolvedService ").append(mResolvedService).append("\n");
834 for(int i = 0; i< mClientIds.size(); i++) {
835 int clientID = mClientIds.keyAt(i);
836 sb.append("clientId ").append(clientID).
837 append(" mDnsId ").append(mClientIds.valueAt(i)).
838 append(" type ").append(mClientRequests.get(clientID)).append("\n");
840 return sb.toString();
843 // Remove any pending requests from the global map when we get rid of a client,
844 // and send cancellations to the daemon.
845 private void expungeAllRequests() {
846 int globalId, clientId, i;
847 // TODO: to keep handler responsive, do not clean all requests for that client at once.
848 for (i = 0; i < mClientIds.size(); i++) {
849 clientId = mClientIds.keyAt(i);
850 globalId = mClientIds.valueAt(i);
851 mIdToClientInfoMap.remove(globalId);
852 if (DBG) Slog.d(TAG, "Terminating client-ID " + clientId +
853 " global-ID " + globalId + " type " + mClientRequests.get(clientId));
854 switch (mClientRequests.get(clientId)) {
855 case NsdManager.DISCOVER_SERVICES:
856 stopServiceDiscovery(globalId);
858 case NsdManager.RESOLVE_SERVICE:
859 stopResolveService(globalId);
861 case NsdManager.REGISTER_SERVICE:
862 unregisterService(globalId);
869 mClientRequests.clear();
872 // mClientIds is a sparse array of listener id -> mDnsClient id. For a given mDnsClient id,
873 // return the corresponding listener id. mDnsClient id is also called a global id.
874 private int getClientId(final int globalId) {
875 int idx = mClientIds.indexOfValue(globalId);
879 return mClientIds.keyAt(idx);
884 * Interface which encapsulates dependencies of NsdService that are hard to mock, hard to
885 * override, or have side effects on global state in unit tests.
888 public interface NsdSettings {
890 void putEnabledStatus(boolean isEnabled);
891 void registerContentObserver(Uri uri, ContentObserver observer);
893 static NsdSettings makeDefault(Context context) {
894 final ContentResolver resolver = context.getContentResolver();
895 return new NsdSettings() {
897 public boolean isEnabled() {
898 return Settings.Global.getInt(resolver, Settings.Global.NSD_ON, 1) == 1;
902 public void putEnabledStatus(boolean isEnabled) {
903 Settings.Global.putInt(resolver, Settings.Global.NSD_ON, isEnabled ? 1 : 0);
907 public void registerContentObserver(Uri uri, ContentObserver observer) {
908 resolver.registerContentObserver(uri, false, observer);