diff --git a/CHANGELOG.md b/CHANGELOG.md index f833d9a..4fd0962 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,12 @@ ## ChangeLog +#### 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) +- [enhancement:] Support `duplex` attribute (Android) + + #### Version 0.7.1 (23.04.2015) - [bugfix:] `isAvailable` does not block the main thread anymore. - [bugfix:] iPad+iOS8 incompatibility (Thanks to __zmagyar__) diff --git a/plugin.xml b/plugin.xml index 55cec76..49d3212 100644 --- a/plugin.xml +++ b/plugin.xml @@ -42,7 +42,8 @@ - + + diff --git a/src/android/Printer.java b/src/android/Printer.java index 9879af4..fb4d646 100644 --- a/src/android/Printer.java +++ b/src/android/Printer.java @@ -21,14 +21,6 @@ package de.appplant.cordova.plugin.printer; -import org.apache.cordova.CallbackContext; -import org.apache.cordova.CordovaPlugin; -import org.apache.cordova.PluginResult; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.os.Build; @@ -39,7 +31,20 @@ import android.print.PrintManager; import android.webkit.WebView; import android.webkit.WebViewClient; -@TargetApi(19) +import org.apache.cordova.CallbackContext; +import org.apache.cordova.CordovaPlugin; +import org.apache.cordova.PluginResult; +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; + public class Printer extends CordovaPlugin { private WebView view; @@ -65,23 +70,20 @@ public class Printer extends CordovaPlugin { */ @Override public boolean execute (String action, JSONArray args, - CallbackContext callback) throws JSONException { + CallbackContext callback) throws JSONException { command = callback; if (action.equalsIgnoreCase("isAvailable")) { isAvailable(); - return true; } if (action.equalsIgnoreCase("print")) { print(args); - return true; } - // Returning false results in a "MethodNotFound" error. return false; } @@ -93,10 +95,17 @@ public class Printer extends CordovaPlugin { cordova.getThreadPool().execute(new Runnable() { @Override public void run() { - Boolean supported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; - PluginResult result = new PluginResult(PluginResult.Status.OK, supported); + List ids = getEnabledPrintServiceIds(); + Boolean available = ids.size() > 1; - command.sendPluginResult(result); + PluginResult res1 = new PluginResult( + PluginResult.Status.OK, available); + PluginResult res2 = new PluginResult( + PluginResult.Status.OK, new JSONArray(ids)); + PluginResult res = new PluginResult( + PluginResult.Status.OK, Arrays.asList(res1, res2)); + + command.sendPluginResult(res); } }); } @@ -130,11 +139,12 @@ public class Printer extends CordovaPlugin { if (content.startsWith("http") || content.startsWith("file:")) { view.loadUrl(content); } else { - //Set base URI to the assets/www folder String baseURL = webView.getUrl(); baseURL = baseURL.substring(0, baseURL.lastIndexOf('/') + 1); - view.loadDataWithBaseURL(baseURL, content, "text/html", "UTF-8", null); + // Set base URI to the assets/www folder + view.loadDataWithBaseURL( + baseURL, content, "text/html", "UTF-8", null); } } @@ -161,9 +171,10 @@ public class Printer extends CordovaPlugin { * The JSON object with the containing page properties */ private void setWebViewClient (JSONObject props) { - final String docName = props.optString("name", DEFAULT_DOC_NAME); + final String docName = props.optString("name", DEFAULT_DOC_NAME); final boolean landscape = props.optBoolean("landscape", false); final boolean graystyle = props.optBoolean("graystyle", false); + final String duplex = props.optString("duplex", "none"); view.setWebViewClient(new WebViewClient() { @Override @@ -173,27 +184,31 @@ public class Printer extends CordovaPlugin { @Override public void onPageFinished (WebView webView, String url) { - // Get a PrintManager instance - PrintManager printManager = (PrintManager) cordova.getActivity() - .getSystemService(Context.PRINT_SERVICE); - - // Get a print adapter instance - PrintDocumentAdapter printAdapter = webView.createPrintDocumentAdapter(); - - // Get a print builder instance + PrintManager printManager = getPrintMgr(); PrintAttributes.Builder builder = new PrintAttributes.Builder(); + PrintDocumentAdapter adapter = getAdapter(webView, docName); - // The page does itself set its own margins builder.setMinMargins(PrintAttributes.Margins.NO_MARGINS); - builder.setColorMode(graystyle ? PrintAttributes.COLOR_MODE_MONOCHROME + builder.setColorMode(graystyle + ? PrintAttributes.COLOR_MODE_MONOCHROME : PrintAttributes.COLOR_MODE_COLOR); - builder.setMediaSize(landscape ? PrintAttributes.MediaSize.UNKNOWN_LANDSCAPE + builder.setMediaSize(landscape + ? PrintAttributes.MediaSize.UNKNOWN_LANDSCAPE : PrintAttributes.MediaSize.UNKNOWN_PORTRAIT); - // Create a print job with name and adapter instance - PrintJob job = printManager.print(docName, printAdapter, builder.build()); + if (!duplex.equals("none") && Build.VERSION.SDK_INT >= 23) { + boolean longEdge = duplex.equals("long"); + Method setDuplexModeMethod = getMethod(builder.getClass(), + "setDuplexMode", int.class); + + invokeMethod(builder, setDuplexModeMethod, + longEdge ? 2 : 4); + } + + PrintJob job = printManager.print( + docName, adapter, builder.build()); invokeCallbackOnceCompletedOrCanceled(job); @@ -221,4 +236,125 @@ public class Printer extends CordovaPlugin { } }); } + + /** + * Get a PrintManager instance. + * + * @return A PrintManager instance. + */ + private PrintManager getPrintMgr () { + return (PrintManager) cordova.getActivity() + .getSystemService(Context.PRINT_SERVICE); + } + + /** + * Create the print document adapter for the web view component. On + * devices older then SDK 21 it will use the deprecated method + * `createPrintDocumentAdapter` without arguments and on newer devices + * the recommended way. + * + * @param webView + * The web view which content to print out. + * @param docName + * The name of the printed document. + * @return + * The created adapter. + */ + private PrintDocumentAdapter getAdapter (WebView webView, String docName) { + if (Build.VERSION.SDK_INT >= 21) { + Method createPrintDocumentAdapterMethod = getMethod( + WebView.class, "createPrintDocumentAdapter", String.class); + + return (PrintDocumentAdapter) invokeMethod( + webView, createPrintDocumentAdapterMethod, docName); + } else { + Method createPrintDocumentAdapterMethod = getMethod( + WebView.class, "createPrintDocumentAdapter"); + + return (PrintDocumentAdapter) 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; + } }