OSDN Git Service

[RESTRICT_AUTOMERGE]: Add cross user permission check - areNotificationsEnabledForPackage
[android-x86/frameworks-base.git] / services / core / java / com / android / server / NsdService.java
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
17 package com.android.server;
18
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;
40
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;
47
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;
54
55 /**
56  * Network Service Discovery Service handles remote service discovery operation requests by
57  * implementing the INsdManager interface.
58  *
59  * @hide
60  */
61 public class NsdService extends INsdManager.Stub {
62     private static final String TAG = "NsdService";
63     private static final String MDNS_TAG = "mDnsConnector";
64
65     private static final boolean DBG = true;
66
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;
72
73     /**
74      * Clients receiving asynchronous messages
75      */
76     private final HashMap<Messenger, ClientInfo> mClients = new HashMap<>();
77
78     /* A map from unique id to client info */
79     private final SparseArray<ClientInfo> mIdToClientInfoMap= new SparseArray<>();
80
81     private final AsyncChannel mReplyChannel = new AsyncChannel();
82
83     private static final int INVALID_ID = 0;
84     private int mUniqueId = 1;
85
86     private class NsdStateMachine extends StateMachine {
87
88         private final DefaultState mDefaultState = new DefaultState();
89         private final DisabledState mDisabledState = new DisabledState();
90         private final EnabledState mEnabledState = new EnabledState();
91
92         @Override
93         protected String getWhatToString(int what) {
94             return NsdManager.nameOf(what);
95         }
96
97         /**
98          * Observes the NSD on/off setting, and takes action when changed.
99          */
100         private void registerForNsdSetting() {
101             final ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
102                 @Override
103                 public void onChange(boolean selfChange) {
104                     notifyEnabled(isNsdEnabled());
105                 }
106             };
107
108             final Uri uri = Settings.Global.getUriFor(Settings.Global.NSD_ON);
109             mNsdSettings.registerContentObserver(uri, contentObserver);
110         }
111
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);
119             setLogRecSize(25);
120             registerForNsdSetting();
121         }
122
123         class DefaultState extends State {
124             @Override
125             public boolean processMessage(Message msg) {
126                 ClientInfo cInfo = null;
127                 switch (msg.what) {
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);
135                         } else {
136                             Slog.e(TAG, "Client connection failure, error=" + msg.arg1);
137                         }
138                         break;
139                     case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
140                         switch (msg.arg1) {
141                             case AsyncChannel.STATUS_SEND_UNSUCCESSFUL:
142                                 Slog.e(TAG, "Send failed, client connection lost");
143                                 break;
144                             case AsyncChannel.STATUS_REMOTE_DISCONNECTION:
145                                 if (DBG) Slog.d(TAG, "Client disconnected");
146                                 break;
147                             default:
148                                 if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
149                                 break;
150                         }
151                         cInfo = mClients.get(msg.replyTo);
152                         if (cInfo != null) {
153                             cInfo.expungeAllRequests();
154                             mClients.remove(msg.replyTo);
155                         }
156                         //Last client
157                         if (mClients.size() == 0) {
158                             mDaemon.stop();
159                         }
160                         break;
161                     case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
162                         AsyncChannel ac = new AsyncChannel();
163                         ac.connect(mContext, getHandler(), msg.replyTo);
164                         break;
165                     case NsdManager.DISCOVER_SERVICES:
166                         replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
167                                 NsdManager.FAILURE_INTERNAL_ERROR);
168                        break;
169                     case NsdManager.STOP_DISCOVERY:
170                        replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
171                                NsdManager.FAILURE_INTERNAL_ERROR);
172                         break;
173                     case NsdManager.REGISTER_SERVICE:
174                         replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
175                                 NsdManager.FAILURE_INTERNAL_ERROR);
176                         break;
177                     case NsdManager.UNREGISTER_SERVICE:
178                         replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
179                                 NsdManager.FAILURE_INTERNAL_ERROR);
180                         break;
181                     case NsdManager.RESOLVE_SERVICE:
182                         replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
183                                 NsdManager.FAILURE_INTERNAL_ERROR);
184                         break;
185                     case NsdManager.NATIVE_DAEMON_EVENT:
186                     default:
187                         Slog.e(TAG, "Unhandled " + msg);
188                         return NOT_HANDLED;
189                 }
190                 return HANDLED;
191             }
192         }
193
194         class DisabledState extends State {
195             @Override
196             public void enter() {
197                 sendNsdStateChangeBroadcast(false);
198             }
199
200             @Override
201             public boolean processMessage(Message msg) {
202                 switch (msg.what) {
203                     case NsdManager.ENABLE:
204                         transitionTo(mEnabledState);
205                         break;
206                     default:
207                         return NOT_HANDLED;
208                 }
209                 return HANDLED;
210             }
211         }
212
213         class EnabledState extends State {
214             @Override
215             public void enter() {
216                 sendNsdStateChangeBroadcast(true);
217                 if (mClients.size() > 0) {
218                     mDaemon.start();
219                 }
220             }
221
222             @Override
223             public void exit() {
224                 if (mClients.size() > 0) {
225                     mDaemon.stop();
226                 }
227             }
228
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);
232                     return true;
233                 }
234                 return false;
235             }
236
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);
241             }
242
243             private void removeRequestMap(int clientId, int globalId, ClientInfo clientInfo) {
244                 clientInfo.mClientIds.delete(clientId);
245                 clientInfo.mClientRequests.delete(clientId);
246                 mIdToClientInfoMap.remove(globalId);
247             }
248
249             @Override
250             public boolean processMessage(Message msg) {
251                 ClientInfo clientInfo;
252                 NsdServiceInfo servInfo;
253                 int id;
254                 switch (msg.what) {
255                     case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
256                         //First client
257                         if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL &&
258                                 mClients.size() == 0) {
259                             mDaemon.start();
260                         }
261                         return NOT_HANDLED;
262                     case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
263                         return NOT_HANDLED;
264                     case NsdManager.DISABLE:
265                         //TODO: cleanup clients
266                         transitionTo(mDisabledState);
267                         break;
268                     case NsdManager.DISCOVER_SERVICES:
269                         if (DBG) Slog.d(TAG, "Discover services");
270                         servInfo = (NsdServiceInfo) msg.obj;
271                         clientInfo = mClients.get(msg.replyTo);
272
273                         if (requestLimitReached(clientInfo)) {
274                             replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
275                                     NsdManager.FAILURE_MAX_LIMIT);
276                             break;
277                         }
278
279                         id = getUniqueId();
280                         if (discoverServices(id, servInfo.getServiceType())) {
281                             if (DBG) {
282                                 Slog.d(TAG, "Discover " + msg.arg2 + " " + id +
283                                         servInfo.getServiceType());
284                             }
285                             storeRequestMap(msg.arg2, id, clientInfo, msg.what);
286                             replyToMessage(msg, NsdManager.DISCOVER_SERVICES_STARTED, servInfo);
287                         } else {
288                             stopServiceDiscovery(id);
289                             replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
290                                     NsdManager.FAILURE_INTERNAL_ERROR);
291                         }
292                         break;
293                     case NsdManager.STOP_DISCOVERY:
294                         if (DBG) Slog.d(TAG, "Stop service discovery");
295                         clientInfo = mClients.get(msg.replyTo);
296
297                         try {
298                             id = clientInfo.mClientIds.get(msg.arg2);
299                         } catch (NullPointerException e) {
300                             replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
301                                     NsdManager.FAILURE_INTERNAL_ERROR);
302                             break;
303                         }
304                         removeRequestMap(msg.arg2, id, clientInfo);
305                         if (stopServiceDiscovery(id)) {
306                             replyToMessage(msg, NsdManager.STOP_DISCOVERY_SUCCEEDED);
307                         } else {
308                             replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
309                                     NsdManager.FAILURE_INTERNAL_ERROR);
310                         }
311                         break;
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);
318                             break;
319                         }
320
321                         id = getUniqueId();
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
326                         } else {
327                             unregisterService(id);
328                             replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
329                                     NsdManager.FAILURE_INTERNAL_ERROR);
330                         }
331                         break;
332                     case NsdManager.UNREGISTER_SERVICE:
333                         if (DBG) Slog.d(TAG, "unregister service");
334                         clientInfo = mClients.get(msg.replyTo);
335                         try {
336                             id = clientInfo.mClientIds.get(msg.arg2);
337                         } catch (NullPointerException e) {
338                             replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
339                                     NsdManager.FAILURE_INTERNAL_ERROR);
340                             break;
341                         }
342                         removeRequestMap(msg.arg2, id, clientInfo);
343                         if (unregisterService(id)) {
344                             replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_SUCCEEDED);
345                         } else {
346                             replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
347                                     NsdManager.FAILURE_INTERNAL_ERROR);
348                         }
349                         break;
350                     case NsdManager.RESOLVE_SERVICE:
351                         if (DBG) Slog.d(TAG, "Resolve service");
352                         servInfo = (NsdServiceInfo) msg.obj;
353                         clientInfo = mClients.get(msg.replyTo);
354
355
356                         if (clientInfo.mResolvedService != null) {
357                             replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
358                                     NsdManager.FAILURE_ALREADY_ACTIVE);
359                             break;
360                         }
361
362                         id = getUniqueId();
363                         if (resolveService(id, servInfo)) {
364                             clientInfo.mResolvedService = new NsdServiceInfo();
365                             storeRequestMap(msg.arg2, id, clientInfo, msg.what);
366                         } else {
367                             replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
368                                     NsdManager.FAILURE_INTERNAL_ERROR);
369                         }
370                         break;
371                     case NsdManager.NATIVE_DAEMON_EVENT:
372                         NativeEvent event = (NativeEvent) msg.obj;
373                         if (!handleNativeEvent(event.code, event.raw, event.cooked)) {
374                             return NOT_HANDLED;
375                         }
376                         break;
377                     default:
378                         return NOT_HANDLED;
379                 }
380                 return HANDLED;
381             }
382
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));
390                     return false;
391                 }
392
393                 /* This goes in response as msg.arg2 */
394                 int clientId = clientInfo.getClientId(id);
395                 if (clientId < 0) {
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",
402                             name, id));
403                     return false;
404                 }
405                 if (DBG) {
406                     String name = NativeResponseCode.nameOf(code);
407                     Slog.d(TAG, String.format("Native daemon message %s: %s", name, raw));
408                 }
409                 switch (code) {
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,
414                                 clientId, servInfo);
415                         break;
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,
420                                 clientId, servInfo);
421                         break;
422                     case NativeResponseCode.SERVICE_DISCOVERY_FAILED:
423                         /* NNN uniqueId errorCode */
424                         clientInfo.mChannel.sendMessage(NsdManager.DISCOVER_SERVICES_FAILED,
425                                 NsdManager.FAILURE_INTERNAL_ERROR, clientId);
426                         break;
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);
432                         break;
433                     case NativeResponseCode.SERVICE_REGISTRATION_FAILED:
434                         /* NNN regId errorCode */
435                         clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_FAILED,
436                                NsdManager.FAILURE_INTERNAL_ERROR, clientId);
437                         break;
438                     case NativeResponseCode.SERVICE_UPDATED:
439                         /* NNN regId */
440                         break;
441                     case NativeResponseCode.SERVICE_UPDATE_FAILED:
442                         /* NNN regId errorCode */
443                         break;
444                     case NativeResponseCode.SERVICE_RESOLVED:
445                         /* NNN resolveId fullName hostName port txtlen txtdata */
446                         int index = 0;
447                         while (index < cooked[2].length() && cooked[2].charAt(index) != '.') {
448                             if (cooked[2].charAt(index) == '\\') {
449                                 ++index;
450                             }
451                             ++index;
452                         }
453                         if (index >= cooked[2].length()) {
454                             Slog.e(TAG, "Invalid service found " + raw);
455                             break;
456                         }
457                         String name = cooked[2].substring(0, index);
458                         String rest = cooked[2].substring(index);
459                         String type = rest.replace(".local.", "");
460
461                         name = unescape(name);
462
463                         clientInfo.mResolvedService.setServiceName(name);
464                         clientInfo.mResolvedService.setServiceType(type);
465                         clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4]));
466                         clientInfo.mResolvedService.setTxtRecords(cooked[6]);
467
468                         stopResolveService(id);
469                         removeRequestMap(clientId, id, clientInfo);
470
471                         int id2 = getUniqueId();
472                         if (getAddrInfo(id2, cooked[3])) {
473                             storeRequestMap(clientId, id2, clientInfo, NsdManager.RESOLVE_SERVICE);
474                         } else {
475                             clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
476                                     NsdManager.FAILURE_INTERNAL_ERROR, clientId);
477                             clientInfo.mResolvedService = null;
478                         }
479                         break;
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);
487                         break;
488                     case NativeResponseCode.SERVICE_GET_ADDR_FAILED:
489                         /* NNN resolveId errorCode */
490                         stopGetAddrInfo(id);
491                         removeRequestMap(clientId, id, clientInfo);
492                         clientInfo.mResolvedService = null;
493                         clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
494                                 NsdManager.FAILURE_INTERNAL_ERROR, clientId);
495                         break;
496                     case NativeResponseCode.SERVICE_GET_ADDR_SUCCESS:
497                         /* NNN resolveId hostname ttl addr */
498                         try {
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);
505                         }
506                         stopGetAddrInfo(id);
507                         removeRequestMap(clientId, id, clientInfo);
508                         clientInfo.mResolvedService = null;
509                         break;
510                     default:
511                         return false;
512                 }
513                 return true;
514             }
515        }
516     }
517
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);
522             if (c == '\\') {
523                 if (++i >= s.length()) {
524                     Slog.e(TAG, "Unexpected end of escape sequence in: " + s);
525                     break;
526                 }
527                 c = s.charAt(i);
528                 if (c != '.' && c != '\\') {
529                     if (i + 2 >= s.length()) {
530                         Slog.e(TAG, "Unexpected end of escape sequence in: " + s);
531                         break;
532                     }
533                     c = (char) ((c-'0') * 100 + (s.charAt(i+1)-'0') * 10 + (s.charAt(i+2)-'0'));
534                     i += 2;
535                 }
536             }
537             sb.append(c);
538         }
539         return sb.toString();
540     }
541
542     @VisibleForTesting
543     NsdService(Context ctx, NsdSettings settings, Handler handler, DaemonConnectionSupplier fn) {
544         mContext = ctx;
545         mNsdSettings = settings;
546         mNsdStateMachine = new NsdStateMachine(TAG, handler);
547         mNsdStateMachine.start();
548         mDaemonCallback = new NativeCallbackReceiver();
549         mDaemon = fn.get(mDaemonCallback);
550     }
551
552     public static NsdService create(Context context) throws InterruptedException {
553         NsdSettings settings = NsdSettings.makeDefault(context);
554         HandlerThread thread = new HandlerThread(TAG);
555         thread.start();
556         Handler handler = new Handler(thread.getLooper());
557         NsdService service = new NsdService(context, settings, handler, DaemonConnection::new);
558         service.mDaemonCallback.awaitConnection();
559         return service;
560     }
561
562     public Messenger getMessenger() {
563         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET, "NsdService");
564         return new Messenger(mNsdStateMachine.getHandler());
565     }
566
567     public void setEnabled(boolean isEnabled) {
568         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL,
569                 "NsdService");
570         mNsdSettings.putEnabledStatus(isEnabled);
571         notifyEnabled(isEnabled);
572     }
573
574     private void notifyEnabled(boolean isEnabled) {
575         mNsdStateMachine.sendMessage(isEnabled ? NsdManager.ENABLE : NsdManager.DISABLE);
576     }
577
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);
584     }
585
586     private boolean isNsdEnabled() {
587         boolean ret = mNsdSettings.isEnabled();
588         if (DBG) {
589             Slog.d(TAG, "Network service discovery is " + (ret ? "enabled" : "disabled"));
590         }
591         return ret;
592     }
593
594     private int getUniqueId() {
595         if (++mUniqueId == INVALID_ID) return ++mUniqueId;
596         return mUniqueId;
597     }
598
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;
604
605         public static final int SERVICE_REGISTRATION_FAILED =   605;
606         public static final int SERVICE_REGISTERED          =   606;
607
608         public static final int SERVICE_RESOLUTION_FAILED   =   607;
609         public static final int SERVICE_RESOLVED            =   608;
610
611         public static final int SERVICE_UPDATED             =   609;
612         public static final int SERVICE_UPDATE_FAILED       =   610;
613
614         public static final int SERVICE_GET_ADDR_FAILED     =   611;
615         public static final int SERVICE_GET_ADDR_SUCCESS    =   612;
616
617         private static final SparseArray<String> CODE_NAMES = new SparseArray<>();
618         static {
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");
630         }
631
632         static String nameOf(int code) {
633             String name = CODE_NAMES.get(code);
634             if (name == null) {
635                 return Integer.toString(code);
636             }
637             return name;
638         }
639     }
640
641     private class NativeEvent {
642         final int code;
643         final String raw;
644         final String[] cooked;
645
646         NativeEvent(int code, String raw, String[] cooked) {
647             this.code = code;
648             this.raw = raw;
649             this.cooked = cooked;
650         }
651     }
652
653     class NativeCallbackReceiver implements INativeDaemonConnectorCallbacks {
654         private final CountDownLatch connected = new CountDownLatch(1);
655
656         public void awaitConnection() throws InterruptedException {
657             connected.await();
658         }
659
660         @Override
661         public void onDaemonConnected() {
662             connected.countDown();
663         }
664
665         @Override
666         public boolean onCheckHoldWakeLock(int code) {
667             return false;
668         }
669
670         @Override
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);
676             return true;
677         }
678     }
679
680     interface DaemonConnectionSupplier {
681         DaemonConnection get(NativeCallbackReceiver callback);
682     }
683
684     @VisibleForTesting
685     public static class DaemonConnection {
686         final NativeDaemonConnector mNativeConnector;
687
688         DaemonConnection(NativeCallbackReceiver callback) {
689             mNativeConnector = new NativeDaemonConnector(callback, "mdns", 10, MDNS_TAG, 25, null);
690             new Thread(mNativeConnector, MDNS_TAG).start();
691         }
692
693         public boolean execute(Object... args) {
694             if (DBG) {
695                 Slog.d(TAG, "mdnssd " + Arrays.toString(args));
696             }
697             try {
698                 mNativeConnector.execute("mdnssd", args);
699             } catch (NativeDaemonConnectorException e) {
700                 Slog.e(TAG, "Failed to execute mdnssd " + Arrays.toString(args), e);
701                 return false;
702             }
703             return true;
704         }
705
706         public void start() {
707             execute("start-service");
708         }
709
710         public void stop() {
711             execute("stop-service");
712         }
713     }
714
715     private boolean registerService(int regId, NsdServiceInfo service) {
716         if (DBG) {
717             Slog.d(TAG, "registerService: " + regId + " " + service);
718         }
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);
725     }
726
727     private boolean unregisterService(int regId) {
728         return mDaemon.execute("stop-register", regId);
729     }
730
731     private boolean updateService(int regId, DnsSdTxtRecord t) {
732         if (t == null) {
733             return false;
734         }
735         return mDaemon.execute("update", regId, t.size(), t.getRawData());
736     }
737
738     private boolean discoverServices(int discoveryId, String serviceType) {
739         return mDaemon.execute("discover", discoveryId, serviceType);
740     }
741
742     private boolean stopServiceDiscovery(int discoveryId) {
743         return mDaemon.execute("stop-discover", discoveryId);
744     }
745
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.");
750     }
751
752     private boolean stopResolveService(int resolveId) {
753         return mDaemon.execute("stop-resolve", resolveId);
754     }
755
756     private boolean getAddrInfo(int resolveId, String hostname) {
757         return mDaemon.execute("getaddrinfo", resolveId, hostname);
758     }
759
760     private boolean stopGetAddrInfo(int resolveId) {
761         return mDaemon.execute("stop-getaddrinfo", resolveId);
762     }
763
764     @Override
765     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
766         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
767
768         for (ClientInfo client : mClients.values()) {
769             pw.println("Client Info");
770             pw.println(client);
771         }
772
773         mNsdStateMachine.dump(fd, pw, args);
774     }
775
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;
781         return msg;
782     }
783
784     private void replyToMessage(Message msg, int what) {
785         if (msg.replyTo == null) return;
786         Message dstMsg = obtainMessage(msg);
787         dstMsg.what = what;
788         mReplyChannel.replyToMessage(msg, dstMsg);
789     }
790
791     private void replyToMessage(Message msg, int what, int arg1) {
792         if (msg.replyTo == null) return;
793         Message dstMsg = obtainMessage(msg);
794         dstMsg.what = what;
795         dstMsg.arg1 = arg1;
796         mReplyChannel.replyToMessage(msg, dstMsg);
797     }
798
799     private void replyToMessage(Message msg, int what, Object obj) {
800         if (msg.replyTo == null) return;
801         Message dstMsg = obtainMessage(msg);
802         dstMsg.what = what;
803         dstMsg.obj = obj;
804         mReplyChannel.replyToMessage(msg, dstMsg);
805     }
806
807     /* Information tracked per client */
808     private class ClientInfo {
809
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;
815
816         /* A map from client id to unique id sent to mDns */
817         private final SparseIntArray mClientIds = new SparseIntArray();
818
819         /* A map from client id to the type of the request we had received */
820         private final SparseIntArray mClientRequests = new SparseIntArray();
821
822         private ClientInfo(AsyncChannel c, Messenger m) {
823             mChannel = c;
824             mMessenger = m;
825             if (DBG) Slog.d(TAG, "New client, channel: " + c + " messenger: " + m);
826         }
827
828         @Override
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");
839             }
840             return sb.toString();
841         }
842
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);
857                         break;
858                     case NsdManager.RESOLVE_SERVICE:
859                         stopResolveService(globalId);
860                         break;
861                     case NsdManager.REGISTER_SERVICE:
862                         unregisterService(globalId);
863                         break;
864                     default:
865                         break;
866                 }
867             }
868             mClientIds.clear();
869             mClientRequests.clear();
870         }
871
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);
876             if (idx < 0) {
877                 return idx;
878             }
879             return mClientIds.keyAt(idx);
880         }
881     }
882
883     /**
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.
886      */
887     @VisibleForTesting
888     public interface NsdSettings {
889         boolean isEnabled();
890         void putEnabledStatus(boolean isEnabled);
891         void registerContentObserver(Uri uri, ContentObserver observer);
892
893         static NsdSettings makeDefault(Context context) {
894             final ContentResolver resolver = context.getContentResolver();
895             return new NsdSettings() {
896                 @Override
897                 public boolean isEnabled() {
898                     return Settings.Global.getInt(resolver, Settings.Global.NSD_ON, 1) == 1;
899                 }
900
901                 @Override
902                 public void putEnabledStatus(boolean isEnabled) {
903                     Settings.Global.putInt(resolver, Settings.Global.NSD_ON, isEnabled ? 1 : 0);
904                 }
905
906                 @Override
907                 public void registerContentObserver(Uri uri, ContentObserver observer) {
908                     resolver.registerContentObserver(uri, false, observer);
909                 }
910             };
911         }
912     }
913 }