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