NsdService.java revision 5a577903d366ff07b3c4ec6359e4757154f49790
1/*
2 * Copyright (C) 2010 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
17package com.android.server;
18
19import android.content.Context;
20import android.content.ContentResolver;
21import android.content.Intent;
22import android.content.pm.PackageManager;
23import android.database.ContentObserver;
24import android.net.nsd.NsdServiceInfo;
25import android.net.nsd.DnsSdTxtRecord;
26import android.net.nsd.INsdManager;
27import android.net.nsd.NsdManager;
28import android.os.Binder;
29import android.os.Message;
30import android.os.Messenger;
31import android.os.UserHandle;
32import android.provider.Settings;
33import android.util.Slog;
34import android.util.SparseArray;
35
36import java.io.FileDescriptor;
37import java.io.PrintWriter;
38import java.io.UnsupportedEncodingException;
39import java.net.InetAddress;
40import java.util.HashMap;
41import java.util.List;
42import java.util.Locale;
43import java.util.Map;
44import java.util.concurrent.CountDownLatch;
45
46import com.android.internal.util.AsyncChannel;
47import com.android.internal.util.Protocol;
48import com.android.internal.util.State;
49import com.android.internal.util.StateMachine;
50import com.android.server.NativeDaemonConnector.Command;
51
52/**
53 * Network Service Discovery Service handles remote service discovery operation requests by
54 * implementing the INsdManager interface.
55 *
56 * @hide
57 */
58public class NsdService extends INsdManager.Stub {
59    private static final String TAG = "NsdService";
60    private static final String MDNS_TAG = "mDnsConnector";
61
62    private static final boolean DBG = true;
63
64    private Context mContext;
65    private ContentResolver mContentResolver;
66    private NsdStateMachine mNsdStateMachine;
67
68    /**
69     * Clients receiving asynchronous messages
70     */
71    private HashMap<Messenger, ClientInfo> mClients = new HashMap<Messenger, ClientInfo>();
72
73    /* A map from unique id to client info */
74    private SparseArray<ClientInfo> mIdToClientInfoMap= new SparseArray<ClientInfo>();
75
76    private AsyncChannel mReplyChannel = new AsyncChannel();
77
78    private int INVALID_ID = 0;
79    private int mUniqueId = 1;
80
81    private static final int BASE = Protocol.BASE_NSD_MANAGER;
82    private static final int CMD_TO_STRING_COUNT = NsdManager.RESOLVE_SERVICE - BASE + 1;
83    private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT];
84
85    static {
86        sCmdToString[NsdManager.DISCOVER_SERVICES - BASE] = "DISCOVER";
87        sCmdToString[NsdManager.STOP_DISCOVERY - BASE] = "STOP-DISCOVER";
88        sCmdToString[NsdManager.REGISTER_SERVICE - BASE] = "REGISTER";
89        sCmdToString[NsdManager.UNREGISTER_SERVICE - BASE] = "UNREGISTER";
90        sCmdToString[NsdManager.RESOLVE_SERVICE - BASE] = "RESOLVE";
91    }
92
93    private static String cmdToString(int cmd) {
94        cmd -= BASE;
95        if ((cmd >= 0) && (cmd < sCmdToString.length)) {
96            return sCmdToString[cmd];
97        } else {
98            return null;
99        }
100    }
101
102    private class NsdStateMachine extends StateMachine {
103
104        private final DefaultState mDefaultState = new DefaultState();
105        private final DisabledState mDisabledState = new DisabledState();
106        private final EnabledState mEnabledState = new EnabledState();
107
108        @Override
109        protected String getWhatToString(int what) {
110            return cmdToString(what);
111        }
112
113        /**
114         * Observes the NSD on/off setting, and takes action when changed.
115         */
116        private void registerForNsdSetting() {
117            ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
118                @Override
119                    public void onChange(boolean selfChange) {
120                        if (isNsdEnabled()) {
121                            mNsdStateMachine.sendMessage(NsdManager.ENABLE);
122                        } else {
123                            mNsdStateMachine.sendMessage(NsdManager.DISABLE);
124                        }
125                    }
126            };
127
128            mContext.getContentResolver().registerContentObserver(
129                    Settings.Global.getUriFor(Settings.Global.NSD_ON),
130                    false, contentObserver);
131        }
132
133        NsdStateMachine(String name) {
134            super(name);
135            addState(mDefaultState);
136                addState(mDisabledState, mDefaultState);
137                addState(mEnabledState, mDefaultState);
138            if (isNsdEnabled()) {
139                setInitialState(mEnabledState);
140            } else {
141                setInitialState(mDisabledState);
142            }
143            setLogRecSize(25);
144            registerForNsdSetting();
145        }
146
147        class DefaultState extends State {
148            @Override
149            public boolean processMessage(Message msg) {
150                ClientInfo cInfo = null;
151                switch (msg.what) {
152                    case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
153                        if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
154                            AsyncChannel c = (AsyncChannel) msg.obj;
155                            if (DBG) Slog.d(TAG, "New client listening to asynchronous messages");
156                            c.sendMessage(AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED);
157                            cInfo = new ClientInfo(c, msg.replyTo);
158                            mClients.put(msg.replyTo, cInfo);
159                        } else {
160                            Slog.e(TAG, "Client connection failure, error=" + msg.arg1);
161                        }
162                        break;
163                    case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
164                        switch (msg.arg1) {
165                            case AsyncChannel.STATUS_SEND_UNSUCCESSFUL:
166                                Slog.e(TAG, "Send failed, client connection lost");
167                                break;
168                            case AsyncChannel.STATUS_REMOTE_DISCONNECTION:
169                                if (DBG) Slog.d(TAG, "Client disconnected");
170                                break;
171                            default:
172                                if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
173                                break;
174                        }
175                        cInfo = mClients.get(msg.replyTo);
176                        if (cInfo != null) {
177                            cInfo.expungeAllRequests();
178                            mClients.remove(msg.replyTo);
179                        }
180                        //Last client
181                        if (mClients.size() == 0) {
182                            stopMDnsDaemon();
183                        }
184                        break;
185                    case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
186                        AsyncChannel ac = new AsyncChannel();
187                        ac.connect(mContext, getHandler(), msg.replyTo);
188                        break;
189                    case NsdManager.DISCOVER_SERVICES:
190                        replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
191                                NsdManager.FAILURE_INTERNAL_ERROR);
192                       break;
193                    case NsdManager.STOP_DISCOVERY:
194                       replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
195                               NsdManager.FAILURE_INTERNAL_ERROR);
196                        break;
197                    case NsdManager.REGISTER_SERVICE:
198                        replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
199                                NsdManager.FAILURE_INTERNAL_ERROR);
200                        break;
201                    case NsdManager.UNREGISTER_SERVICE:
202                        replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
203                                NsdManager.FAILURE_INTERNAL_ERROR);
204                        break;
205                    case NsdManager.RESOLVE_SERVICE:
206                        replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
207                                NsdManager.FAILURE_INTERNAL_ERROR);
208                        break;
209                    case NsdManager.NATIVE_DAEMON_EVENT:
210                    default:
211                        Slog.e(TAG, "Unhandled " + msg);
212                        return NOT_HANDLED;
213                }
214                return HANDLED;
215            }
216        }
217
218        class DisabledState extends State {
219            @Override
220            public void enter() {
221                sendNsdStateChangeBroadcast(false);
222            }
223
224            @Override
225            public boolean processMessage(Message msg) {
226                switch (msg.what) {
227                    case NsdManager.ENABLE:
228                        transitionTo(mEnabledState);
229                        break;
230                    default:
231                        return NOT_HANDLED;
232                }
233                return HANDLED;
234            }
235        }
236
237        class EnabledState extends State {
238            @Override
239            public void enter() {
240                sendNsdStateChangeBroadcast(true);
241                if (mClients.size() > 0) {
242                    startMDnsDaemon();
243                }
244            }
245
246            @Override
247            public void exit() {
248                if (mClients.size() > 0) {
249                    stopMDnsDaemon();
250                }
251            }
252
253            private boolean requestLimitReached(ClientInfo clientInfo) {
254                if (clientInfo.mClientIds.size() >= ClientInfo.MAX_LIMIT) {
255                    if (DBG) Slog.d(TAG, "Exceeded max outstanding requests " + clientInfo);
256                    return true;
257                }
258                return false;
259            }
260
261            private void storeRequestMap(int clientId, int globalId, ClientInfo clientInfo, int what) {
262                clientInfo.mClientIds.put(clientId, globalId);
263                clientInfo.mClientRequests.put(clientId, what);
264                mIdToClientInfoMap.put(globalId, clientInfo);
265            }
266
267            private void removeRequestMap(int clientId, int globalId, ClientInfo clientInfo) {
268                clientInfo.mClientIds.remove(clientId);
269                clientInfo.mClientRequests.remove(clientId);
270                mIdToClientInfoMap.remove(globalId);
271            }
272
273            @Override
274            public boolean processMessage(Message msg) {
275                ClientInfo clientInfo;
276                NsdServiceInfo servInfo;
277                boolean result = HANDLED;
278                int id;
279                switch (msg.what) {
280                  case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
281                        //First client
282                        if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL &&
283                                mClients.size() == 0) {
284                            startMDnsDaemon();
285                        }
286                        result = NOT_HANDLED;
287                        break;
288                    case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
289                        result = NOT_HANDLED;
290                        break;
291                    case NsdManager.DISABLE:
292                        //TODO: cleanup clients
293                        transitionTo(mDisabledState);
294                        break;
295                    case NsdManager.DISCOVER_SERVICES:
296                        if (DBG) Slog.d(TAG, "Discover services");
297                        servInfo = (NsdServiceInfo) msg.obj;
298                        clientInfo = mClients.get(msg.replyTo);
299
300                        if (requestLimitReached(clientInfo)) {
301                            replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
302                                    NsdManager.FAILURE_MAX_LIMIT);
303                            break;
304                        }
305
306                        id = getUniqueId();
307                        if (discoverServices(id, servInfo.getServiceType())) {
308                            if (DBG) {
309                                Slog.d(TAG, "Discover " + msg.arg2 + " " + id +
310                                        servInfo.getServiceType());
311                            }
312                            storeRequestMap(msg.arg2, id, clientInfo, msg.what);
313                            replyToMessage(msg, NsdManager.DISCOVER_SERVICES_STARTED, servInfo);
314                        } else {
315                            stopServiceDiscovery(id);
316                            replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
317                                    NsdManager.FAILURE_INTERNAL_ERROR);
318                        }
319                        break;
320                    case NsdManager.STOP_DISCOVERY:
321                        if (DBG) Slog.d(TAG, "Stop service discovery");
322                        clientInfo = mClients.get(msg.replyTo);
323
324                        try {
325                            id = clientInfo.mClientIds.get(msg.arg2).intValue();
326                        } catch (NullPointerException e) {
327                            replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
328                                    NsdManager.FAILURE_INTERNAL_ERROR);
329                            break;
330                        }
331                        removeRequestMap(msg.arg2, id, clientInfo);
332                        if (stopServiceDiscovery(id)) {
333                            replyToMessage(msg, NsdManager.STOP_DISCOVERY_SUCCEEDED);
334                        } else {
335                            replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
336                                    NsdManager.FAILURE_INTERNAL_ERROR);
337                        }
338                        break;
339                    case NsdManager.REGISTER_SERVICE:
340                        if (DBG) Slog.d(TAG, "Register service");
341                        clientInfo = mClients.get(msg.replyTo);
342                        if (requestLimitReached(clientInfo)) {
343                            replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
344                                    NsdManager.FAILURE_MAX_LIMIT);
345                            break;
346                        }
347
348                        id = getUniqueId();
349                        if (registerService(id, (NsdServiceInfo) msg.obj)) {
350                            if (DBG) Slog.d(TAG, "Register " + msg.arg2 + " " + id);
351                            storeRequestMap(msg.arg2, id, clientInfo, msg.what);
352                            // Return success after mDns reports success
353                        } else {
354                            unregisterService(id);
355                            replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
356                                    NsdManager.FAILURE_INTERNAL_ERROR);
357                        }
358                        break;
359                    case NsdManager.UNREGISTER_SERVICE:
360                        if (DBG) Slog.d(TAG, "unregister service");
361                        clientInfo = mClients.get(msg.replyTo);
362                        try {
363                            id = clientInfo.mClientIds.get(msg.arg2).intValue();
364                        } catch (NullPointerException e) {
365                            replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
366                                    NsdManager.FAILURE_INTERNAL_ERROR);
367                            break;
368                        }
369                        removeRequestMap(msg.arg2, id, clientInfo);
370                        if (unregisterService(id)) {
371                            replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_SUCCEEDED);
372                        } else {
373                            replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
374                                    NsdManager.FAILURE_INTERNAL_ERROR);
375                        }
376                        break;
377                    case NsdManager.RESOLVE_SERVICE:
378                        if (DBG) Slog.d(TAG, "Resolve service");
379                        servInfo = (NsdServiceInfo) msg.obj;
380                        clientInfo = mClients.get(msg.replyTo);
381
382
383                        if (clientInfo.mResolvedService != null) {
384                            replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
385                                    NsdManager.FAILURE_ALREADY_ACTIVE);
386                            break;
387                        }
388
389                        id = getUniqueId();
390                        if (resolveService(id, servInfo)) {
391                            clientInfo.mResolvedService = new NsdServiceInfo();
392                            storeRequestMap(msg.arg2, id, clientInfo, msg.what);
393                        } else {
394                            replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
395                                    NsdManager.FAILURE_INTERNAL_ERROR);
396                        }
397                        break;
398                    case NsdManager.NATIVE_DAEMON_EVENT:
399                        NativeEvent event = (NativeEvent) msg.obj;
400                        if (!handleNativeEvent(event.code, event.raw,
401                                NativeDaemonEvent.unescapeArgs(event.raw))) {
402                            result = NOT_HANDLED;
403                        }
404                        break;
405                    default:
406                        result = NOT_HANDLED;
407                        break;
408                }
409                return result;
410            }
411
412            private boolean handleNativeEvent(int code, String raw, String[] cooked) {
413                boolean handled = true;
414                NsdServiceInfo servInfo;
415                int id = Integer.parseInt(cooked[1]);
416                ClientInfo clientInfo = mIdToClientInfoMap.get(id);
417                if (clientInfo == null) {
418                    Slog.e(TAG, "Unique id with no client mapping: " + id);
419                    handled = false;
420                    return handled;
421                }
422
423                /* This goes in response as msg.arg2 */
424                int clientId = clientInfo.getClientId(id);
425                if (clientId < 0) {
426                    // This can happen because of race conditions. For example,
427                    // SERVICE_FOUND may race with STOP_SERVICE_DISCOVERY,
428                    // and we may get in this situation.
429                    Slog.d(TAG, "Notification for a listener that is no longer active: " + id);
430                    handled = false;
431                    return handled;
432                }
433
434                switch (code) {
435                    case NativeResponseCode.SERVICE_FOUND:
436                        /* NNN uniqueId serviceName regType domain */
437                        if (DBG) Slog.d(TAG, "SERVICE_FOUND Raw: " + raw);
438                        servInfo = new NsdServiceInfo(cooked[2], cooked[3]);
439                        clientInfo.mChannel.sendMessage(NsdManager.SERVICE_FOUND, 0,
440                                clientId, servInfo);
441                        break;
442                    case NativeResponseCode.SERVICE_LOST:
443                        /* NNN uniqueId serviceName regType domain */
444                        if (DBG) Slog.d(TAG, "SERVICE_LOST Raw: " + raw);
445                        servInfo = new NsdServiceInfo(cooked[2], cooked[3]);
446                        clientInfo.mChannel.sendMessage(NsdManager.SERVICE_LOST, 0,
447                                clientId, servInfo);
448                        break;
449                    case NativeResponseCode.SERVICE_DISCOVERY_FAILED:
450                        /* NNN uniqueId errorCode */
451                        if (DBG) Slog.d(TAG, "SERVICE_DISC_FAILED Raw: " + raw);
452                        clientInfo.mChannel.sendMessage(NsdManager.DISCOVER_SERVICES_FAILED,
453                                NsdManager.FAILURE_INTERNAL_ERROR, clientId);
454                        break;
455                    case NativeResponseCode.SERVICE_REGISTERED:
456                        /* NNN regId serviceName regType */
457                        if (DBG) Slog.d(TAG, "SERVICE_REGISTERED Raw: " + raw);
458                        servInfo = new NsdServiceInfo(cooked[2], null);
459                        clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_SUCCEEDED,
460                                id, clientId, servInfo);
461                        break;
462                    case NativeResponseCode.SERVICE_REGISTRATION_FAILED:
463                        /* NNN regId errorCode */
464                        if (DBG) Slog.d(TAG, "SERVICE_REGISTER_FAILED Raw: " + raw);
465                        clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_FAILED,
466                               NsdManager.FAILURE_INTERNAL_ERROR, clientId);
467                        break;
468                    case NativeResponseCode.SERVICE_UPDATED:
469                        /* NNN regId */
470                        break;
471                    case NativeResponseCode.SERVICE_UPDATE_FAILED:
472                        /* NNN regId errorCode */
473                        break;
474                    case NativeResponseCode.SERVICE_RESOLVED:
475                        /* NNN resolveId fullName hostName port txtlen txtdata */
476                        if (DBG) Slog.d(TAG, "SERVICE_RESOLVED Raw: " + raw);
477                        int index = cooked[2].indexOf(".");
478                        if (index == -1) {
479                            Slog.e(TAG, "Invalid service found " + raw);
480                            break;
481                        }
482                        String name = cooked[2].substring(0, index);
483                        String rest = cooked[2].substring(index);
484                        String type = rest.replace(".local.", "");
485
486                        clientInfo.mResolvedService.setServiceName(name);
487                        clientInfo.mResolvedService.setServiceType(type);
488                        clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4]));
489
490                        stopResolveService(id);
491                        removeRequestMap(clientId, id, clientInfo);
492
493                        int id2 = getUniqueId();
494                        if (getAddrInfo(id2, cooked[3])) {
495                            storeRequestMap(clientId, id2, clientInfo, NsdManager.RESOLVE_SERVICE);
496                        } else {
497                            clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
498                                    NsdManager.FAILURE_INTERNAL_ERROR, clientId);
499                            clientInfo.mResolvedService = null;
500                        }
501                        break;
502                    case NativeResponseCode.SERVICE_RESOLUTION_FAILED:
503                        /* NNN resolveId errorCode */
504                        if (DBG) Slog.d(TAG, "SERVICE_RESOLVE_FAILED Raw: " + raw);
505                        stopResolveService(id);
506                        removeRequestMap(clientId, id, clientInfo);
507                        clientInfo.mResolvedService = null;
508                        clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
509                                NsdManager.FAILURE_INTERNAL_ERROR, clientId);
510                        break;
511                    case NativeResponseCode.SERVICE_GET_ADDR_FAILED:
512                        /* NNN resolveId errorCode */
513                        stopGetAddrInfo(id);
514                        removeRequestMap(clientId, id, clientInfo);
515                        clientInfo.mResolvedService = null;
516                        if (DBG) Slog.d(TAG, "SERVICE_RESOLVE_FAILED Raw: " + raw);
517                        clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
518                                NsdManager.FAILURE_INTERNAL_ERROR, clientId);
519                        break;
520                    case NativeResponseCode.SERVICE_GET_ADDR_SUCCESS:
521                        /* NNN resolveId hostname ttl addr */
522                        if (DBG) Slog.d(TAG, "SERVICE_GET_ADDR_SUCCESS Raw: " + raw);
523                        try {
524                            clientInfo.mResolvedService.setHost(InetAddress.getByName(cooked[4]));
525                            clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_SUCCEEDED,
526                                   0, clientId, clientInfo.mResolvedService);
527                        } catch (java.net.UnknownHostException e) {
528                            clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
529                                    NsdManager.FAILURE_INTERNAL_ERROR, clientId);
530                        }
531                        stopGetAddrInfo(id);
532                        removeRequestMap(clientId, id, clientInfo);
533                        clientInfo.mResolvedService = null;
534                        break;
535                    default:
536                        handled = false;
537                        break;
538                }
539                return handled;
540            }
541       }
542    }
543
544    private NativeDaemonConnector mNativeConnector;
545    private final CountDownLatch mNativeDaemonConnected = new CountDownLatch(1);
546
547    private NsdService(Context context) {
548        mContext = context;
549        mContentResolver = context.getContentResolver();
550
551        mNativeConnector = new NativeDaemonConnector(new NativeCallbackReceiver(), "mdns", 10,
552                MDNS_TAG, 25, null);
553
554        mNsdStateMachine = new NsdStateMachine(TAG);
555        mNsdStateMachine.start();
556
557        Thread th = new Thread(mNativeConnector, MDNS_TAG);
558        th.start();
559    }
560
561    public static NsdService create(Context context) throws InterruptedException {
562        NsdService service = new NsdService(context);
563        service.mNativeDaemonConnected.await();
564        return service;
565    }
566
567    public Messenger getMessenger() {
568        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET,
569            "NsdService");
570        return new Messenger(mNsdStateMachine.getHandler());
571    }
572
573    public void setEnabled(boolean enable) {
574        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL,
575                "NsdService");
576        Settings.Global.putInt(mContentResolver, Settings.Global.NSD_ON, enable ? 1 : 0);
577        if (enable) {
578            mNsdStateMachine.sendMessage(NsdManager.ENABLE);
579        } else {
580            mNsdStateMachine.sendMessage(NsdManager.DISABLE);
581        }
582    }
583
584    private void sendNsdStateChangeBroadcast(boolean enabled) {
585        final Intent intent = new Intent(NsdManager.ACTION_NSD_STATE_CHANGED);
586        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
587        if (enabled) {
588            intent.putExtra(NsdManager.EXTRA_NSD_STATE, NsdManager.NSD_STATE_ENABLED);
589        } else {
590            intent.putExtra(NsdManager.EXTRA_NSD_STATE, NsdManager.NSD_STATE_DISABLED);
591        }
592        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
593    }
594
595    private boolean isNsdEnabled() {
596        boolean ret = Settings.Global.getInt(mContentResolver, Settings.Global.NSD_ON, 1) == 1;
597        if (DBG) Slog.d(TAG, "Network service discovery enabled " + ret);
598        return ret;
599    }
600
601    private int getUniqueId() {
602        if (++mUniqueId == INVALID_ID) return ++mUniqueId;
603        return mUniqueId;
604    }
605
606    /* These should be in sync with system/netd/mDnsResponseCode.h */
607    class NativeResponseCode {
608        public static final int SERVICE_DISCOVERY_FAILED    =   602;
609        public static final int SERVICE_FOUND               =   603;
610        public static final int SERVICE_LOST                =   604;
611
612        public static final int SERVICE_REGISTRATION_FAILED =   605;
613        public static final int SERVICE_REGISTERED          =   606;
614
615        public static final int SERVICE_RESOLUTION_FAILED   =   607;
616        public static final int SERVICE_RESOLVED            =   608;
617
618        public static final int SERVICE_UPDATED             =   609;
619        public static final int SERVICE_UPDATE_FAILED       =   610;
620
621        public static final int SERVICE_GET_ADDR_FAILED     =   611;
622        public static final int SERVICE_GET_ADDR_SUCCESS    =   612;
623    }
624
625    private class NativeEvent {
626        final int code;
627        final String raw;
628
629        NativeEvent(int code, String raw) {
630            this.code = code;
631            this.raw = raw;
632        }
633    }
634
635    class NativeCallbackReceiver implements INativeDaemonConnectorCallbacks {
636        public void onDaemonConnected() {
637            mNativeDaemonConnected.countDown();
638        }
639
640        public boolean onCheckHoldWakeLock(int code) {
641            return false;
642        }
643
644        public boolean onEvent(int code, String raw, String[] cooked) {
645            // TODO: NDC translates a message to a callback, we could enhance NDC to
646            // directly interact with a state machine through messages
647            NativeEvent event = new NativeEvent(code, raw);
648            mNsdStateMachine.sendMessage(NsdManager.NATIVE_DAEMON_EVENT, event);
649            return true;
650        }
651    }
652
653    private boolean startMDnsDaemon() {
654        if (DBG) Slog.d(TAG, "startMDnsDaemon");
655        try {
656            mNativeConnector.execute("mdnssd", "start-service");
657        } catch(NativeDaemonConnectorException e) {
658            Slog.e(TAG, "Failed to start daemon" + e);
659            return false;
660        }
661        return true;
662    }
663
664    private boolean stopMDnsDaemon() {
665        if (DBG) Slog.d(TAG, "stopMDnsDaemon");
666        try {
667            mNativeConnector.execute("mdnssd", "stop-service");
668        } catch(NativeDaemonConnectorException e) {
669            Slog.e(TAG, "Failed to start daemon" + e);
670            return false;
671        }
672        return true;
673    }
674
675    private boolean registerService(int regId, NsdServiceInfo service) {
676        if (DBG) Slog.d(TAG, "registerService: " + regId + " " + service);
677        try {
678            Command cmd = new Command("mdnssd", "register", regId, service.getServiceName(),
679                    service.getServiceType(), service.getPort());
680
681            // Add TXT records as additional arguments.
682            Map<String, byte[]> txtRecords = service.getAttributes();
683            for (String key : txtRecords.keySet()) {
684                try {
685                    // TODO: Send encoded TXT record as bytes once NDC/netd supports binary data.
686                    cmd.appendArg(String.format(Locale.US, "%s=%s", key,
687                            new String(txtRecords.get(key), "UTF_8")));
688                } catch (UnsupportedEncodingException e) {
689                    Slog.e(TAG, "Failed to encode txtRecord " + e);
690                }
691            }
692
693            mNativeConnector.execute(cmd);
694        } catch(NativeDaemonConnectorException e) {
695            Slog.e(TAG, "Failed to execute registerService " + e);
696            return false;
697        }
698        return true;
699    }
700
701    private boolean unregisterService(int regId) {
702        if (DBG) Slog.d(TAG, "unregisterService: " + regId);
703        try {
704            mNativeConnector.execute("mdnssd", "stop-register", regId);
705        } catch(NativeDaemonConnectorException e) {
706            Slog.e(TAG, "Failed to execute unregisterService " + e);
707            return false;
708        }
709        return true;
710    }
711
712    private boolean updateService(int regId, DnsSdTxtRecord t) {
713        if (DBG) Slog.d(TAG, "updateService: " + regId + " " + t);
714        try {
715            if (t == null) return false;
716            mNativeConnector.execute("mdnssd", "update", regId, t.size(), t.getRawData());
717        } catch(NativeDaemonConnectorException e) {
718            Slog.e(TAG, "Failed to updateServices " + e);
719            return false;
720        }
721        return true;
722    }
723
724    private boolean discoverServices(int discoveryId, String serviceType) {
725        if (DBG) Slog.d(TAG, "discoverServices: " + discoveryId + " " + serviceType);
726        try {
727            mNativeConnector.execute("mdnssd", "discover", discoveryId, serviceType);
728        } catch(NativeDaemonConnectorException e) {
729            Slog.e(TAG, "Failed to discoverServices " + e);
730            return false;
731        }
732        return true;
733    }
734
735    private boolean stopServiceDiscovery(int discoveryId) {
736        if (DBG) Slog.d(TAG, "stopServiceDiscovery: " + discoveryId);
737        try {
738            mNativeConnector.execute("mdnssd", "stop-discover", discoveryId);
739        } catch(NativeDaemonConnectorException e) {
740            Slog.e(TAG, "Failed to stopServiceDiscovery " + e);
741            return false;
742        }
743        return true;
744    }
745
746    private boolean resolveService(int resolveId, NsdServiceInfo service) {
747        if (DBG) Slog.d(TAG, "resolveService: " + resolveId + " " + service);
748        try {
749            mNativeConnector.execute("mdnssd", "resolve", resolveId, service.getServiceName(),
750                    service.getServiceType(), "local.");
751        } catch(NativeDaemonConnectorException e) {
752            Slog.e(TAG, "Failed to resolveService " + e);
753            return false;
754        }
755        return true;
756    }
757
758    private boolean stopResolveService(int resolveId) {
759        if (DBG) Slog.d(TAG, "stopResolveService: " + resolveId);
760        try {
761            mNativeConnector.execute("mdnssd", "stop-resolve", resolveId);
762        } catch(NativeDaemonConnectorException e) {
763            Slog.e(TAG, "Failed to stop resolve " + e);
764            return false;
765        }
766        return true;
767    }
768
769    private boolean getAddrInfo(int resolveId, String hostname) {
770        if (DBG) Slog.d(TAG, "getAdddrInfo: " + resolveId);
771        try {
772            mNativeConnector.execute("mdnssd", "getaddrinfo", resolveId, hostname);
773        } catch(NativeDaemonConnectorException e) {
774            Slog.e(TAG, "Failed to getAddrInfo " + e);
775            return false;
776        }
777        return true;
778    }
779
780    private boolean stopGetAddrInfo(int resolveId) {
781        if (DBG) Slog.d(TAG, "stopGetAdddrInfo: " + resolveId);
782        try {
783            mNativeConnector.execute("mdnssd", "stop-getaddrinfo", resolveId);
784        } catch(NativeDaemonConnectorException e) {
785            Slog.e(TAG, "Failed to stopGetAddrInfo " + e);
786            return false;
787        }
788        return true;
789    }
790
791    @Override
792    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
793        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
794                != PackageManager.PERMISSION_GRANTED) {
795            pw.println("Permission Denial: can't dump ServiceDiscoverService from from pid="
796                    + Binder.getCallingPid()
797                    + ", uid=" + Binder.getCallingUid());
798            return;
799        }
800
801        for (ClientInfo client : mClients.values()) {
802            pw.println("Client Info");
803            pw.println(client);
804        }
805
806        mNsdStateMachine.dump(fd, pw, args);
807    }
808
809    /* arg2 on the source message has an id that needs to be retained in replies
810     * see NsdManager for details */
811    private Message obtainMessage(Message srcMsg) {
812        Message msg = Message.obtain();
813        msg.arg2 = srcMsg.arg2;
814        return msg;
815    }
816
817    private void replyToMessage(Message msg, int what) {
818        if (msg.replyTo == null) return;
819        Message dstMsg = obtainMessage(msg);
820        dstMsg.what = what;
821        mReplyChannel.replyToMessage(msg, dstMsg);
822    }
823
824    private void replyToMessage(Message msg, int what, int arg1) {
825        if (msg.replyTo == null) return;
826        Message dstMsg = obtainMessage(msg);
827        dstMsg.what = what;
828        dstMsg.arg1 = arg1;
829        mReplyChannel.replyToMessage(msg, dstMsg);
830    }
831
832    private void replyToMessage(Message msg, int what, Object obj) {
833        if (msg.replyTo == null) return;
834        Message dstMsg = obtainMessage(msg);
835        dstMsg.what = what;
836        dstMsg.obj = obj;
837        mReplyChannel.replyToMessage(msg, dstMsg);
838    }
839
840    /* Information tracked per client */
841    private class ClientInfo {
842
843        private static final int MAX_LIMIT = 10;
844        private final AsyncChannel mChannel;
845        private final Messenger mMessenger;
846        /* Remembers a resolved service until getaddrinfo completes */
847        private NsdServiceInfo mResolvedService;
848
849        /* A map from client id to unique id sent to mDns */
850        private SparseArray<Integer> mClientIds = new SparseArray<Integer>();
851
852        /* A map from client id to the type of the request we had received */
853        private SparseArray<Integer> mClientRequests = new SparseArray<Integer>();
854
855        private ClientInfo(AsyncChannel c, Messenger m) {
856            mChannel = c;
857            mMessenger = m;
858            if (DBG) Slog.d(TAG, "New client, channel: " + c + " messenger: " + m);
859        }
860
861        @Override
862        public String toString() {
863            StringBuffer sb = new StringBuffer();
864            sb.append("mChannel ").append(mChannel).append("\n");
865            sb.append("mMessenger ").append(mMessenger).append("\n");
866            sb.append("mResolvedService ").append(mResolvedService).append("\n");
867            for(int i = 0; i< mClientIds.size(); i++) {
868                int clientID = mClientIds.keyAt(i);
869                sb.append("clientId ").append(clientID).
870                    append(" mDnsId ").append(mClientIds.valueAt(i)).
871                    append(" type ").append(mClientRequests.get(clientID)).append("\n");
872            }
873            return sb.toString();
874        }
875
876        // Remove any pending requests from the global map when we get rid of a client,
877        // and send cancellations to the daemon.
878        private void expungeAllRequests() {
879            int globalId, clientId, i;
880            for (i = 0; i < mClientIds.size(); i++) {
881                clientId = mClientIds.keyAt(i);
882                globalId = mClientIds.valueAt(i);
883                mIdToClientInfoMap.remove(globalId);
884                if (DBG) Slog.d(TAG, "Terminating client-ID " + clientId +
885                        " global-ID " + globalId + " type " + mClientRequests.get(clientId));
886                switch (mClientRequests.get(clientId)) {
887                    case NsdManager.DISCOVER_SERVICES:
888                        stopServiceDiscovery(globalId);
889                        break;
890                    case NsdManager.RESOLVE_SERVICE:
891                        stopResolveService(globalId);
892                        break;
893                    case NsdManager.REGISTER_SERVICE:
894                        unregisterService(globalId);
895                        break;
896                    default:
897                        break;
898                }
899            }
900            mClientIds.clear();
901            mClientRequests.clear();
902        }
903
904        // mClientIds is a sparse array of listener id -> mDnsClient id.  For a given mDnsClient id,
905        // return the corresponding listener id.  mDnsClient id is also called a global id.
906        private int getClientId(final int globalId) {
907            // This doesn't use mClientIds.indexOfValue because indexOfValue uses == (not .equals)
908            // while also coercing the int primitives to Integer objects.
909            for (int i = 0, nSize = mClientIds.size(); i < nSize; i++) {
910                int mDnsId = mClientIds.valueAt(i);
911                if (globalId == mDnsId) {
912                    return mClientIds.keyAt(i);
913                }
914            }
915            return -1;
916        }
917    }
918}
919