• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  * Copyright (C) 2016 Mopria Alliance, Inc.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.android.bips.discovery;
19 
20 import android.content.Context;
21 import android.net.Uri;
22 import android.net.nsd.NsdManager;
23 import android.net.nsd.NsdServiceInfo;
24 import android.os.Handler;
25 import android.text.TextUtils;
26 import android.util.Log;
27 
28 import com.android.bips.BuiltInPrintService;
29 
30 import java.net.Inet4Address;
31 import java.util.Locale;
32 import java.util.Map;
33 
34 /**
35  * Search the local network for devices advertising IPP print services
36  */
37 public class MdnsDiscovery extends Discovery {
38     private static final String TAG = MdnsDiscovery.class.getSimpleName();
39     private static final boolean DEBUG = false;
40 
41     // Prepend this to a UUID to create a proper URN
42     private static final String PREFIX_URN_UUID = "urn:uuid:";
43 
44     // Keys for expected txtRecord attributes
45     private static final String ATTRIBUTE_RP = "rp";
46     private static final String ATTRIBUTE_UUID = "UUID";
47     private static final String ATTRIBUTE_NOTE = "note";
48     private static final String ATTRIBUTE_PRINT_WFDS = "print_wfds";
49     private static final String VALUE_PRINT_WFDS_OPT_OUT = "F";
50 
51     // Service name of interest
52     private static final String SERVICE_IPP = "_ipp._tcp";
53 
54     /** Network Service Discovery Manager */
55     private final NsdManager mNsdManager;
56 
57     /** Handler used for posting to main thread */
58     private final Handler mMainHandler;
59 
60     /** Handle to listener when registered */
61     private NsdServiceListener mServiceListener;
62 
MdnsDiscovery(BuiltInPrintService printService)63     public MdnsDiscovery(BuiltInPrintService printService) {
64         this(printService, (NsdManager) printService.getSystemService(Context.NSD_SERVICE));
65     }
66 
67     /** Constructor for use by test */
MdnsDiscovery(BuiltInPrintService printService, NsdManager nsdManager)68     MdnsDiscovery(BuiltInPrintService printService, NsdManager nsdManager) {
69         super(printService);
70         mNsdManager = nsdManager;
71         mMainHandler = new Handler(printService.getMainLooper());
72     }
73 
74     /** Return a valid {@link DiscoveredPrinter} from {@link NsdServiceInfo}, or null if invalid */
toNetworkPrinter(NsdServiceInfo info)75     private static DiscoveredPrinter toNetworkPrinter(NsdServiceInfo info) {
76         // Honor printers that deliberately opt-out
77         if (VALUE_PRINT_WFDS_OPT_OUT.equals(getStringAttribute(info, ATTRIBUTE_PRINT_WFDS))) {
78             if (DEBUG) Log.d(TAG, "Opted out: " + info);
79             return null;
80         }
81 
82         // Collect resource path
83         String resourcePath = getStringAttribute(info, ATTRIBUTE_RP);
84         if (TextUtils.isEmpty(resourcePath)) {
85             if (DEBUG) Log.d(TAG, "Missing RP" + info);
86             return null;
87         }
88         if (resourcePath.startsWith("/")) {
89             resourcePath = resourcePath.substring(1);
90         }
91 
92         // Hopefully has a UUID
93         Uri uuidUri = null;
94         String uuid = getStringAttribute(info, ATTRIBUTE_UUID);
95         if (!TextUtils.isEmpty(uuid)) {
96             uuidUri = Uri.parse(PREFIX_URN_UUID + uuid);
97         }
98 
99         // Must be IPv4
100         if (!(info.getHost() instanceof Inet4Address)) {
101             if (DEBUG) Log.d(TAG, "Not IPv4" + info);
102             return null;
103         }
104 
105         Uri path = Uri.parse("ipp://" + info.getHost().getHostAddress() +
106                 ":" + info.getPort() + "/" + resourcePath);
107         String location = getStringAttribute(info, ATTRIBUTE_NOTE);
108 
109         return new DiscoveredPrinter(uuidUri, info.getServiceName(), path, location);
110     }
111 
112     /** Return the value of an attribute or null if not present */
getStringAttribute(NsdServiceInfo info, String key)113     private static String getStringAttribute(NsdServiceInfo info, String key) {
114         key = key.toLowerCase(Locale.US);
115         for (Map.Entry<String, byte[]> entry : info.getAttributes().entrySet()) {
116             if (entry.getKey().toLowerCase(Locale.US).equals(key) && entry.getValue() != null) {
117                 return new String(entry.getValue());
118             }
119         }
120         return null;
121     }
122 
123     @Override
onStart()124     void onStart() {
125         if (DEBUG) Log.d(TAG, "onStart()");
126         mServiceListener = new NsdServiceListener();
127         mNsdManager.discoverServices(SERVICE_IPP, NsdManager.PROTOCOL_DNS_SD, mServiceListener);
128     }
129 
130     @Override
onStop()131     void onStop() {
132         if (DEBUG) Log.d(TAG, "onStop()");
133 
134         if (mServiceListener != null) {
135             mNsdManager.stopServiceDiscovery(mServiceListener);
136             mServiceListener = null;
137         }
138         mMainHandler.removeCallbacksAndMessages(null);
139         NsdResolveQueue.getInstance(getPrintService()).clear();
140     }
141 
142     /**
143      * Manage notifications from NsdManager
144      */
145     private class NsdServiceListener implements NsdManager.DiscoveryListener,
146             NsdManager.ResolveListener {
147         @Override
onStartDiscoveryFailed(String serviceType, int errorCode)148         public void onStartDiscoveryFailed(String serviceType, int errorCode) {
149             Log.w(TAG, "onStartDiscoveryFailed: " + errorCode);
150             mServiceListener = null;
151         }
152 
153         @Override
onStopDiscoveryFailed(String s, int errorCode)154         public void onStopDiscoveryFailed(String s, int errorCode) {
155             Log.w(TAG, "onStopDiscoveryFailed: " + errorCode);
156         }
157 
158         @Override
onDiscoveryStarted(String s)159         public void onDiscoveryStarted(String s) {
160             if (DEBUG) Log.d(TAG, "onDiscoveryStarted");
161         }
162 
163         @Override
onDiscoveryStopped(String s)164         public void onDiscoveryStopped(String s) {
165             if (DEBUG) Log.d(TAG, "onDiscoveryStopped");
166 
167             // On the main thread, notify loss of all known printers
168             mMainHandler.post(() -> allPrintersLost());
169         }
170 
171         @Override
onServiceFound(final NsdServiceInfo info)172         public void onServiceFound(final NsdServiceInfo info) {
173             if (DEBUG) Log.d(TAG, "onServiceFound - " + info.getServiceName());
174             NsdResolveQueue.getInstance(getPrintService()).resolve(mNsdManager, info, this);
175         }
176 
177         @Override
onServiceLost(final NsdServiceInfo info)178         public void onServiceLost(final NsdServiceInfo info) {
179             if (DEBUG) Log.d(TAG, "onServiceLost - " + info.getServiceName());
180 
181             // On the main thread, seek the missing printer by name and notify its loss
182             mMainHandler.post(() -> {
183                 for (DiscoveredPrinter printer : getPrinters()) {
184                     if (TextUtils.equals(printer.name, info.getServiceName())) {
185                         printerLost(printer.getUri());
186                         return;
187                     }
188                 }
189             });
190         }
191 
192         @Override
onResolveFailed(final NsdServiceInfo info, final int errorCode)193         public void onResolveFailed(final NsdServiceInfo info, final int errorCode) {
194         }
195 
196         @Override
onServiceResolved(final NsdServiceInfo info)197         public void onServiceResolved(final NsdServiceInfo info) {
198             final DiscoveredPrinter printer = toNetworkPrinter(info);
199             if (DEBUG) Log.d(TAG, "Service " + info.getServiceName() + " resolved to " + printer);
200             if (printer == null) {
201                 return;
202             }
203 
204             mMainHandler.post(() -> printerFound(printer));
205         }
206     }
207 }