1 /* 2 * Copyright (C) 2016 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 package com.android.printservice.recommendation.util; 17 18 import android.annotation.NonNull; 19 import android.annotation.Nullable; 20 import android.content.Context; 21 import android.net.nsd.NsdManager; 22 import android.net.nsd.NsdServiceInfo; 23 import android.util.Log; 24 25 import com.android.internal.annotations.GuardedBy; 26 import com.android.internal.util.Preconditions; 27 import com.android.printservice.recommendation.PrintServicePlugin; 28 29 import java.net.InetAddress; 30 import java.util.ArrayList; 31 import java.util.HashSet; 32 import java.util.Set; 33 34 /** 35 * A discovery listening for mDNS results and only adding the ones that {@link 36 * PrinterFilter#matchesCriteria match} configured list 37 */ 38 public class MDNSFilteredDiscovery implements NsdManager.DiscoveryListener { 39 private static final String LOG_TAG = "MDNSFilteredDiscovery"; 40 41 /** 42 * mDNS service filter interface. 43 * Implement {@link PrinterFilter#matchesCriteria} to filter out supported services 44 */ 45 public interface PrinterFilter { 46 /** 47 * Main filter method. Should return true if mDNS service is supported 48 * by the print service plugin 49 * 50 * @param nsdServiceInfo The service info to check 51 * 52 * @return True if service is supported by the print service plugin 53 */ 54 boolean matchesCriteria(NsdServiceInfo nsdServiceInfo); 55 } 56 57 /** Printer identifiers of the mPrinters found. */ 58 @GuardedBy("mLock") 59 private final @NonNull HashSet<InetAddress> mPrinters; 60 61 /** Service types discovered by this plugin */ 62 private final @NonNull HashSet<String> mServiceTypes; 63 64 /** Context of the user of this plugin */ 65 private final @NonNull Context mContext; 66 67 /** mDNS services filter */ 68 private final @NonNull PrinterFilter mPrinterFilter; 69 70 /** 71 * Call back to report the number of mPrinters found. 72 * 73 * We assume that {@link #start} and {@link #stop} are never called in parallel, hence it is 74 * safe to not synchronize access to this field. 75 */ 76 private @Nullable PrintServicePlugin.PrinterDiscoveryCallback mCallback; 77 78 /** Queue used to resolve nsd infos */ 79 private final @NonNull NsdResolveQueue mResolveQueue; 80 81 /** 82 * Create new stub that assumes that a print service can be used to print on all mPrinters 83 * matching some mDNS names. 84 * 85 * @param context The context the plugin runs in 86 * @param serviceTypes The mDNS service types to listen to. 87 * @param printerFilter The filter for mDNS services 88 */ 89 public MDNSFilteredDiscovery(@NonNull Context context, 90 @NonNull Set<String> serviceTypes, 91 @NonNull PrinterFilter printerFilter) { 92 mContext = Preconditions.checkNotNull(context, "context"); 93 mServiceTypes = new HashSet<>(Preconditions 94 .checkCollectionNotEmpty(Preconditions.checkCollectionElementsNotNull(serviceTypes, 95 "serviceTypes"), "serviceTypes")); 96 mPrinterFilter = Preconditions.checkNotNull(printerFilter, "printerFilter"); 97 98 mResolveQueue = NsdResolveQueue.getInstance(); 99 mPrinters = new HashSet<>(); 100 } 101 102 /** 103 * @return The NDS manager 104 */ 105 private NsdManager getNDSManager() { 106 return (NsdManager) mContext.getSystemService(Context.NSD_SERVICE); 107 } 108 109 /** 110 * Start the discovery. 111 * 112 * @param callback Callbacks used by this plugin. 113 */ 114 public void start(@NonNull PrintServicePlugin.PrinterDiscoveryCallback callback) { 115 mCallback = callback; 116 mCallback.onChanged(new ArrayList<>(mPrinters)); 117 118 for (String serviceType : mServiceTypes) { 119 DiscoveryListenerMultiplexer.addListener(getNDSManager(), serviceType, this); 120 } 121 } 122 123 /** 124 * Stop the discovery. This can only return once the plugin is completely finished and cleaned up. 125 */ 126 public void stop() { 127 mCallback.onChanged(null); 128 mCallback = null; 129 130 for (int i = 0; i < mServiceTypes.size(); ++i) { 131 DiscoveryListenerMultiplexer.removeListener(getNDSManager(), this); 132 } 133 } 134 135 @Override 136 public void onStartDiscoveryFailed(String serviceType, int errorCode) { 137 Log.w(LOG_TAG, "Failed to start network discovery for type " + serviceType + ": " 138 + errorCode); 139 } 140 141 @Override 142 public void onStopDiscoveryFailed(String serviceType, int errorCode) { 143 Log.w(LOG_TAG, "Failed to stop network discovery for type " + serviceType + ": " 144 + errorCode); 145 } 146 147 @Override 148 public void onDiscoveryStarted(String serviceType) { 149 // empty 150 } 151 152 @Override 153 public void onDiscoveryStopped(String serviceType) { 154 mPrinters.clear(); 155 } 156 157 @Override 158 public void onServiceFound(NsdServiceInfo serviceInfo) { 159 mResolveQueue.resolve(getNDSManager(), serviceInfo, 160 new NsdManager.ResolveListener() { 161 @Override 162 public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) { 163 Log.w(LOG_TAG, "Service found: could not resolve " + serviceInfo + ": " + 164 errorCode); 165 } 166 167 @Override 168 public void onServiceResolved(NsdServiceInfo serviceInfo) { 169 if (mPrinterFilter.matchesCriteria(serviceInfo)) { 170 if (mCallback != null) { 171 boolean added = mPrinters.add(serviceInfo.getHost()); 172 if (added) { 173 mCallback.onChanged(new ArrayList<>(mPrinters)); 174 } 175 } 176 } 177 } 178 }); 179 } 180 181 @Override 182 public void onServiceLost(NsdServiceInfo serviceInfo) { 183 mResolveQueue.resolve(getNDSManager(), serviceInfo, 184 new NsdManager.ResolveListener() { 185 @Override 186 public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) { 187 Log.w(LOG_TAG, "Service lost: Could not resolve " + serviceInfo + ": " 188 + errorCode); 189 } 190 191 @Override 192 public void onServiceResolved(NsdServiceInfo serviceInfo) { 193 if (mPrinterFilter.matchesCriteria(serviceInfo)) { 194 if (mCallback != null) { 195 boolean removed = mPrinters.remove(serviceInfo.getHost()); 196 197 if (removed) { 198 mCallback.onChanged(new ArrayList<>(mPrinters)); 199 } 200 } 201 } 202 } 203 }); 204 } 205 }