From 4251c0639dddbec55bebecd150d44b4a194be16d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Katzer?= Date: Wed, 27 Jul 2016 14:24:48 +0200 Subject: [PATCH] Support for picking a printer for iOS and Android --- CHANGELOG.md | 5 +- plugin.xml | 30 ++ res/android/layout/printer_list_item.xml | 80 +++++ .../layout/select_printer_activity.xml | 74 ++++ src/android/Printer.java | 171 +++------ src/android/ext/PrintManager.java | 115 ++++++ src/android/ext/PrintServiceInfo.java | 62 ++++ src/android/ext/PrinterDiscoverySession.java | 178 +++++++++ src/android/reflect/Meta.java | 123 +++++++ src/android/ui/SelectPrinterActivity.java | 339 ++++++++++++++++++ src/ios/APPPrinter.m | 3 +- 11 files changed, 1064 insertions(+), 116 deletions(-) create mode 100644 res/android/layout/printer_list_item.xml create mode 100644 res/android/layout/select_printer_activity.xml create mode 100644 src/android/ext/PrintManager.java create mode 100644 src/android/ext/PrintServiceInfo.java create mode 100644 src/android/ext/PrinterDiscoverySession.java create mode 100644 src/android/reflect/Meta.java create mode 100644 src/android/ui/SelectPrinterActivity.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 7578f04..ad45646 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,9 @@ #### Version 0.7.2 (not yet released) - [__change__:] Changed plugin ID to `cordova-plugin-printer` - [__change__:] Plugin requires Android KitKat or newer -- [__change__:] `isAvailable` returns false if no enabled print services can be found (Android) -- [enhancement:] `isAvailable` returns additional list of available print services (Android) +- [__change__:] `isAvailable` returns false if no enabled service can be found (Android) +- [feature:] New `pick` interface to pick a printer for future usage +- [enhancement:] `isAvailable` returns count of available services (Android) - [enhancement:] `print` returns bool value to indicate the result - [enhancement:] Added missing `duplex` support (Android) - [__change__:] `duplex` requires a string (`none`, `long` or `short`) diff --git a/plugin.xml b/plugin.xml index 49d3212..6ccfbec 100644 --- a/plugin.xml +++ b/plugin.xml @@ -73,7 +73,37 @@ + + + + + + + + + + + + + + + + + + + diff --git a/res/android/layout/printer_list_item.xml b/res/android/layout/printer_list_item.xml new file mode 100644 index 0000000..aec511a --- /dev/null +++ b/res/android/layout/printer_list_item.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/res/android/layout/select_printer_activity.xml b/res/android/layout/select_printer_activity.xml new file mode 100644 index 0000000..7897d66 --- /dev/null +++ b/res/android/layout/select_printer_activity.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/android/Printer.java b/src/android/Printer.java index 0df5f68..ec5d603 100644 --- a/src/android/Printer.java +++ b/src/android/Printer.java @@ -22,13 +22,12 @@ package de.appplant.cordova.plugin.printer; import android.app.Activity; -import android.content.Context; import android.content.Intent; import android.os.Build; import android.print.PrintAttributes; import android.print.PrintDocumentAdapter; import android.print.PrintJob; -import android.print.PrintManager; +import android.print.PrinterId; import android.view.View; import android.webkit.WebSettings; import android.webkit.WebView; @@ -41,13 +40,17 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; +import de.appplant.cordova.plugin.printer.ext.PrintManager; +import de.appplant.cordova.plugin.printer.ext.PrintServiceInfo; +import de.appplant.cordova.plugin.printer.reflect.Meta; +import de.appplant.cordova.plugin.printer.ui.SelectPrinterActivity; + +import static de.appplant.cordova.plugin.printer.ui.SelectPrinterActivity.EXTRA_PRINTER_ID; + /** * Plugin to print HTML documents. Therefore it creates an invisible web view * that loads the markup data. Once the page has been fully rendered it takes @@ -102,6 +105,11 @@ public class Printer extends CordovaPlugin { return true; } + if (action.equalsIgnoreCase("pick")) { + pick(); + return true; + } + if (action.equalsIgnoreCase("print")) { print(args); return true; @@ -118,13 +126,14 @@ public class Printer extends CordovaPlugin { cordova.getThreadPool().execute(new Runnable() { @Override public void run() { - List ids = getEnabledPrintServiceIds(); - Boolean available = ids.size() > 1; + PrintManager pm = new PrintManager(cordova.getActivity()); + List services = pm.getEnabledPrintServices(); + Boolean available = services.size() > 0; PluginResult res1 = new PluginResult( PluginResult.Status.OK, available); PluginResult res2 = new PluginResult( - PluginResult.Status.OK, new JSONArray(ids)); + PluginResult.Status.OK, services.size()); PluginResult res = new PluginResult( PluginResult.Status.OK, Arrays.asList(res1, res2)); @@ -152,6 +161,17 @@ public class Printer extends CordovaPlugin { }); } + /** + * Presents a list with all enabled print services and invokes the + * callback with the selected one. + */ + private void pick () { + Intent intent = new Intent( + cordova.getActivity(), SelectPrinterActivity.class); + + cordova.startActivityForResult(this, intent, 0); + } + /** * Loads the content into the web view. * @@ -190,10 +210,10 @@ public class Printer extends CordovaPlugin { view.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY); if (Build.VERSION.SDK_INT >= 21) { - Method setMixedContentModeMethod = getMethod(settings.getClass(), - "setMixedContentMode", int.class); + Method setMixedContentModeMethod = Meta.getMethod( + settings.getClass(), "setMixedContentMode", int.class); - invokeMethod(settings, setMixedContentModeMethod, 2); + Meta.invokeMethod(settings, setMixedContentModeMethod, 2); } setWebViewClient(props); @@ -219,7 +239,7 @@ public class Printer extends CordovaPlugin { @Override public void onPageFinished (WebView webView, String url) { - PrintManager printManager = getPrintMgr(); + PrintManager pm = new PrintManager(cordova.getActivity()); PrintAttributes.Builder builder = new PrintAttributes.Builder(); PrintDocumentAdapter adapter = getAdapter(webView, docName); @@ -235,14 +255,14 @@ public class Printer extends CordovaPlugin { if (!duplex.equals("none") && Build.VERSION.SDK_INT >= 23) { boolean longEdge = duplex.equals("long"); - Method setDuplexModeMethod = getMethod(builder.getClass(), - "setDuplexMode", int.class); + Method setDuplexModeMethod = Meta.getMethod( + builder.getClass(), "setDuplexMode", int.class); - invokeMethod(builder, setDuplexModeMethod, + Meta.invokeMethod(builder, setDuplexModeMethod, longEdge ? 2 : 4); } - job = printManager.print(docName, adapter, builder.build()); + job = pm.getInstance().print(docName, adapter, builder.build()); view = null; } }); @@ -265,14 +285,24 @@ public class Printer extends CordovaPlugin { command.sendPluginResult(res); } - /** - * Get a PrintManager instance. - * - * @return A PrintManager instance. - */ - private PrintManager getPrintMgr () { - return (PrintManager) cordova.getActivity() - .getSystemService(Context.PRINT_SERVICE); + @Override + public void onActivityResult(int requestCode, int resultCode, Intent intent) { + super.onActivityResult(requestCode, resultCode, intent); + + if (command == null) + return; + + String url = null; + + if (intent != null) { + PrinterId printerId = intent.getParcelableExtra(EXTRA_PRINTER_ID); + url = printerId.toString(); + } + + PluginResult res = new PluginResult( + PluginResult.Status.OK, url); + + command.sendPluginResult(res); } /** @@ -290,99 +320,14 @@ public class Printer extends CordovaPlugin { */ private PrintDocumentAdapter getAdapter (WebView webView, String docName) { if (Build.VERSION.SDK_INT >= 21) { - Method createPrintDocumentAdapterMethod = getMethod( + Method createPrintDocumentAdapterMethod = Meta.getMethod( WebView.class, "createPrintDocumentAdapter", String.class); - return (PrintDocumentAdapter) invokeMethod( + return (PrintDocumentAdapter) Meta.invokeMethod( webView, createPrintDocumentAdapterMethod, docName); } else { - Method createPrintDocumentAdapterMethod = getMethod( - WebView.class, "createPrintDocumentAdapter"); - - return (PrintDocumentAdapter) invokeMethod( - webView, createPrintDocumentAdapterMethod); + return (PrintDocumentAdapter) Meta.invokeMethod(webView, + "createPrintDocumentAdapterMethod"); } } - - /** - * Get a list of ids of all installed and enabled print services. For - * that it uses reflections to call public but hidden methods from the - * PrintManager. - * - * @return A list of found print service ids. - */ - private List getEnabledPrintServiceIds () { - try { - PrintManager printMgr = getPrintMgr(); - Class printerCls = Class.forName( - "android.printservice.PrintServiceInfo"); - Method getPrinterMethod = getMethod(printMgr.getClass(), - "getEnabledPrintServices"); - Method getIdMethod = getMethod(printerCls, - "getId"); - - List printers = (List) invokeMethod(printMgr, getPrinterMethod); - ArrayList printerIds = new ArrayList(); - - printerIds.add("android.print.pdf"); - - if (printers == null) - return printerIds; - - for (Object printer : printers) { - String printerId = (String) invokeMethod(printer, getIdMethod); - printerIds.add(printerId); - } - - return printerIds; - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } - - return Collections.emptyList(); - } - - /** - * Finds the method with given name and set of arguments. - * - * @param cls - * The class in where to look for the method declaration. - * @param name - * The name of the method. - * @param params - * The arguments of the method. - * @return - * The found method or null. - */ - private Method getMethod (Class cls, String name, Class... params) { - try { - return cls.getDeclaredMethod(name, params); - } catch (NoSuchMethodException e) { - return null; - } - } - - /** - * Invokes the method on the given object with the specified arguments. - * - * @param obj - * An object which class defines the method. - * @param method - * The method to invoke. - * @param args - * Set of arguments. - * @return - * The returned object or null. - */ - private Object invokeMethod (Object obj, Method method, Object... args) { - try { - return method.invoke(obj, args); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } - - return null; - } } diff --git a/src/android/ext/PrintManager.java b/src/android/ext/PrintManager.java new file mode 100644 index 0000000..b275033 --- /dev/null +++ b/src/android/ext/PrintManager.java @@ -0,0 +1,115 @@ +/* + Copyright 2013-2016 appPlant GmbH + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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 de.appplant.cordova.plugin.printer.ext; + +import android.content.Context; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import de.appplant.cordova.plugin.printer.reflect.Meta; + +public final class PrintManager { + + /** + * The application context. + */ + private Context ctx; + + /** + * Constructor + * + * @param context The context where to look for. + */ + public PrintManager (Context context) { + this.ctx = context; + } + + /** + * Get an instance from PrintManager service. + * + * @return A PrintManager instance. + */ + public final android.print.PrintManager getInstance () { + return (android.print.PrintManager) + ctx.getSystemService(Context.PRINT_SERVICE); + } + + /** + * Gets the list of installed print services. + * + * @return The found service list or an empty list. + */ + public final List getInstalledPrintServices () { + List printers = (List) Meta.invokeMethod(getInstance(), + "getInstalledPrintServices"); + + ArrayList services = + new ArrayList(); + + if (printers == null) + return Collections.emptyList(); + + for (Object printer : printers) { + services.add(new PrintServiceInfo(printer)); + } + + return services; + } + + /** + * Gets the list of enabled print services. + * + * @return The found service list or an empty list. + */ + public final List getEnabledPrintServices () { + List printers = (List) Meta.invokeMethod(getInstance(), + "getEnabledPrintServices"); + + ArrayList services = + new ArrayList(); + + if (printers == null) + return Collections.emptyList(); + + for (Object printer : printers) { + services.add(new PrintServiceInfo(printer)); + } + + return services; + } + + /** + * Creates an session object to discover all printer services. To do so + * you need to register a listener object and start the discovery process. + * + * @return An instance of class PrinterDiscoverySession. + */ + public final PrinterDiscoverySession createPrinterDiscoverySession () { + Object session = Meta.invokeMethod(getInstance(), + "createPrinterDiscoverySession"); + + return new PrinterDiscoverySession(session); + } + +} \ No newline at end of file diff --git a/src/android/ext/PrintServiceInfo.java b/src/android/ext/PrintServiceInfo.java new file mode 100644 index 0000000..be57764 --- /dev/null +++ b/src/android/ext/PrintServiceInfo.java @@ -0,0 +1,62 @@ +/* + Copyright 2013-2016 appPlant GmbH + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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 de.appplant.cordova.plugin.printer.ext; + +import android.content.pm.ResolveInfo; + +import de.appplant.cordova.plugin.printer.reflect.Meta; + +public final class PrintServiceInfo { + + /** + * The wrapped object of type android.printservice.PrintServiceInfo + */ + private Object obj; + + /** + * Wraps the object of the hidden class + * android.printservice.PrintServiceInfo. + * + * @param wrappedObj The object to wrap. + */ + PrintServiceInfo (Object wrappedObj) { + obj = wrappedObj; + } + + /** + * The add printers activity name. + * + * @return The add printers activity name. + */ + public final String getAddPrintersActivityName() { + return (String) Meta.invokeMethod(obj, "getAddPrintersActivityName"); + } + /** + * The service {@link ResolveInfo}. + * + * @return The info. + */ + public final ResolveInfo getResolveInfo() { + return (ResolveInfo) Meta.invokeMethod(obj, "getResolveInfo"); + } + +} \ No newline at end of file diff --git a/src/android/ext/PrinterDiscoverySession.java b/src/android/ext/PrinterDiscoverySession.java new file mode 100644 index 0000000..b04da82 --- /dev/null +++ b/src/android/ext/PrinterDiscoverySession.java @@ -0,0 +1,178 @@ +/* + Copyright 2013-2016 appPlant GmbH + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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 de.appplant.cordova.plugin.printer.ext; + +import android.print.PrinterInfo; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Collections; +import java.util.List; + +import de.appplant.cordova.plugin.printer.reflect.Meta; + +public final class PrinterDiscoverySession { + + /** + * Required interface of the listener. + */ + public interface OnPrintersChangeListener { + void onPrintersChanged(List printerInfos); + } + + /** + * Required to be able to register a listener of the expected type. + */ + private class OnPrintersChangeProxy implements InvocationHandler { + @Override + public Object invoke (Object o, Method method, Object[] objects) + throws Throwable { + + if (method.getName().equals("onPrintersChanged")) { + onPrintersChanged(); + return null; + } else throw new Exception(); + } + + /** + * Delegate the event to the listener. + */ + public void onPrintersChanged() { + notifyOnPrintersChanged(); + } + } + + /** + * The wrapped session of type + * android.print.PrinterDiscoverySession + */ + private Object session; + + /** + * Registered listener object. + */ + private OnPrintersChangeListener listener = null; + + /** + * Constructor + * + * @param session An instance of type + * android.print.PrinterDiscoverySession + */ + public PrinterDiscoverySession (Object session) { + this.session = session; + } + + /** + * Start discovering available printers. + */ + public final void startPrinterDiscovery () { + Method method = Meta.getMethod(session.getClass(), + "startPrinterDiscovery", List.class); + + Meta.invokeMethod(session, method, Collections.emptyList()); + } + + /** + * Stop discovering printers. + */ + public final void stopPrinterDiscovery() { + Meta.invokeMethod(session, "stopPrinterDiscovery"); + } + + /** + * If session is discovering printers. + */ + @SuppressWarnings("ConstantConditions") + public final boolean isPrinterDiscoveryStarted() { + return (Boolean) Meta.invokeMethod(session, + "isPrinterDiscoveryStarted"); + } + + /** + * Register the listener. + * + * @param listener Use null to unregister the listener. + */ + public final void setOnPrintersChangeListener( + OnPrintersChangeListener listener) { + + Class interfaceCls = null; + Object proxy = null; + + this.listener = listener; + + try { + interfaceCls = Class.forName( + "android.print.PrinterDiscoverySession$OnPrintersChangeListener"); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + + if (interfaceCls == null) + return; + + Method method = Meta.getMethod(session.getClass(), + "setOnPrintersChangeListener", interfaceCls); + + if (listener != null) { + Class[] interfaces = {interfaceCls}; + + proxy = Proxy.newProxyInstance( + interfaceCls.getClassLoader(), + interfaces, + new OnPrintersChangeProxy() + ); + } + + Meta.invokeMethod(session, method, proxy); + } + + /** + * Destroy the session if not already done. + */ + public final void destroy() { + Meta.invokeMethod(session, "destroy"); + } + + /** + * Get a list of all yet discovered printers. + * + * @return List of their basic infos + */ + @SuppressWarnings("unchecked") + public final List getPrinters() { + Method method = Meta.getMethod(session.getClass(), "getPrinters"); + + return (List) Meta.invokeMethod(session, method); + } + + /** + * Notifies the listener about the occurred event. + */ + private void notifyOnPrintersChanged() { + if (listener != null) { + listener.onPrintersChanged(getPrinters()); + } + } +} \ No newline at end of file diff --git a/src/android/reflect/Meta.java b/src/android/reflect/Meta.java new file mode 100644 index 0000000..71a5767 --- /dev/null +++ b/src/android/reflect/Meta.java @@ -0,0 +1,123 @@ +/* + Copyright 2013-2016 appPlant GmbH + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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 de.appplant.cordova.plugin.printer.reflect; + +import android.content.Context; +import android.content.res.Resources; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * This is an activity for selecting a printer. + */ +public abstract class Meta { + /** + * Finds the method with given name and set of arguments. + * + * @param cls + * The class in where to look for the method declaration. + * @param name + * The name of the method. + * @param params + * The arguments of the method. + * @return + * The found method or null. + */ + public static Method getMethod (Class cls, String name, Class... params) { + try { + return cls.getDeclaredMethod(name, params); + } catch (NoSuchMethodException e) { + return null; + } + } + + /** + * Invokes the method on the given object with the specified arguments. + * + * @param obj + * An object which class defines the method. + * @param method + * The method to invoke. + * @param args + * Set of arguments. + * @return + * The returned object or null. + */ + public static Object invokeMethod (Object obj, Method method, Object... args) { + try { + return method.invoke(obj, args); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + + return null; + } + + /** + * Invokes the method on the given object. + * + * @param obj + * An object which class defines the method. + * @param methodName + * The name of method to invoke. + * @return + * The returned object or null. + */ + public static Object invokeMethod (Object obj, String methodName) { + Method method = getMethod(obj.getClass(), methodName); + + if (method == null) + return null; + + try { + return method.invoke(obj); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + + return null; + } + + /** + * Return a resource identifier for the given resource name. + * + * @param context The applications context. + * @param type Resource type to find (id or layout or string ...) + * @param name The name of the desired resource. + * + * @return The associated resource identifier or 0 if not found. + */ + public static int getResId (Context context, String type, String name) { + Resources res = context.getResources(); + String pkgName = context.getPackageName(); + + int resId; + resId = res.getIdentifier(name, type, pkgName); + + return resId; + } +} diff --git a/src/android/ui/SelectPrinterActivity.java b/src/android/ui/SelectPrinterActivity.java new file mode 100644 index 0000000..18bd7d2 --- /dev/null +++ b/src/android/ui/SelectPrinterActivity.java @@ -0,0 +1,339 @@ +/* + Copyright 2013-2016 appPlant GmbH + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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 de.appplant.cordova.plugin.printer.ui; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.print.PrinterId; +import android.print.PrinterInfo; +import android.text.TextUtils; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +import de.appplant.cordova.plugin.printer.ext.PrintManager; +import de.appplant.cordova.plugin.printer.ext.PrinterDiscoverySession; +import de.appplant.cordova.plugin.printer.ext.PrinterDiscoverySession.OnPrintersChangeListener; +import de.appplant.cordova.plugin.printer.reflect.Meta; + +@SuppressLint("SetTextI18n") +public final class SelectPrinterActivity extends Activity { + + /** + * The extra string to identify the data from the intents bundle. + */ + public static final String EXTRA_PRINTER_ID = + "INTENT_EXTRA_PRINTER_ID"; + + /** + * Reference to the main view which lists all discovered printers. + */ + private ListView listView; + + /** + * Session for printer discovering within the network. + */ + private PrinterDiscoverySession discoverySession; + + /** + * Called when the activity is starting. + * + * @param savedInstanceState If the activity is being re-initialized + * after previously being shut down then this + * Bundle contains the data it most recently + * supplied in. + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(Meta.getResId( + this, "layout", "select_printer_activity")); + + discoverySession = new PrintManager(this).createPrinterDiscoverySession(); + listView = (ListView) findViewById(android.R.id.list); + + initListView(); + startPrinterDiscovery(); + updateEmptyView(); + } + + /** + * Perform any final cleanup before an activity is destroyed. + */ + @Override + protected void onDestroy() { + discoverySession.destroy(); + super.onDestroy(); + } + + /** + * Sends an intent with the specified printer back to the owning activity + * and finishes that activity. + * + * @param printerId The printerId object of the printer containing + * everything necessary to identify and contact him. + */ + private void onPrinterSelected (PrinterId printerId) { + Intent intent = new Intent(); + + intent.putExtra(EXTRA_PRINTER_ID, printerId); + setResult(RESULT_OK, intent); + + finish(); + } + + /** + * Assigns the adapter and the click listener to the list. + */ + private void initListView() { + listView.setAdapter(new ListViewAdapter()); + + listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + if (!listView.getAdapter().isEnabled(position)) { + return; + } + + PrinterInfo printer = (PrinterInfo) + listView.getAdapter().getItem(position); + + onPrinterSelected(printer.getId()); + } + }); + } + + /** + * Start printer discovery if there are installed services found on the + * device. + */ + private void startPrinterDiscovery() { + PrintManager pm = new PrintManager(this); + + if (pm.getInstalledPrintServices().isEmpty()) + return; + + discoverySession.startPrinterDiscovery(); + } + + /** + * Shows or hides the empty-view of the list view depend on if the + * adapter contains any printers or not. + */ + private void updateEmptyView() { + TextView titleView = (TextView) findViewById(android.R.id.title); + View progressBar = findViewById(android.R.id.progress); + + if (listView.getEmptyView() == null) { + View emptyView = findViewById(android.R.id.empty); + listView.setEmptyView(emptyView); + } + + if (discoverySession.isPrinterDiscoveryStarted()) { + titleView.setText("Searching for printers"); + progressBar.setVisibility(View.VISIBLE); + } else { + titleView.setText("No printers found"); + progressBar.setVisibility(View.GONE); + } + } + + private final class ListViewAdapter extends BaseAdapter { + + /** + * Wait lock for synchronization. + */ + private final Object lock = new Object(); + + /** + * List of all received printer to display in the list. + */ + private final List printers = new ArrayList(); + + /** + * Constructor registers a listener to monitor about newly discovered + * printers. + */ + ListViewAdapter() { + discoverySession.setOnPrintersChangeListener(new OnPrintersChangeListener() { + @Override + public void onPrintersChanged (List printerInfos) { + printers.addAll(printerInfos); + notifyDataSetChanged(); + } + }); + } + + /** + * How many items are in the data set represented by this Adapter. + * + * @return Count of items. + */ + @Override + public int getCount() { + synchronized (lock) { + return printers.size(); + } + } + + /** + * Get the data item associated with the specified position in the + * data set. + * + * @param position Position of the item whose data we want within the + * adapter's data set. + * + * @return The data at the specified position. + */ + @Override + public Object getItem (int position) { + synchronized (lock) { + return printers.get(position); + } + } + + /** + * Get the row id associated with the specified position in the list. + * + * @param position The position of the item within the adapter's data + * set whose row id we want. + * + * @return The id of the item at the specified position. + */ + @Override + public long getItemId (int position) { + return position; + } + + /** + * Gets a View that displays in the drop down popup the data at the + * specified position in the data set. + * + * @param pos The index of the item whose view we want. + * @param view The old view to reuse, if possible. Note: You should + * check that this view is non-null and of an appropriate + * type before using. If it is not possible to convert + * this view to display the correct data. + * @param parent The parent that this view will eventually be + * attached to. + * + * @return A View corresponding to the data at the specified position. + */ + @Override + public View getDropDownView (int pos, View view, ViewGroup parent) { + return getView(pos, view, parent); + } + + /** + * Get a View that displays the data at the specified position in the + * data set. + * + * @param pos The position of the item within the adapter's data set + * of the item whose view we want. + * @param view The old view to reuse, if possible. Note: You should + * check that this view is non-null and of an appropriate + * type before using. If it is not possible to convert + * this view to display the correct data. + * @param parent The parent that this view will eventually be + * attached to. + * + * @return A View corresponding to the data at the specified position. + */ + @Override + public View getView (int pos, View view, ViewGroup parent) { + PrinterInfo printer = (PrinterInfo) getItem(pos); + CharSequence title = printer.getName(); + CharSequence subtitle = null; + Drawable icon = null; + + if (view == null) { + view = getLayoutInflater().inflate( + Meta.getResId(getApplicationContext(), "layout", "printer_list_item"), + parent, false); + } + + try { + PackageManager pm = getPackageManager(); + PrinterId pId = printer.getId(); + Object cmpName = Meta.invokeMethod(pId, "getServiceName"); + Object pkgName = Meta.invokeMethod(cmpName, "getPackageName"); + + PackageInfo packageInfo = pm.getPackageInfo( + (String) pkgName, 0); + + subtitle = packageInfo.applicationInfo.loadLabel(pm); + icon = packageInfo.applicationInfo.loadIcon(pm); + } catch (NameNotFoundException e) { + /* ignore */ + } + + TextView titleView = (TextView) view.findViewById(android.R.id.title); + titleView.setText(title); + + TextView subtitleView = (TextView) view.findViewById(android.R.id.hint); + if (!TextUtils.isEmpty(subtitle)) { + subtitleView.setText(subtitle); + subtitleView.setVisibility(View.VISIBLE); + } else { + subtitleView.setText(null); + subtitleView.setVisibility(View.GONE); + } + + ImageView iconView = (ImageView) view.findViewById(android.R.id.icon); + if (icon != null) { + iconView.setImageDrawable(icon); + iconView.setVisibility(View.VISIBLE); + } else { + iconView.setVisibility(View.GONE); + } + + return view; + } + + /** + * Indicate whether the specified printer by position is available. + * + * @param position The position of the printer in the list. + * + * @return A truthy value means that the printer is available. + */ + @Override + public boolean isEnabled (int position) { + PrinterInfo printer = (PrinterInfo) getItem(position); + return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE; + } + } +} diff --git a/src/ios/APPPrinter.m b/src/ios/APPPrinter.m index 207a44b..a68d0af 100755 --- a/src/ios/APPPrinter.m +++ b/src/ios/APPPrinter.m @@ -46,7 +46,8 @@ [self.commandDelegate runInBackground:^{ CDVPluginResult* pluginResult; BOOL isAvailable = [self isPrintingAvailable]; - NSArray *multipart = @[[NSNumber numberWithBool:isAvailable], @[]]; + NSArray *multipart = @[[NSNumber numberWithBool:isAvailable], + [NSNumber numberWithInt:-1]]; pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsMultipart:multipart];