Add Samsung print recommendation plugin.

The code follows the same pattern as the HP and Mopria plugins. As this code is contributed by a 3rd I'd rather submit this as is and let the original submitter clean it up once it appears in AOSP.

The code should not affect anything outside of the package it is in. In the package the code is strongly isolated so that one plugin cannot cause issues with another plugin.

The code was minimally tested on a Samsung SCX-3406FW .

Bug: 28908572
Change-Id: I33c2673d06711902dbd0f1d82563cf5d3fbfe86a
(cherry picked from commit 83defde085)
This commit is contained in:
Philip P. Moltmann
2016-05-23 10:33:57 -07:00
parent 5ee420c0d5
commit a26b775dc1
11 changed files with 647 additions and 9 deletions

View File

@@ -32,8 +32,15 @@
<item>Hewlett Packard</item>
</string-array>
<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>
<array name="known_print_plugin_vendors" translatable="false">
<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_samsung</item>
</array>
</resources>

View File

@@ -23,7 +23,7 @@
<string name="plugin_vendor_brother">Brother</string>
<string name="plugin_vendor_canon">Canon</string>
<string name="plugin_vendor_xerox">Xerox</string>
<string name="plugin_vendor_samsung">Samsung Electronics</string>
<string name="plugin_vendor_samsung">Samsung</string>
<string name="plugin_vendor_epson">Epson</string>
<string name="plugin_vendor_konica_minolta">Konica Minolta</string>
<string name="plugin_vendor_fuji">Fuji</string>

View File

@@ -50,14 +50,6 @@
</mdns-names>
</vendor>
<vendor>
<name>@string/plugin_vendor_samsung</name>
<package>com.sec.app.samsungprintservice</package>
<mdns-names>
<mdns-name>Samsung</mdns-name>
</mdns-names>
</vendor>
<vendor>
<name>@string/plugin_vendor_epson</name>
<package>com.epson.mobilephone.android.epsonprintserviceplugin</package>

View File

@@ -25,6 +25,7 @@ import com.android.printservice.recommendation.plugin.hp.HPRecommendationPlugin;
import com.android.printservice.recommendation.plugin.mdnsFilter.MDNSFilterPlugin;
import com.android.printservice.recommendation.plugin.mdnsFilter.VendorConfig;
import com.android.printservice.recommendation.plugin.mopria.MopriaRecommendationPlugin;
import com.android.printservice.recommendation.plugin.samsung.SamsungRecommendationPlugin;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
@@ -74,6 +75,14 @@ public class RecommendationServiceImpl extends RecommendationService
" plugin", e);
}
try {
mPlugins.add(new RemotePrintServicePlugin(new SamsungRecommendationPlugin(this), this,
false));
} catch (Exception e) {
Log.e(LOG_TAG, "Could not initiate " + getString(R.string.plugin_vendor_samsung) +
" plugin", e);
}
final int numPlugins = mPlugins.size();
for (int i = 0; i < numPlugins; i++) {
try {

View File

@@ -0,0 +1,74 @@
/*
* 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,33 @@
/*
* 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

@@ -0,0 +1,102 @@
/*
(c) Copyright 2016 Samsung Electronics..
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.NsdServiceInfo;
import android.text.TextUtils;
import java.util.Locale;
import java.util.Map;
import com.android.printservice.recommendation.R;
public class SamsungRecommendationPlugin extends ServiceRecommendationPlugin {
private static final String TAG = "SamsungRecommendation";
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 String[] mNotSupportedDevices = new String[]{
"SCX-5x15",
"SF-555P",
"CF-555P",
"SCX-4x16",
"SCX-4214F",
"CLP-500",
"CJX-",
"MJC-"
};
private static boolean isSupportedModel(String model) {
if (!TextUtils.isEmpty(model)) {
String modelToUpper = model.toUpperCase(Locale.US);
for (String unSupportedPrinter : mNotSupportedDevices) {
if (modelToUpper.contains(unSupportedPrinter)) {
return false;
}
}
}
return true;
}
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 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

@@ -0,0 +1,186 @@
/*
* 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

@@ -0,0 +1,86 @@
/*
* 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

@@ -0,0 +1,109 @@
/*
* 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

@@ -0,0 +1,40 @@
/*
* 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[]{};
}
}