/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.printservice.recommendation.util; import android.content.Context; import android.net.nsd.NsdManager; import android.net.nsd.NsdServiceInfo; import android.annotation.NonNull; import android.annotation.Nullable; import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import com.android.printservice.recommendation.PrintServicePlugin; import java.util.HashSet; import java.util.Set; /** * A discovery listening for mDNS results and only adding the ones that {@link * PrinterFilter#matchesCriteria match} configured list */ public class MDNSFilteredDiscovery implements NsdManager.DiscoveryListener { private static final String LOG_TAG = "MDNSFilteredDiscovery"; /** * mDNS service filter interface. * Implement {@link PrinterFilter#matchesCriteria} to filter out supported services */ public interface PrinterFilter { /** * Main filter method. Should return true if mDNS service is supported * by the print service plugin * * @param nsdServiceInfo The service info to check * * @return True if service is supported by the print service plugin */ boolean matchesCriteria(NsdServiceInfo nsdServiceInfo); } /** Printer identifiers of the mPrinters found. */ @GuardedBy("mLock") private final @NonNull HashSet<String> mPrinters; /** Service types discovered by this plugin */ private final @NonNull HashSet<String> mServiceTypes; /** Context of the user of this plugin */ private final @NonNull Context mContext; /** mDNS services filter */ private final @NonNull PrinterFilter mPrinterFilter; /** * Call back to report the number of mPrinters found. * * We assume that {@link #start} and {@link #stop} are never called in parallel, hence it is * safe to not synchronize access to this field. */ private @Nullable PrintServicePlugin.PrinterDiscoveryCallback mCallback; /** Queue used to resolve nsd infos */ private final @NonNull NsdResolveQueue mResolveQueue; /** * Create new stub that assumes that a print service can be used to print on all mPrinters * matching some mDNS names. * * @param context The context the plugin runs in * @param serviceTypes The mDNS service types to listen to. * @param printerFilter The filter for mDNS services */ public MDNSFilteredDiscovery(@NonNull Context context, @NonNull Set<String> serviceTypes, @NonNull PrinterFilter printerFilter) { mContext = Preconditions.checkNotNull(context, "context"); mServiceTypes = new HashSet<>(Preconditions .checkCollectionNotEmpty(Preconditions.checkCollectionElementsNotNull(serviceTypes, "serviceTypes"), "serviceTypes")); mPrinterFilter = Preconditions.checkNotNull(printerFilter, "printerFilter"); mResolveQueue = NsdResolveQueue.getInstance(); mPrinters = new HashSet<>(); } /** * @return The NDS manager */ private NsdManager getNDSManager() { return (NsdManager) mContext.getSystemService(Context.NSD_SERVICE); } /** * Start the discovery. * * @param callback Callbacks used by this plugin. */ public void start(@NonNull PrintServicePlugin.PrinterDiscoveryCallback callback) { mCallback = callback; mCallback.onChanged(mPrinters.size()); for (String serviceType : mServiceTypes) { DiscoveryListenerMultiplexer.addListener(getNDSManager(), serviceType, this); } } /** * Stop the discovery. This can only return once the plugin is completely finished and cleaned up. */ public void stop() { mCallback.onChanged(0); mCallback = null; for (int i = 0; i < mServiceTypes.size(); ++i) { DiscoveryListenerMultiplexer.removeListener(getNDSManager(), this); } } /** * * @return The number of discovered printers */ public int getCount() { return mPrinters.size(); } @Override public void onStartDiscoveryFailed(String serviceType, int errorCode) { Log.w(LOG_TAG, "Failed to start network discovery for type " + serviceType + ": " + errorCode); } @Override public void onStopDiscoveryFailed(String serviceType, int errorCode) { Log.w(LOG_TAG, "Failed to stop network discovery for type " + serviceType + ": " + errorCode); } @Override public void onDiscoveryStarted(String serviceType) { // empty } @Override public void onDiscoveryStopped(String serviceType) { mPrinters.clear(); } @Override public void onServiceFound(NsdServiceInfo serviceInfo) { mResolveQueue.resolve(getNDSManager(), serviceInfo, new NsdManager.ResolveListener() { @Override public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) { Log.w(LOG_TAG, "Service found: could not resolve " + serviceInfo + ": " + errorCode); } @Override public void onServiceResolved(NsdServiceInfo serviceInfo) { if (mPrinterFilter.matchesCriteria(serviceInfo)) { if (mCallback != null) { boolean added = mPrinters.add(serviceInfo.getHost().getHostAddress()); if (added) { mCallback.onChanged(mPrinters.size()); } } } } }); } @Override public void onServiceLost(NsdServiceInfo serviceInfo) { mResolveQueue.resolve(getNDSManager(), serviceInfo, new NsdManager.ResolveListener() { @Override public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) { Log.w(LOG_TAG, "Service lost: Could not resolve " + serviceInfo + ": " + errorCode); } @Override public void onServiceResolved(NsdServiceInfo serviceInfo) { if (mPrinterFilter.matchesCriteria(serviceInfo)) { if (mCallback != null) { boolean removed = mPrinters .remove(serviceInfo.getHost().getHostAddress()); if (removed) { mCallback.onChanged(mPrinters.size()); } } } } }); } }