Merge "Update Samsung Print Recommendation Plugin (to support Mopria printers)" am: 41f14f47a5

am: 3a1cf2281b

Change-Id: Id94c02dc6106b5eb15274926ecd24e43c0ffc76f
This commit is contained in:
Sergey Yakovlev
2016-12-03 04:30:42 +00:00
committed by android-build-merger
14 changed files with 544 additions and 770 deletions

View File

@@ -32,13 +32,6 @@
<item>Hewlett Packard</item> <item>Hewlett Packard</item>
</string-array> </string-array>
<!-- Samsung plugin -->
<string-array name="known_print_vendor_info_for_samsung" translatable="false">
<item>com.sec.app.samsungprintservice</item>
<item>Samsung Electronics</item>
<item>Samsung</item>
</string-array>
<!-- Xerox plugin --> <!-- Xerox plugin -->
<string-array name="known_print_vendor_info_for_xerox" translatable="false"> <string-array name="known_print_vendor_info_for_xerox" translatable="false">
<item>com.xerox.printservice</item> <item>com.xerox.printservice</item>
@@ -49,6 +42,8 @@
<array name="known_print_plugin_vendors" translatable="false"> <array name="known_print_plugin_vendors" translatable="false">
<item>@array/known_print_vendor_info_for_mopria</item> <item>@array/known_print_vendor_info_for_mopria</item>
<item>@array/known_print_vendor_info_for_hp</item> <item>@array/known_print_vendor_info_for_hp</item>
<item>@array/known_print_vendor_info_for_samsung</item>
</array> </array>
<!-- Samsung plugin -->
<string name="plugin_package_samsung">com.sec.app.samsungprintservice</string>
</resources> </resources>

View File

@@ -78,7 +78,7 @@ public class RecommendationServiceImpl extends RecommendationService
try { try {
mPlugins.add(new RemotePrintServicePlugin(new SamsungRecommendationPlugin(this), this, mPlugins.add(new RemotePrintServicePlugin(new SamsungRecommendationPlugin(this), this,
false)); true));
} catch (Exception e) { } catch (Exception e) {
Log.e(LOG_TAG, "Could not initiate " + getString(R.string.plugin_vendor_samsung) + Log.e(LOG_TAG, "Could not initiate " + getString(R.string.plugin_vendor_samsung) +
" plugin", e); " plugin", e);

View File

@@ -16,30 +16,52 @@
package com.android.printservice.recommendation.plugin.mdnsFilter; package com.android.printservice.recommendation.plugin.mdnsFilter;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringRes;
import android.content.Context; import android.content.Context;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo; import android.net.nsd.NsdServiceInfo;
import android.util.Log; import android.annotation.NonNull;
import com.android.internal.annotations.GuardedBy; import android.annotation.StringRes;
import com.android.internal.util.Preconditions;
import com.android.printservice.recommendation.PrintServicePlugin; import com.android.printservice.recommendation.PrintServicePlugin;
import com.android.printservice.recommendation.util.DiscoveryListenerMultiplexer; import com.android.printservice.recommendation.util.MDNSFilteredDiscovery;
import com.android.printservice.recommendation.util.NsdResolveQueue; import com.android.printservice.recommendation.util.MDNSUtils;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
/** /**
* A plugin listening for mDNS results and only adding the ones that {@link * A plugin listening for mDNS results and only adding the ones that {@link
* MDNSUtils#isVendorPrinter match} configured list * MDNSUtils#isVendorPrinter match} configured list
*/ */
public class MDNSFilterPlugin implements PrintServicePlugin, NsdManager.DiscoveryListener { public class MDNSFilterPlugin implements PrintServicePlugin {
private static final String LOG_TAG = "MDNSFilterPlugin";
private static final String PRINTER_SERVICE_TYPE = "_ipp._tcp"; /** The mDNS service types supported */
private static final Set<String> PRINTER_SERVICE_TYPES = new HashSet<String>() {{
add("_ipp._tcp");
}};
/**
* The printer filter for {@link MDNSFilteredDiscovery} passing only mDNS results
* that {@link MDNSUtils#isVendorPrinter match} configured list
*/
private static class VendorNameFilter implements MDNSFilteredDiscovery.PrinterFilter {
/** mDNS names handled by the print service this plugin is for */
private final @NonNull Set<String> mMDNSNames;
/**
* Filter constructor
*
* @param vendorNames The vendor names to pass
*/
VendorNameFilter(@NonNull Set<String> vendorNames) {
mMDNSNames = new HashSet<>(vendorNames);
}
@Override
public boolean matchesCriteria(NsdServiceInfo nsdServiceInfo) {
return MDNSUtils.isVendorPrinter(nsdServiceInfo, mMDNSNames);
}
}
/** Name of the print service this plugin is for */ /** Name of the print service this plugin is for */
private final @StringRes int mName; private final @StringRes int mName;
@@ -47,26 +69,8 @@ public class MDNSFilterPlugin implements PrintServicePlugin, NsdManager.Discover
/** Package name of the print service this plugin is for */ /** Package name of the print service this plugin is for */
private final @NonNull CharSequence mPackageName; private final @NonNull CharSequence mPackageName;
/** mDNS names handled by the print service this plugin is for */ /** The mDNS filtered discovery */
private final @NonNull HashSet<String> mMDNSNames; private final MDNSFilteredDiscovery mMDNSFilteredDiscovery;
/** Printer identifiers of the mPrinters found. */
@GuardedBy("mLock")
private final @NonNull HashSet<String> mPrinters;
/** Context of the user of this plugin */
private final @NonNull Context mContext;
/**
* 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 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 * Create new stub that assumes that a print service can be used to print on all mPrinters
@@ -79,16 +83,11 @@ public class MDNSFilterPlugin implements PrintServicePlugin, NsdManager.Discover
*/ */
public MDNSFilterPlugin(@NonNull Context context, @NonNull String name, public MDNSFilterPlugin(@NonNull Context context, @NonNull String name,
@NonNull CharSequence packageName, @NonNull List<String> mDNSNames) { @NonNull CharSequence packageName, @NonNull List<String> mDNSNames) {
mContext = Preconditions.checkNotNull(context, "context"); mName = context.getResources().getIdentifier(name, null,
mName = mContext.getResources().getIdentifier(Preconditions.checkStringNotEmpty(name, "com.android.printservice.recommendation");
"name"), null, "com.android.printservice.recommendation"); mPackageName = packageName;
mPackageName = Preconditions.checkStringNotEmpty(packageName); mMDNSFilteredDiscovery = new MDNSFilteredDiscovery(context, PRINTER_SERVICE_TYPES,
mMDNSNames = new HashSet<>(Preconditions new VendorNameFilter(new HashSet<>(mDNSNames)));
.checkCollectionNotEmpty(Preconditions.checkCollectionElementsNotNull(mDNSNames,
"mDNSNames"), "mDNSNames"));
mResolveQueue = NsdResolveQueue.getInstance();
mPrinters = new HashSet<>();
} }
@Override @Override
@@ -96,18 +95,9 @@ public class MDNSFilterPlugin implements PrintServicePlugin, NsdManager.Discover
return mPackageName; return mPackageName;
} }
/**
* @return The NDS manager
*/
private NsdManager getNDSManager() {
return (NsdManager) mContext.getSystemService(Context.NSD_SERVICE);
}
@Override @Override
public void start(@NonNull PrinterDiscoveryCallback callback) throws Exception { public void start(@NonNull PrinterDiscoveryCallback callback) throws Exception {
mCallback = callback; mMDNSFilteredDiscovery.start(callback);
DiscoveryListenerMultiplexer.addListener(getNDSManager(), PRINTER_SERVICE_TYPE, this);
} }
@Override @Override
@@ -117,82 +107,6 @@ public class MDNSFilterPlugin implements PrintServicePlugin, NsdManager.Discover
@Override @Override
public void stop() throws Exception { public void stop() throws Exception {
mCallback.onChanged(0); mMDNSFilteredDiscovery.stop();
mCallback = null;
DiscoveryListenerMultiplexer.removeListener(getNDSManager(), this);
}
@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 (MDNSUtils.isVendorPrinter(serviceInfo, mMDNSNames)) {
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 (MDNSUtils.isVendorPrinter(serviceInfo, mMDNSNames)) {
if (mCallback != null) {
boolean removed = mPrinters
.remove(serviceInfo.getHost().getHostAddress());
if (removed) {
mCallback.onChanged(mPrinters.size());
}
}
}
}
});
} }
} }

View File

@@ -1,74 +0,0 @@
/*
* 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.plugin.samsung;
import android.net.nsd.NsdServiceInfo;
import android.text.TextUtils;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import java.util.Map;
public class MDnsUtils {
public static final String ATTRIBUTE__TY = "ty";
public static final String ATTRIBUTE__PRODUCT = "product";
public static final String ATTRIBUTE__USB_MFG = "usb_MFG";
public static final String ATTRIBUTE__MFG = "mfg";
public static String getString(byte[] value) {
if (value != null) return new String(value,StandardCharsets.UTF_8);
return null;
}
public static boolean isVendorPrinter(NsdServiceInfo networkDevice, String[] vendorValues) {
Map<String,byte[]> attributes = networkDevice.getAttributes();
String product = getString(attributes.get(ATTRIBUTE__PRODUCT));
String ty = getString(attributes.get(ATTRIBUTE__TY));
String usbMfg = getString(attributes.get(ATTRIBUTE__USB_MFG));
String mfg = getString(attributes.get(ATTRIBUTE__MFG));
return containsVendor(product, vendorValues) || containsVendor(ty, vendorValues) || containsVendor(usbMfg, vendorValues) || containsVendor(mfg, vendorValues);
}
public static String getVendor(NsdServiceInfo networkDevice) {
String vendor;
Map<String,byte[]> attributes = networkDevice.getAttributes();
vendor = getString(attributes.get(ATTRIBUTE__MFG));
if (!TextUtils.isEmpty(vendor)) return vendor;
vendor = getString(attributes.get(ATTRIBUTE__USB_MFG));
if (!TextUtils.isEmpty(vendor)) return vendor;
return null;
}
private static boolean containsVendor(String container, String[] vendorValues) {
if ((container == null) || (vendorValues == null)) return false;
for (String value : vendorValues) {
if (containsString(container, value)
|| containsString(container.toLowerCase(Locale.US), value.toLowerCase(Locale.US))
|| containsString(container.toUpperCase(Locale.US), value.toUpperCase(Locale.US)))
return true;
}
return false;
}
private static boolean containsString(String container, String contained) {
return (container != null) && (contained != null) && (container.equalsIgnoreCase(contained) || container.contains(contained + " "));
}
}

View File

@@ -0,0 +1,63 @@
/*
* (c) Copyright 2016 Samsung Electronics
* (c) Copyright 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.plugin.samsung;
import android.net.nsd.NsdServiceInfo;
import android.text.TextUtils;
import android.util.Log;
import com.android.printservice.recommendation.util.MDNSFilteredDiscovery;
import com.android.printservice.recommendation.util.MDNSUtils;
import java.util.HashSet;
import java.util.Set;
/**
* Printer filter for Mopria printer models supported by the print service plugin
*/
class PrinterFilterMopria implements MDNSFilteredDiscovery.PrinterFilter {
private static final String TAG = "PrinterFilterMopria";
static final Set<String> MOPRIA_MDNS_SERVICES = new HashSet<String>() {{
add("_ipp._tcp");
add("_ipps._tcp");
}};
private static final String PDL__PDF = "application/pdf";
private static final String PDL__PCLM = "application/PCLm";
private static final String PDL__PWG_RASTER = "image/pwg-raster";
private static final String PDL_ATTRIBUTE = "pdl";
@Override
public boolean matchesCriteria(NsdServiceInfo nsdServiceInfo) {
if (!MDNSUtils.isSupportedServiceType(nsdServiceInfo, MOPRIA_MDNS_SERVICES)) {
return false;
}
String pdls = MDNSUtils.getString(nsdServiceInfo.getAttributes().get(PDL_ATTRIBUTE));
boolean isMatch = !TextUtils.isEmpty(pdls)
&& (pdls.contains(PDL__PDF)
|| pdls.contains(PDL__PCLM)
|| pdls.contains(PDL__PWG_RASTER));
if (isMatch) {
Log.d(TAG, "Mopria printer found: " + nsdServiceInfo.getServiceName());
}
return isMatch;
}
}

View File

@@ -0,0 +1,117 @@
/*
* (c) Copyright 2016 Samsung Electronics
* (c) Copyright 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.plugin.samsung;
import android.net.nsd.NsdServiceInfo;
import android.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import com.android.printservice.recommendation.util.MDNSFilteredDiscovery;
import com.android.printservice.recommendation.util.MDNSUtils;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
/**
* Printer filter for Samsung printer models supported by the print service plugin
*/
class PrinterFilterSamsung implements MDNSFilteredDiscovery.PrinterFilter {
private static final String TAG = "PrinterFilterSamsung";
static final Set<String> SAMSUNG_MDNS_SERVICES = new HashSet<String>() {{
add("_pdl-datastream._tcp");
}};
private static final String[] NOT_SUPPORTED_MODELS = new String[]{
"SCX-5x15",
"SF-555P",
"CF-555P",
"SCX-4x16",
"SCX-4214F",
"CLP-500",
"CJX-",
"MJC-"
};
private static final String ATTR_USB_MFG = "usb_MFG";
private static final String ATTR_MFG = "mfg";
private static final String ATTR_USB_MDL = "usb_MDL";
private static final String ATTR_MDL = "mdl";
private static final String ATTR_PRODUCT = "product";
private static final String ATTR_TY = "ty";
private static Set<String> SAMUNG_VENDOR_SET = new HashSet<String>() {{
add("samsung");
}};
@Override
public boolean matchesCriteria(NsdServiceInfo nsdServiceInfo) {
if (!MDNSUtils.isSupportedServiceType(nsdServiceInfo, SAMSUNG_MDNS_SERVICES)) {
return false;
}
if (!MDNSUtils.isVendorPrinter(nsdServiceInfo, SAMUNG_VENDOR_SET)) {
return false;
}
String modelName = getSamsungModelName(nsdServiceInfo);
if (modelName != null && isSupportedSamsungModel(modelName)) {
Log.d(TAG, "Samsung printer found: " + nsdServiceInfo.getServiceName());
return true;
}
return false;
}
private boolean isSupportedSamsungModel(String model) {
if (!TextUtils.isEmpty(model)) {
String modelToUpper = model.toUpperCase(Locale.US);
for (String unSupportedPrinter : NOT_SUPPORTED_MODELS) {
if (modelToUpper.contains(unSupportedPrinter)) {
return false;
}
}
}
return true;
}
private String getSamsungModelName(@NonNull NsdServiceInfo resolvedDevice) {
Map<String,byte[]> attributes = resolvedDevice.getAttributes();
String usb_mfg = MDNSUtils.getString(attributes.get(ATTR_USB_MFG));
if (TextUtils.isEmpty(usb_mfg)) {
usb_mfg = MDNSUtils.getString(attributes.get(ATTR_MFG));
}
String usb_mdl = MDNSUtils.getString(attributes.get(ATTR_USB_MDL));
if (TextUtils.isEmpty(usb_mdl)) {
usb_mdl = MDNSUtils.getString(attributes.get(ATTR_MDL));
}
String modelName;
if (!TextUtils.isEmpty(usb_mfg) && !TextUtils.isEmpty(usb_mdl)) {
modelName = usb_mfg.trim() + " " + usb_mdl.trim();
} else {
modelName = MDNSUtils.getString(attributes.get(ATTR_PRODUCT));
if (TextUtils.isEmpty(modelName)) {
modelName = MDNSUtils.getString(attributes.get(ATTR_TY));
}
}
return modelName;
}
}

View File

@@ -1,33 +0,0 @@
/*
* 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.plugin.samsung;
import android.net.nsd.NsdServiceInfo;
import java.util.HashMap;
final class PrinterHashMap extends HashMap<String, NsdServiceInfo> {
public static String getKey(NsdServiceInfo serviceInfo) {
return serviceInfo.getServiceName();
}
public NsdServiceInfo addPrinter(NsdServiceInfo device) {
return put(getKey(device), device);
}
public NsdServiceInfo removePrinter(NsdServiceInfo device) {
return remove(getKey(device));
}
}

View File

@@ -1,102 +1,69 @@
/* /*
(c) Copyright 2016 Samsung Electronics.. * (c) Copyright 2016 Samsung Electronics
* (c) Copyright 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. * Licensed under the Apache License, Version 2.0 (the "License");
You may obtain a copy of the License at * 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 *
* 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, * Unless required by applicable law or agreed to in writing, software
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * distributed under the License is distributed on an "AS IS" BASIS,
See the License for the specific language governing permissions and * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
limitations under the License. * See the License for the specific language governing permissions and
*/ * limitations under the License.
*/
package com.android.printservice.recommendation.plugin.samsung;
package com.android.printservice.recommendation.plugin.samsung;
import android.content.Context;
import android.content.Context; import android.net.nsd.NsdServiceInfo;
import android.net.nsd.NsdServiceInfo; import android.annotation.NonNull;
import android.text.TextUtils;
import com.android.printservice.recommendation.PrintServicePlugin;
import java.util.Locale; import com.android.printservice.recommendation.R;
import java.util.Map; import com.android.printservice.recommendation.util.MDNSFilteredDiscovery;
import com.android.printservice.recommendation.R; import java.util.HashSet;
import java.util.Set;
public class SamsungRecommendationPlugin extends ServiceRecommendationPlugin {
public class SamsungRecommendationPlugin implements PrintServicePlugin {
private static final String TAG = "SamsungRecommendation"; private static final Set<String> ALL_MDNS_SERVICES = new HashSet<String>() {{
addAll(PrinterFilterMopria.MOPRIA_MDNS_SERVICES);
private static final String ATTR_USB_MFG = "usb_MFG"; addAll(PrinterFilterSamsung.SAMSUNG_MDNS_SERVICES);
private static final String ATTR_MFG = "mfg"; }};
private static final String ATTR_USB_MDL = "usb_MDL";
private static final String ATTR_MDL = "mdl"; private final @NonNull Context mContext;
private static final String ATTR_PRODUCT = "product"; private final @NonNull MDNSFilteredDiscovery mMDNSFilteredDiscovery;
private static final String ATTR_TY = "ty";
private final @NonNull PrinterFilterSamsung mPrinterFilterSamsung = new PrinterFilterSamsung();
private static String[] mNotSupportedDevices = new String[]{ private final @NonNull PrinterFilterMopria mPrinterFilterMopria = new PrinterFilterMopria();
"SCX-5x15",
"SF-555P", public SamsungRecommendationPlugin(@NonNull Context context) {
"CF-555P", mContext = context;
"SCX-4x16", mMDNSFilteredDiscovery = new MDNSFilteredDiscovery(context, ALL_MDNS_SERVICES,
"SCX-4214F", (NsdServiceInfo nsdServiceInfo) ->
"CLP-500", mPrinterFilterSamsung.matchesCriteria(nsdServiceInfo) ||
"CJX-", mPrinterFilterMopria.matchesCriteria(nsdServiceInfo));
"MJC-" }
};
@Override
private static boolean isSupportedModel(String model) { public int getName() {
if (!TextUtils.isEmpty(model)) { return R.string.plugin_vendor_samsung;
String modelToUpper = model.toUpperCase(Locale.US); }
for (String unSupportedPrinter : mNotSupportedDevices) {
if (modelToUpper.contains(unSupportedPrinter)) { @Override
return false; public @NonNull CharSequence getPackageName() {
} return mContext.getString(R.string.plugin_package_samsung);
} }
}
return true; @Override
} public void start(@NonNull PrinterDiscoveryCallback callback) throws Exception {
mMDNSFilteredDiscovery.start(callback);
public SamsungRecommendationPlugin(Context context) { }
super(context, R.string.plugin_vendor_samsung, new VendorInfo(context.getResources(), R.array.known_print_vendor_info_for_samsung), new String[]{"_pdl-datastream._tcp"});
} @Override
public void stop() throws Exception {
@Override mMDNSFilteredDiscovery.stop();
public boolean matchesCriteria(String vendor, NsdServiceInfo nsdServiceInfo) { }
if (!TextUtils.equals(vendor, mVendorInfo.mVendorID)) return false; }
String modelName = getModelName(nsdServiceInfo);
if (modelName != null) {
return (isSupportedModel(modelName));
}
return false;
}
private String getModelName(NsdServiceInfo resolvedDevice) {
Map<String,byte[]> attributes = resolvedDevice.getAttributes();
String usb_mfg = MDnsUtils.getString(attributes.get(ATTR_USB_MFG));
if (TextUtils.isEmpty(usb_mfg)) {
usb_mfg = MDnsUtils.getString(attributes.get(ATTR_MFG));
}
String usb_mdl = MDnsUtils.getString(attributes.get(ATTR_USB_MDL));
if (TextUtils.isEmpty(usb_mdl)) {
usb_mdl = MDnsUtils.getString(attributes.get(ATTR_MDL));
}
String modelName = null;
if (!TextUtils.isEmpty(usb_mfg) && !TextUtils.isEmpty(usb_mdl)) {
modelName = usb_mfg.trim() + " " + usb_mdl.trim();
} else {
modelName = MDnsUtils.getString(attributes.get(ATTR_PRODUCT));
if (TextUtils.isEmpty(modelName)) {
modelName = MDnsUtils.getString(attributes.get(ATTR_TY));
}
}
return modelName;
}
}

View File

@@ -1,186 +0,0 @@
/*
* 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.plugin.samsung;
import android.content.Context;
import android.content.res.TypedArray;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.text.TextUtils;
import android.util.Pair;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.android.printservice.recommendation.R;
import com.android.printservice.recommendation.util.DiscoveryListenerMultiplexer;
public class ServiceListener implements ServiceResolveQueue.ResolveCallback {
private final NsdManager mNSDManager;
private final Map<String, VendorInfo> mVendorInfoHashMap;
private final String[] mServiceType;
private final Observer mObserver;
private final ServiceResolveQueue mResolveQueue;
private List<NsdManager.DiscoveryListener> mListeners = new ArrayList<>();
public HashMap<String, PrinterHashMap> mVendorHashMap = new HashMap<>();
public interface Observer {
boolean matchesCriteria(String vendor, NsdServiceInfo serviceInfo);
void dataSetChanged();
}
public ServiceListener(Context context, Observer observer, String[] serviceTypes) {
mObserver = observer;
mServiceType = serviceTypes;
mNSDManager = (NsdManager)context.getSystemService(Context.NSD_SERVICE);
mResolveQueue = ServiceResolveQueue.getInstance(mNSDManager);
Map<String, VendorInfo> vendorInfoMap = new HashMap<>();
TypedArray testArray = context.getResources().obtainTypedArray(R.array.known_print_plugin_vendors);
for(int i = 0; i < testArray.length(); i++) {
int arrayID = testArray.getResourceId(i, 0);
if (arrayID != 0) {
VendorInfo info = new VendorInfo(context.getResources(), arrayID);
vendorInfoMap.put(info.mVendorID, info);
vendorInfoMap.put(info.mPackageName, info);
}
}
testArray.recycle();
mVendorInfoHashMap = vendorInfoMap;
}
@Override
public void serviceResolved(NsdServiceInfo nsdServiceInfo) {
printerFound(nsdServiceInfo);
}
private synchronized void printerFound(NsdServiceInfo nsdServiceInfo) {
if (nsdServiceInfo == null) return;
if (TextUtils.isEmpty(PrinterHashMap.getKey(nsdServiceInfo))) return;
String vendor = MDnsUtils.getVendor(nsdServiceInfo);
if (vendor == null) vendor = "";
for(Map.Entry<String,VendorInfo> entry : mVendorInfoHashMap.entrySet()) {
for(String vendorValues : entry.getValue().mDNSValues) {
if (vendor.equalsIgnoreCase(vendorValues)) {
vendor = entry.getValue().mVendorID;
break;
}
}
// intentional pointer check
//noinspection StringEquality
if ((vendor != entry.getValue().mVendorID) &&
MDnsUtils.isVendorPrinter(nsdServiceInfo, entry.getValue().mDNSValues)) {
vendor = entry.getValue().mVendorID;
}
// intentional pointer check
//noinspection StringEquality
if (vendor == entry.getValue().mVendorID) break;
}
if (TextUtils.isEmpty(vendor)) {
return;
}
if (!mObserver.matchesCriteria(vendor, nsdServiceInfo))
return;
boolean mapsChanged;
PrinterHashMap vendorHash = mVendorHashMap.get(vendor);
if (vendorHash == null) {
vendorHash = new PrinterHashMap();
}
mapsChanged = (vendorHash.addPrinter(nsdServiceInfo) == null);
mVendorHashMap.put(vendor, vendorHash);
if (mapsChanged) {
mObserver.dataSetChanged();
}
}
private synchronized void printerRemoved(NsdServiceInfo nsdServiceInfo) {
boolean wasRemoved = false;
Set<String> vendors = mVendorHashMap.keySet();
for(String vendor : vendors) {
PrinterHashMap map = mVendorHashMap.get(vendor);
wasRemoved |= (map.removePrinter(nsdServiceInfo) != null);
if (map.isEmpty()) wasRemoved |= (mVendorHashMap.remove(vendor) != null);
}
if (wasRemoved) {
mObserver.dataSetChanged();
}
}
public void start() {
stop();
for(final String service :mServiceType) {
NsdManager.DiscoveryListener listener = new NsdManager.DiscoveryListener() {
@Override
public void onStartDiscoveryFailed(String s, int i) {
}
@Override
public void onStopDiscoveryFailed(String s, int i) {
}
@Override
public void onDiscoveryStarted(String s) {
}
@Override
public void onDiscoveryStopped(String s) {
}
@Override
public void onServiceFound(NsdServiceInfo nsdServiceInfo) {
mResolveQueue.queueRequest(nsdServiceInfo, ServiceListener.this);
}
@Override
public void onServiceLost(NsdServiceInfo nsdServiceInfo) {
mResolveQueue.removeRequest(nsdServiceInfo, ServiceListener.this);
printerRemoved(nsdServiceInfo);
}
};
DiscoveryListenerMultiplexer.addListener(mNSDManager, service, listener);
mListeners.add(listener);
}
}
public void stop() {
for(NsdManager.DiscoveryListener listener : mListeners) {
DiscoveryListenerMultiplexer.removeListener(mNSDManager, listener);
}
mVendorHashMap.clear();
mListeners.clear();
}
public Pair<Integer, Integer> getCount() {
int count = 0;
for (PrinterHashMap map : mVendorHashMap.values()) {
count += map.size();
}
return Pair.create(mVendorHashMap.size(), count);
}
}

View File

@@ -1,86 +0,0 @@
/*
* 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.plugin.samsung;
import android.content.Context;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.annotation.NonNull;
import android.text.TextUtils;
import com.android.printservice.recommendation.PrintServicePlugin;
public abstract class ServiceRecommendationPlugin implements PrintServicePlugin, ServiceListener.Observer {
protected static final String PDL_ATTRIBUTE = "pdl";
protected final Object mLock = new Object();
protected PrinterDiscoveryCallback mCallback = null;
protected final ServiceListener mListener;
protected final NsdManager mNSDManager;
protected final VendorInfo mVendorInfo;
private final int mVendorStringID;
protected ServiceRecommendationPlugin(Context context, int vendorStringID, VendorInfo vendorInfo, String[] services) {
mNSDManager = (NsdManager)context.getSystemService(Context.NSD_SERVICE);
mVendorStringID = vendorStringID;
mVendorInfo = vendorInfo;
mListener = new ServiceListener(context, this, services);
}
@Override
public int getName() {
return mVendorStringID;
}
@NonNull
@Override
public CharSequence getPackageName() {
return mVendorInfo.mPackageName;
}
@Override
public void start(@NonNull PrinterDiscoveryCallback callback) throws Exception {
synchronized (mLock) {
mCallback = callback;
}
mListener.start();
}
@Override
public void stop() throws Exception {
synchronized (mLock) {
mCallback = null;
}
mListener.stop();
}
@Override
public void dataSetChanged() {
synchronized (mLock) {
if (mCallback != null) mCallback.onChanged(getCount());
}
}
@Override
public boolean matchesCriteria(String vendor, NsdServiceInfo nsdServiceInfo) {
return TextUtils.equals(vendor, mVendorInfo.mVendorID);
}
public int getCount() {
return mListener.getCount().second;
}
}

View File

@@ -1,109 +0,0 @@
/*
* 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.plugin.samsung;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.util.Pair;
import com.android.printservice.recommendation.util.NsdResolveQueue;
import java.util.LinkedList;
final class ServiceResolveQueue {
private final NsdManager mNsdManager;
private final LinkedList<Pair<NsdServiceInfo, ResolveCallback>> mQueue = new LinkedList<>();
private final Object mLock = new Object();
private static Object sLock = new Object();
private static ServiceResolveQueue sInstance = null;
private final NsdResolveQueue mNsdResolveQueue;
private Pair<NsdServiceInfo, ResolveCallback> mCurrentRequest = null;
public static void createInstance(NsdManager nsdManager) {
if (sInstance == null) sInstance = new ServiceResolveQueue(nsdManager);
}
public static ServiceResolveQueue getInstance(NsdManager nsdManager) {
synchronized (sLock) {
createInstance(nsdManager);
return sInstance;
}
}
public static void destroyInstance() {
sInstance = null;
}
public interface ResolveCallback {
void serviceResolved(NsdServiceInfo nsdServiceInfo);
}
public ServiceResolveQueue(NsdManager nsdManager) {
mNsdManager = nsdManager;
mNsdResolveQueue = NsdResolveQueue.getInstance();
}
public void queueRequest(NsdServiceInfo serviceInfo, ResolveCallback callback) {
synchronized (mLock) {
Pair<NsdServiceInfo, ResolveCallback> newRequest = Pair.create(serviceInfo, callback);
if (mQueue.contains(newRequest)) return;
mQueue.add(newRequest);
makeNextRequest();
}
}
public void removeRequest(NsdServiceInfo serviceInfo, ResolveCallback callback) {
synchronized (mLock) {
Pair<NsdServiceInfo, ResolveCallback> newRequest = Pair.create(serviceInfo, callback);
mQueue.remove(newRequest);
if ((mCurrentRequest != null) && newRequest.equals(mCurrentRequest)) mCurrentRequest = null;
}
}
private void makeNextRequest() {
synchronized (mLock) {
if (mCurrentRequest != null) return;
if (mQueue.isEmpty()) return;
mCurrentRequest = mQueue.removeFirst();
mNsdResolveQueue.resolve(mNsdManager, mCurrentRequest.first,
new NsdManager.ResolveListener() {
@Override
public void onResolveFailed(NsdServiceInfo nsdServiceInfo, int i) {
synchronized (mLock) {
if (mCurrentRequest != null) mQueue.add(mCurrentRequest);
makeNextRequest();
}
}
@Override
public void onServiceResolved(NsdServiceInfo nsdServiceInfo) {
synchronized (mLock) {
if (mCurrentRequest != null) {
mCurrentRequest.second.serviceResolved(nsdServiceInfo);
mCurrentRequest = null;
}
makeNextRequest();
}
}
});
}
}
}

View File

@@ -1,40 +0,0 @@
/*
* 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.plugin.samsung;
import android.content.res.Resources;
import java.util.Arrays;
public final class VendorInfo {
public final String mPackageName;
public final String mVendorID;
public final String[] mDNSValues;
public final int mID;
public VendorInfo(Resources resources, int vendor_info_id) {
mID = vendor_info_id;
String[] data = resources.getStringArray(vendor_info_id);
if ((data == null) || (data.length < 2)) {
data = new String[] { null, null };
}
mPackageName = data[0];
mVendorID = data[1];
mDNSValues = (data.length > 2) ? Arrays.copyOfRange(data, 2, data.length) : new String[]{};
}
}

View File

@@ -0,0 +1,212 @@
/*
* 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());
}
}
}
}
});
}
}

View File

@@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.printservice.recommendation.plugin.mdnsFilter; package com.android.printservice.recommendation.util;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.net.nsd.NsdServiceInfo; import android.net.nsd.NsdServiceInfo;
@@ -27,12 +27,15 @@ import java.util.Set;
/** /**
* Utils for dealing with mDNS attributes * Utils for dealing with mDNS attributes
*/ */
class MDNSUtils { public class MDNSUtils {
public static final String ATTRIBUTE_TY = "ty"; public static final String ATTRIBUTE_TY = "ty";
public static final String ATTRIBUTE_PRODUCT = "product"; public static final String ATTRIBUTE_PRODUCT = "product";
public static final String ATTRIBUTE_USB_MFG = "usb_mfg"; public static final String ATTRIBUTE_USB_MFG = "usb_mfg";
public static final String ATTRIBUTE_MFG = "mfg"; public static final String ATTRIBUTE_MFG = "mfg";
private MDNSUtils() {
}
/** /**
* Check if the service has any of a set of vendor names. * Check if the service has any of a set of vendor names.
* *
@@ -95,4 +98,35 @@ class MDNSUtils {
private static boolean containsString(@NonNull String container, @NonNull String contained) { private static boolean containsString(@NonNull String container, @NonNull String contained) {
return container.equalsIgnoreCase(contained) || container.contains(contained + " "); return container.equalsIgnoreCase(contained) || container.contains(contained + " ");
} }
/**
* Return String from mDNS attribute byte array
*
* @param value the byte array with string data
*
* @return constructed string
*/
public static String getString(byte[] value) {
if (value != null) return new String(value, StandardCharsets.UTF_8);
return null;
}
/**
* Check if service has a type of supported types set
*
* @param serviceInfo The service
* @param serviceTypes The supported service types set
*
* @return true if service has a type of supported types set
*/
public static boolean isSupportedServiceType(@NonNull NsdServiceInfo serviceInfo,
@NonNull Set<String> serviceTypes) {
String curType = serviceInfo.getServiceType().toLowerCase();
for (String type : serviceTypes) {
if (curType.contains(type.toLowerCase())) {
return true;
}
}
return false;
}
} }