diff options
author | Philip P. Moltmann <moltmann@google.com> | 2016-05-04 22:05:35 +0000 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2016-05-04 22:05:35 +0000 |
commit | 3acc50c8578807d7e7bcaad2854436ba2a591006 (patch) | |
tree | 1035be2bc32b9932b4183e8ff7bb2602e53912f8 /packages/PrintRecommendationService | |
parent | 9b48518e5307db6201349a453757daf36d6c4916 (diff) | |
parent | 747b3d7de89fa6b7330d59a47fd982d38192e3c3 (diff) |
Merge "Merge "Add HP and Mopria print recommendation service" into nyc-dev am: 9e5d4e078a am: e87fdd9c16" into nyc-mr1-dev-plus-aosp
am: 747b3d7de8
* commit '747b3d7de89fa6b7330d59a47fd982d38192e3c3':
Add HP and Mopria print recommendation service
Change-Id: Idba97dabb1dcdefc528541d9c2955705d2e787eb
Diffstat (limited to 'packages/PrintRecommendationService')
15 files changed, 905 insertions, 17 deletions
diff --git a/packages/PrintRecommendationService/res/values/donottranslate.xml b/packages/PrintRecommendationService/res/values/donottranslate.xml index 4cf0eaf4181b..68effbfbdf35 100644 --- a/packages/PrintRecommendationService/res/values/donottranslate.xml +++ b/packages/PrintRecommendationService/res/values/donottranslate.xml @@ -15,4 +15,25 @@ --> <resources> <string name="app_label">Print Service Recommendation Service</string> + + <!-- HP / Mopria plugin --> + <string-array name="known_print_vendor_info_for_mopria" translatable="false"> + <item>org.mopria.printplugin</item> + <item>WFDS</item> + <!-- no specific mDNS values to list --> + </string-array> + + <string-array name="known_print_vendor_info_for_hp" translatable="false"> + <item>com.hp.android.printservice</item> + <item>HP</item> + <!-- HP has used these values in mDNS records over the years --> + <item>HP</item> + <item>Hewlett-Packard</item> + <item>Hewlett Packard</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> + </array> </resources> diff --git a/packages/PrintRecommendationService/res/values/strings.xml b/packages/PrintRecommendationService/res/values/strings.xml index 07d0004fa09b..2d127e2994d3 100644 --- a/packages/PrintRecommendationService/res/values/strings.xml +++ b/packages/PrintRecommendationService/res/values/strings.xml @@ -27,4 +27,5 @@ <string name="plugin_vendor_epson">Epson</string> <string name="plugin_vendor_konika_minolta">Konika Minolta</string> <string name="plugin_vendor_fuji">Fuji</string> + <string name="plugin_vendor_morpia">Mopria</string> </resources> diff --git a/packages/PrintRecommendationService/res/xml/vendorconfigs.xml b/packages/PrintRecommendationService/res/xml/vendorconfigs.xml index 119943cd5f9f..98d466c038d3 100644 --- a/packages/PrintRecommendationService/res/xml/vendorconfigs.xml +++ b/packages/PrintRecommendationService/res/xml/vendorconfigs.xml @@ -18,16 +18,6 @@ <vendors> <vendor> - <name>@string/plugin_vendor_hp</name> - <package>com.hp.android.printservice</package> - <mdns-names> - <mdns-name>HP</mdns-name> - <mdns-name>Hewlett-Packard</mdns-name> - <mdns-name>Hewlett Packard</mdns-name> - </mdns-names> - </vendor> - - <vendor> <name>@string/plugin_vendor_lexmark</name> <package>com.lexmark.print.plugin</package> <mdns-names> diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/RecommendationServiceImpl.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/RecommendationServiceImpl.java index 493cb31f1f5e..4b7c6e0df8c4 100644 --- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/RecommendationServiceImpl.java +++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/RecommendationServiceImpl.java @@ -21,8 +21,10 @@ import android.printservice.recommendation.RecommendationInfo; import android.printservice.recommendation.RecommendationService; import android.printservice.PrintService; import android.util.Log; +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 org.xmlpull.v1.XmlPullParserException; import java.io.IOException; @@ -56,6 +58,22 @@ public class RecommendationServiceImpl extends RecommendationService throw new RuntimeException("Could not parse vendorconfig", e); } + try { + mPlugins.add(new RemotePrintServicePlugin(new HPRecommendationPlugin(this), this, + false)); + } catch (Exception e) { + Log.e(LOG_TAG, "Could not initiate " + getString(R.string.plugin_vendor_hp) + " plugin", + e); + } + + try { + mPlugins.add(new RemotePrintServicePlugin(new MopriaRecommendationPlugin(this), this, + true)); + } catch (Exception e) { + Log.e(LOG_TAG, "Could not initiate " + getString(R.string.plugin_vendor_morpia) + + " plugin", e); + } + final int numPlugins = mPlugins.size(); for (int i = 0; i < numPlugins; i++) { try { diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/HPRecommendationPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/HPRecommendationPlugin.java new file mode 100644 index 000000000000..54d257263172 --- /dev/null +++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/HPRecommendationPlugin.java @@ -0,0 +1,100 @@ +/* +(c) Copyright 2016 HP Inc. +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.hp; + +import android.content.Context; +import android.net.nsd.NsdServiceInfo; +import android.text.TextUtils; + +import com.android.printservice.recommendation.R; + +import java.util.Locale; + +public class HPRecommendationPlugin extends ServiceRecommendationPlugin { + + private static final String PDL__PCL = "application/vnd.hp-PCL"; + private static final String PDL__PCLM = "application/PCLm"; + private static final String PDL__PDF = "application/pdf"; + private static final String PDL__PWG_RASTER = "image/pwg-raster"; + + private static final String TAG_DESIGNJET = "DESIGNJET"; + private static final String TAG_PAGEWIDE = "PAGEWIDE"; + private static final String TAG_LATEX = "LATEX"; + private static final String TAG_SCITEX = "SCITEX"; + private static final String TAG_XL = "XL"; + private static final String ATTRIBUTE_VALUE__TRUE = "T"; + private static final String MDNS_ATTRIBUTE__HPLFMOBILEPRINTER = "hplfpmobileprinter"; + private static final String MDNS_ATTRIBUTE__TY = "ty"; + + + private static String[] mSupportedDesignJet = new String[]{ + "HP DESIGNJET T120", + "HP DESIGNJET T520", + "HP DESIGNJET T930", + "HP DESIGNJET T1530", + "HP DESIGNJET T2530", + "HP DESIGNJET T730", + "HP DESIGNJET T830", + }; + + private boolean isPrintSupported(String printerModel) { + boolean isSupported; + if (!TextUtils.isEmpty(printerModel)) { + String modelToUpper = printerModel.toUpperCase(Locale.US); + if (modelToUpper.contains(TAG_DESIGNJET)) { + isSupported = isSupportedDesignjet(printerModel); + } else + isSupported = !(modelToUpper.contains(TAG_LATEX) || modelToUpper.contains(TAG_SCITEX)) && !(modelToUpper.contains(TAG_PAGEWIDE) && modelToUpper.contains(TAG_XL)); + } else { + isSupported = false; + } + + return isSupported; + } + + private static boolean isSupportedDesignjet(String printerModel) { + boolean isSupported = false; + if (!TextUtils.isEmpty(printerModel)) { + String modelToUpper = printerModel.toUpperCase(Locale.US); + for (String supportedPrinter : mSupportedDesignJet) { + if (modelToUpper.contains(supportedPrinter)) { + isSupported = true; + } + } + } + return isSupported; + } + + public HPRecommendationPlugin(Context context) { + super(context, R.string.plugin_vendor_hp, new VendorInfo(context.getResources(), R.array.known_print_vendor_info_for_hp), new String[]{"_pdl-datastream._tcp","_ipp._tcp", "_ipps._tcp"}); + } + + @Override + public boolean matchesCriteria(String vendor, NsdServiceInfo nsdServiceInfo) { + if (!TextUtils.equals(vendor, mVendorInfo.mVendorID)) return false; + + String pdls = MDnsUtils.getString(nsdServiceInfo.getAttributes().get(PDL_ATTRIBUTE)); + boolean hasMobileSupport = TextUtils.equals(ATTRIBUTE_VALUE__TRUE, MDnsUtils.getString(nsdServiceInfo.getAttributes().get(MDNS_ATTRIBUTE__HPLFMOBILEPRINTER))); + + return (((hasMobileSupport || isPrintSupported(MDnsUtils.getString(nsdServiceInfo.getAttributes().get(MDNS_ATTRIBUTE__TY)))) + &&!TextUtils.isEmpty(pdls)) + && (pdls.contains(PDL__PCL) + || pdls.contains(PDL__PDF) + || pdls.contains(PDL__PCLM) + || pdls.contains(PDL__PWG_RASTER))); + } +} diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/MDnsUtils.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/MDnsUtils.java new file mode 100644 index 000000000000..8c97064beac8 --- /dev/null +++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/MDnsUtils.java @@ -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.hp; + +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 + " ")); + } +} diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/PrinterHashMap.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/PrinterHashMap.java new file mode 100644 index 000000000000..61956f694245 --- /dev/null +++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/PrinterHashMap.java @@ -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.hp; + +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)); + } +} diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/ServiceListener.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/ServiceListener.java new file mode 100644 index 000000000000..e34247a66fb6 --- /dev/null +++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/ServiceListener.java @@ -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.hp; + +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); + } +} diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/ServiceRecommendationPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/ServiceRecommendationPlugin.java new file mode 100644 index 000000000000..7ea530dda9d8 --- /dev/null +++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/ServiceRecommendationPlugin.java @@ -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.hp; + +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; + } +} diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/ServiceResolveQueue.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/ServiceResolveQueue.java new file mode 100644 index 000000000000..fc9e7adf1f64 --- /dev/null +++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/ServiceResolveQueue.java @@ -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.hp; + +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(); + } + } + }); + + } + } + + +} diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/VendorInfo.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/VendorInfo.java new file mode 100644 index 000000000000..0ff59a2d6dc4 --- /dev/null +++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/hp/VendorInfo.java @@ -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.hp; + +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[]{}; + } +} diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java index 26300b1e37b9..a2c0485f3d0a 100644 --- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java +++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java @@ -26,7 +26,7 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import com.android.printservice.recommendation.PrintServicePlugin; -import com.android.printservice.recommendation.util.MDNSUtils; +import com.android.printservice.recommendation.util.DiscoveryListenerMultiplexer; import com.android.printservice.recommendation.util.NsdResolveQueue; import java.util.HashSet; @@ -81,7 +81,7 @@ public class MDNSFilterPlugin implements PrintServicePlugin, NsdManager.Discover @NonNull CharSequence packageName, @NonNull List<String> mDNSNames) { mContext = Preconditions.checkNotNull(context, "context"); mName = mContext.getResources().getIdentifier(Preconditions.checkStringNotEmpty(name, - "name"), null, mContext.getPackageName()); + "name"), null, "com.android.printservice.recommendation"); mPackageName = Preconditions.checkStringNotEmpty(packageName); mMDNSNames = new HashSet<>(Preconditions .checkCollectionNotEmpty(Preconditions.checkCollectionElementsNotNull(mDNSNames, @@ -107,8 +107,7 @@ public class MDNSFilterPlugin implements PrintServicePlugin, NsdManager.Discover public void start(@NonNull PrinterDiscoveryCallback callback) throws Exception { mCallback = callback; - getNDSManager().discoverServices(PRINTER_SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, - this); + DiscoveryListenerMultiplexer.addListener(getNDSManager(), PRINTER_SERVICE_TYPE, this); } @Override @@ -121,7 +120,7 @@ public class MDNSFilterPlugin implements PrintServicePlugin, NsdManager.Discover mCallback.onChanged(0); mCallback = null; - getNDSManager().stopServiceDiscovery(this); + DiscoveryListenerMultiplexer.removeListener(getNDSManager(), this); } @Override diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/util/MDNSUtils.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSUtils.java index 0541c3565dba..4c27a47e144f 100644 --- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/util/MDNSUtils.java +++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSUtils.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package com.android.printservice.recommendation.util; +package com.android.printservice.recommendation.plugin.mdnsFilter; import android.annotation.NonNull; import android.net.nsd.NsdServiceInfo; @@ -27,7 +27,7 @@ import java.util.Set; /** * Utils for dealing with mDNS attributes */ -public class MDNSUtils { +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"; diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mopria/MopriaRecommendationPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mopria/MopriaRecommendationPlugin.java new file mode 100644 index 000000000000..18c9da51865a --- /dev/null +++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mopria/MopriaRecommendationPlugin.java @@ -0,0 +1,54 @@ +/* + * (c) Copyright 2016 Mopria Alliance, Inc. + * 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.mopria; + +import android.content.Context; +import android.net.nsd.NsdServiceInfo; +import android.text.TextUtils; +import android.util.Pair; + +import com.android.printservice.recommendation.plugin.hp.MDnsUtils; +import com.android.printservice.recommendation.plugin.hp.ServiceRecommendationPlugin; +import com.android.printservice.recommendation.plugin.hp.VendorInfo; +import com.android.printservice.recommendation.R; + +public class MopriaRecommendationPlugin extends ServiceRecommendationPlugin { + + 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"; + + public MopriaRecommendationPlugin(Context context) { + super(context, R.string.plugin_vendor_morpia, new VendorInfo(context.getResources(), R.array.known_print_vendor_info_for_mopria), new String[]{"_ipp._tcp", "_ipps._tcp"}); + } + + @Override + public boolean matchesCriteria(String vendor, NsdServiceInfo nsdServiceInfo) { + String pdls = MDnsUtils.getString(nsdServiceInfo.getAttributes().get(PDL_ATTRIBUTE)); + return (!TextUtils.isEmpty(pdls) + && (pdls.contains(PDL__PDF) + || pdls.contains(PDL__PCLM) + || pdls.contains(PDL__PWG_RASTER))); + } + + @Override + public int getCount() { + Pair<Integer, Integer> count = mListener.getCount(); + return ((count.first > 1) ? count.second : 0); + } +} diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/util/DiscoveryListenerMultiplexer.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/util/DiscoveryListenerMultiplexer.java new file mode 100644 index 000000000000..d82b87176299 --- /dev/null +++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/util/DiscoveryListenerMultiplexer.java @@ -0,0 +1,177 @@ +/* + * 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.annotation.NonNull; +import android.net.nsd.NsdManager; +import android.net.nsd.NsdServiceInfo; +import android.util.ArrayMap; +import android.util.Log; + +import java.util.ArrayList; + +/** + * Used to multiplex listening for NSD services. This is needed as only a limited amount of + * {@link NsdManager.DiscoveryListener listeners} are allowed. + */ +public class DiscoveryListenerMultiplexer { + private static final String LOG_TAG = "DiscoveryListenerMx"; + + /** List of registered {@link DiscoveryListenerSet discovery sets}. */ + private static final @NonNull ArrayMap<String, DiscoveryListenerSet> sListeners = + new ArrayMap<>(); + + /** + * Add a new {@link NsdManager.DiscoveryListener listener} for a {@code serviceType}. + * + * @param nsdManager The {@link NsdManager NSD manager} to use + * @param serviceType The service type to listen for + * @param newListener the {@link NsdManager.DiscoveryListener listener} to add. + */ + public static void addListener(@NonNull NsdManager nsdManager, @NonNull String serviceType, + @NonNull NsdManager.DiscoveryListener newListener) { + synchronized (sListeners) { + DiscoveryListenerSet listenerSet = sListeners.get(serviceType); + + if (listenerSet == null) { + ArrayList<NsdManager.DiscoveryListener> subListeners = new ArrayList<>(1); + listenerSet = new DiscoveryListenerSet(subListeners, + new MultiListener(subListeners)); + + sListeners.put(serviceType, listenerSet); + } + + synchronized (listenerSet.subListeners) { + if (listenerSet.subListeners.isEmpty()) { + nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, + listenerSet.mainListener); + } + + listenerSet.subListeners.add(newListener); + } + } + } + + /** + * Remove a previously added {@link NsdManager.DiscoveryListener listener}. + * + * @param nsdManager The {@link NsdManager NSD manager} to use + * @param listener The {@link NsdManager.DiscoveryListener listener} that was registered + * + * @return true iff the listener was removed + */ + public static boolean removeListener(@NonNull NsdManager nsdManager, + @NonNull NsdManager.DiscoveryListener listener) { + boolean wasRemoved = false; + + synchronized (sListeners) { + for (DiscoveryListenerSet listeners : sListeners.values()) { + synchronized (listeners) { + wasRemoved = listeners.subListeners.remove(listener); + + if (wasRemoved) { + if (listeners.subListeners.isEmpty()) { + nsdManager.stopServiceDiscovery(listeners.mainListener); + } + + break; + } + } + } + } + + return wasRemoved; + } + + /** Private class holding all data for a service type */ + private static class DiscoveryListenerSet { + /** The plugin's listeners */ + final @NonNull ArrayList<NsdManager.DiscoveryListener> subListeners; + + /** The listener registered with the NSD Manager */ + final @NonNull MultiListener mainListener; + + private DiscoveryListenerSet(ArrayList<NsdManager.DiscoveryListener> subListeners, + MultiListener mainListener) { + this.subListeners = subListeners; + this.mainListener = mainListener; + } + } + + /** + * A {@link NsdManager.DiscoveryListener} that calls a list of registered listeners when + * a service is found or lost. + */ + private static class MultiListener implements NsdManager.DiscoveryListener { + private final @NonNull ArrayList<NsdManager.DiscoveryListener> mListeners; + + /** + * Create a new multi listener. + * + * @param listeners The listeners to forward the calls. + */ + public MultiListener(@NonNull ArrayList<NsdManager.DiscoveryListener> listeners) { + mListeners = listeners; + } + + @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) { + // not implemented + } + + @Override + public void onDiscoveryStopped(String serviceType) { + // not implemented + } + + @Override + public void onServiceFound(NsdServiceInfo serviceInfo) { + synchronized (mListeners) { + int numListeners = mListeners.size(); + for (int i = 0; i < numListeners; i++) { + NsdManager.DiscoveryListener listener = mListeners.get(i); + + listener.onServiceFound(serviceInfo); + } + } + } + + @Override + public void onServiceLost(NsdServiceInfo serviceInfo) { + synchronized (mListeners) { + int numListeners = mListeners.size(); + for (int i = 0; i < numListeners; i++) { + NsdManager.DiscoveryListener listener = mListeners.get(i); + + listener.onServiceLost(serviceInfo); + } + } + } + } +} |