diff --git a/plugin.xml b/plugin.xml index fc28e5e..cc43cca 100644 --- a/plugin.xml +++ b/plugin.xml @@ -26,7 +26,7 @@ + version="0.8.0.alpha"> Printer @@ -97,37 +97,24 @@ - - - - + + + + + + + + + - - - - - - - - - - - - - + diff --git a/res/android/layout/printer_list_item.xml b/res/android/layout/printer_list_item.xml deleted file mode 100644 index aec511a..0000000 --- a/res/android/layout/printer_list_item.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/res/android/layout/select_printer_activity.xml b/res/android/layout/select_printer_activity.xml deleted file mode 100644 index 7897d66..0000000 --- a/res/android/layout/select_printer_activity.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/android/AssetUtil.java b/src/android/AssetUtil.java new file mode 100644 index 0000000..d9e7bc4 --- /dev/null +++ b/src/android/AssetUtil.java @@ -0,0 +1,343 @@ +/* + 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; + +import android.content.Context; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Base64; + +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +class AssetUtil { + + // List of supported content types + enum ContentType { PLAIN, HTML, IMAGE, PDF, SELF } + + // Application context + private final @NonNull Context context; + + /** + * Initializes the asset utils. + * + * @param ctx The application context. + */ + AssetUtil (@NonNull Context ctx) { + this.context = ctx; + } + + /** + * Returns the content type for the file referenced by its uri. + * + * @param path The path to check. + * + * @return The content type even the file does not exist. + */ + static @NonNull ContentType getContentType (@Nullable String path) + { + ContentType type = ContentType.PLAIN; + + if (path == null || path.isEmpty()) + { + type = ContentType.SELF; + } + else if (path.charAt(0) == '<') + { + type = ContentType.HTML; + } + else if (path.matches("^[a-z]+://.+")) + { + return path.endsWith(".pdf") ? ContentType.PDF : ContentType.IMAGE; + } + + return type; + } + + /** + * Opens a file://, res:// or base64:// Uri as a stream. + * + * @param path The file path to decode. + * + * @return An open IO stream or null if the file does not exist. + */ + @Nullable InputStream open (@NonNull String path) + { + InputStream stream = null; + + if (path.startsWith("res:")) + { + stream = openResource(path); + } + else if (path.startsWith("file:///")) + { + stream = openFile(path); + } + else if (path.startsWith("file://")) + { + stream = openAsset(path); + } + else if (path.startsWith("base64:")) + { + stream = openBase64(path); + } + + return stream; + } + + /** + * Decodes a file://, res:// or base64:// Uri to bitmap. + * + * @param path The file path to decode. + * + * @return A bitmap or null if the path is not valid + */ + @Nullable Bitmap decode (@NonNull String path) + { + Bitmap bitmap; + + if (path.startsWith("res:")) + { + bitmap = decodeResource(path); + } + else if (path.startsWith("file:///")) + { + bitmap = decodeFile(path); + } + else if (path.startsWith("file://")) + { + bitmap = decodeAsset(path); + } + else if (path.startsWith("base64:")) + { + bitmap = decodeBase64(path); + } + else { + bitmap = BitmapFactory.decodeFile(path); + } + + return bitmap; + } + + /** + * Copies content of input stream to output stream. + * + * @param input The readable input stream. + * @param output The writable output stream. + * + * @throws IOException + */ + static void copy (@NonNull InputStream input, + @NonNull OutputStream output) throws IOException + { + byte[] buf = new byte[1024]; + int bytesRead; + + while ((bytesRead = input.read(buf)) > 0) { + output.write(buf, 0, bytesRead); + } + + output.close(); + input.close(); + } + + /** + * Opens an file given as a file:/// path. + * + * @param path The path to the file. + * + * @return An open IO stream or null if the file does not exist. + */ + private @Nullable InputStream openFile (@NonNull String path) + { + String absPath = path.substring(7); + + try { + return new FileInputStream(absPath); + } catch (FileNotFoundException e) { + return null; + } + } + + /** + * Decodes an file given as a file:/// path to a bitmap. + * + * @param path The path to the file. + * + * @return A bitmap or null if the path is not valid + */ + private @Nullable Bitmap decodeFile (@NonNull String path) + { + String absPath = path.substring(7); + + return BitmapFactory.decodeFile(absPath); + } + + /** + * Opens an asset file given as a file:// path. + * + * @param path The path to the asset. + * + * @return An open IO stream or null if the file does not exist. + */ + private @Nullable InputStream openAsset (@NonNull String path) + { + String resPath = path.replaceFirst("file:/", "www"); + + try { + return getAssets().open(resPath); + } catch (Exception e) { + return null; + } + } + + /** + * Decodes an asset file given as a file:// path to a bitmap. + * + * @param path The path to the asset. + * + * @return A bitmap or null if the path is not valid + */ + private @Nullable Bitmap decodeAsset (@NonNull String path) + { + InputStream stream = openAsset(path); + Bitmap bitmap; + + if (stream == null) + return null; + + bitmap = BitmapFactory.decodeStream(stream); + + try { + stream.close(); + } catch (IOException e) { + // ignore + } + + return bitmap; + } + + /** + * Opens a resource file given as a res:// path. + * + * @param path The path to the resource. + * + * @return An open IO stream or null if the file does not exist. + */ + private @NonNull InputStream openResource (@NonNull String path) + { + String resPath = path.substring(6); + int resId = getResId(resPath); + + return getResources().openRawResource(resId); + } + + /** + * Decodes a resource given as a res:// path to a bitmap. + * + * @param path The path to the resource. + * + * @return A bitmap or null if the path is not valid + */ + private @Nullable Bitmap decodeResource (@NonNull String path) + { + String data = path.substring(9); + byte[] bytes = Base64.decode(data, 0); + + return BitmapFactory.decodeByteArray(bytes, 0, bytes.length); + } + + /** + * Opens a resource file given as a res:// path. + * + * @param path The path to the resource. + * + * @return An open IO stream or null if the file does not exist. + */ + private @NonNull InputStream openBase64 (@NonNull String path) + { + String data = path.substring(9); + byte[] bytes = Base64.decode(data, 0); + + return new ByteArrayInputStream(bytes); + } + + /** + * Decodes a resource given as a base64:// string to a bitmap. + * + * @param path The given relative path. + * + * @return A bitmap or null if the path is not valid + */ + private @Nullable Bitmap decodeBase64 (@NonNull String path) + { + String data = path.substring(9); + byte[] bytes = Base64.decode(data, 0); + + return BitmapFactory.decodeByteArray(bytes, 0, bytes.length); + } + + /** + * Returns the resource ID for the given resource path. + * + * @return The resource ID for the given resource. + */ + private int getResId (@NonNull String resPath) + { + Resources res = getResources(); + String pkgName = context.getPackageName(); + String dirName = "drawable"; + String fileName = resPath; + + if (resPath.contains("/")) { + dirName = resPath.substring(0, resPath.lastIndexOf('/')); + fileName = resPath.substring(resPath.lastIndexOf('/') + 1); + } + + String resName = fileName.substring(0, fileName.lastIndexOf('.')); + int resId = res.getIdentifier(resName, dirName, pkgName); + + if (resId == 0) { + resId = res.getIdentifier(resName, "mipmap", pkgName); + } + + if (resId == 0) { + resId = res.getIdentifier(resName, "drawable", pkgName); + } + + return resId; + } + + private AssetManager getAssets() { + return context.getAssets(); + } + + private Resources getResources() { + return context.getResources(); + } +} diff --git a/src/android/Options.java b/src/android/Options.java new file mode 100644 index 0000000..5fb5b9d --- /dev/null +++ b/src/android/Options.java @@ -0,0 +1,167 @@ +/* + 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; + +import android.print.PrintAttributes; +import android.support.annotation.NonNull; +import android.support.v4.print.PrintHelper; + +import org.json.JSONObject; + +import static android.os.Build.VERSION.SDK_INT; +import static android.print.PrintAttributes.DUPLEX_MODE_LONG_EDGE; +import static android.print.PrintAttributes.DUPLEX_MODE_NONE; +import static android.print.PrintAttributes.DUPLEX_MODE_SHORT_EDGE; +import static android.print.PrintAttributes.Margins.NO_MARGINS; +import static android.print.PrintAttributes.MediaSize.UNKNOWN_LANDSCAPE; +import static android.print.PrintAttributes.MediaSize.UNKNOWN_PORTRAIT; +import static android.support.v4.print.PrintHelper.ORIENTATION_LANDSCAPE; +import static android.support.v4.print.PrintHelper.ORIENTATION_PORTRAIT; +import static android.support.v4.print.PrintHelper.SCALE_MODE_FILL; +import static android.support.v4.print.PrintHelper.SCALE_MODE_FIT; + +/** + * Wrapper for the print job settings. + */ +class Options { + + // The print job settings + private @NonNull JSONObject spec; + + /** + * Constructor + * + * @param spec The print job settings. + */ + Options (@NonNull JSONObject spec) + { + this.spec = spec; + } + + /** + * Returns the name for the print job. + */ + @NonNull String getJobName() + { + String jobName = spec.optString("name"); + + if (jobName == null || jobName.isEmpty()) + { + jobName = "Printer Plugin Job #" + System.currentTimeMillis(); + } + + return jobName; + } + + /** + * Converts the options into a PrintAttributes object. + */ + @NonNull PrintAttributes toPrintAttributes() + { + PrintAttributes.Builder builder = new PrintAttributes.Builder(); + + switch (spec.optString("orientation")) + { + case "landscape": + builder.setMediaSize(UNKNOWN_LANDSCAPE); + break; + case "portrait": + builder.setMediaSize(UNKNOWN_PORTRAIT); + break; + } + + if (spec.has("graystyle")) + { + if (spec.optBoolean("graystyle")) + { + builder.setColorMode(PrintAttributes.COLOR_MODE_MONOCHROME); + } + else + { + builder.setColorMode(PrintAttributes.COLOR_MODE_COLOR); + } + } + + if (!spec.optBoolean("border", true)) + { + builder.setMinMargins(NO_MARGINS); + } + + if (SDK_INT >= 23) + { + switch (spec.optString("duplex")) + { + case "long": + builder.setDuplexMode(DUPLEX_MODE_LONG_EDGE); + break; + case "short": + builder.setDuplexMode(DUPLEX_MODE_SHORT_EDGE); + break; + case "none": + builder.setDuplexMode(DUPLEX_MODE_NONE); + break; + } + } + + return builder.build(); + } + + /** + * Tweaks the printer helper depending on the job spec. + * + * @param printer The printer to decorate. + */ + void decoratePrintHelper (@NonNull PrintHelper printer) + { + switch (spec.optString("orientation")) + { + case "landscape": + printer.setOrientation(ORIENTATION_LANDSCAPE); + break; + case "portrait": + printer.setOrientation(ORIENTATION_PORTRAIT); + break; + } + + if (spec.has("graystyle")) + { + if (spec.optBoolean("graystyle")) + { + printer.setColorMode(PrintHelper.COLOR_MODE_MONOCHROME); + } + else + { + printer.setColorMode(PrintHelper.COLOR_MODE_COLOR); + } + } + + if (spec.has("autoFit")) + { + if (spec.optBoolean("autoFit")) + { + printer.setScaleMode(SCALE_MODE_FIT); + } + else + { + printer.setScaleMode(SCALE_MODE_FILL); + } + } + } +} diff --git a/src/android/PdfAdapter.java b/src/android/PdfAdapter.java new file mode 100644 index 0000000..fce2dad --- /dev/null +++ b/src/android/PdfAdapter.java @@ -0,0 +1,108 @@ +/* + 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; + +import android.content.Context; +import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.ParcelFileDescriptor; +import android.print.PageRange; +import android.print.PrintAttributes; +import android.print.PrintDocumentAdapter; +import android.print.PrintDocumentInfo; +import android.support.annotation.NonNull; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static android.print.PrintDocumentInfo.CONTENT_TYPE_DOCUMENT; + +/** + * Document adapter to render and print PDF files. + */ +class PdfAdapter extends PrintDocumentAdapter { + + // The application context + private @NonNull Context context; + + // The path to the PDF file + private @NonNull String path; + + /** + * Constructor + * + * @param context The context where to look for. + */ + PdfAdapter (@NonNull String path, @NonNull Context context) + { + this.path = path; + this.context = context; + } + + @Override + public void onLayout (PrintAttributes oldAttributes, + PrintAttributes newAttributes, + CancellationSignal cancellationSignal, + LayoutResultCallback callback, + Bundle bundle) + { + PrintDocumentInfo pdi; + + if (cancellationSignal.isCanceled()) + return; + + pdi = new PrintDocumentInfo.Builder("test") + .setContentType(CONTENT_TYPE_DOCUMENT) + .build(); + + callback.onLayoutFinished(pdi, true); + } + + @Override + public void onWrite (PageRange[] range, + ParcelFileDescriptor dest, + CancellationSignal cancellationSignal, + WriteResultCallback callback) + { + if (cancellationSignal.isCanceled()) + return; + + AssetUtil io = new AssetUtil(context); + InputStream in = io.open(path); + + if (in == null) { + callback.onWriteFailed("File not found: " + path); + return; + } + + OutputStream out = new FileOutputStream(dest.getFileDescriptor()); + + try { + AssetUtil.copy(in, out); + } catch (IOException e) { + callback.onWriteFailed(e.getMessage()); + return; + } + + callback.onWriteFinished(new PageRange[]{ PageRange.ALL_PAGES }); + } +} diff --git a/src/android/PrintManager.java b/src/android/PrintManager.java new file mode 100644 index 0000000..68a9c7e --- /dev/null +++ b/src/android/PrintManager.java @@ -0,0 +1,167 @@ +/* + 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; + +import android.content.Context; +import android.graphics.Bitmap; +import android.print.PrintAttributes; +import android.print.PrintDocumentAdapter; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.print.PrintHelper; + +import org.json.JSONArray; +import org.json.JSONObject; + +import static android.content.Context.PRINT_SERVICE; + +class PrintManager { + + // The application context + private @NonNull Context context; + + /** + * Constructor + * + * @param context The context where to look for. + */ + PrintManager (@NonNull Context context) + { + this.context = context; + } + + /** + * If the print framework is able to render the referenced file. + * + * @param item Any kind of URL like file://, file:///, res:// or base64:// + * + * @return true if its able to render the content of the file. + */ + boolean canPrintItem (@Nullable String item) + { + boolean supported = PrintHelper.systemSupportsPrint(); + + if (item != null) + { + supported = new AssetUtil(context).open(item) != null; + } + + return supported; + } + + /** + * @return List of all printable document types (utis). + */ + static JSONArray getPrintableUTIs() + { + JSONArray utis = new JSONArray(); + + utis.put("com.adobe.pdf"); + utis.put("com.microsoft.bmp"); + utis.put("public.jpeg"); + utis.put("public.jpeg-2000"); + utis.put("public.png"); + utis.put("public.heif"); + utis.put("com.compuserve.gif"); + utis.put("com.microsoft.ico"); + utis.put("com.microsoft.bmp"); + utis.put("com.microsoft.bmp"); + + return utis; + } + + /** + * Sends the provided content to the printing controller and opens + * them. + * + * @param content The content or file to print. + * @param settings Additional settings how to render the content. + * @param callback The function to invoke once the job is done. + */ + void print (@Nullable String content, JSONObject settings, + @Nullable PrintHelper.OnPrintFinishCallback callback) + { + switch (AssetUtil.getContentType(content)) + { + case IMAGE: + printImage(content, settings, callback); + break; + case PDF: + printPdf(content, settings, callback); + break; + case HTML: + case SELF: + case PLAIN: + // TODO + } + } + + /** + * Prints the provided PDF document. + * + * @param path The path to the file to print. + * @param settings Additional settings how to render the content. + * @param callback The function to invoke once the job is done. + */ + private void printPdf (String path, JSONObject settings, + @Nullable PrintHelper.OnPrintFinishCallback callback) + { + Options options = new Options(settings); + String jobName = options.getJobName(); + PrintDocumentAdapter adapter = new PdfAdapter(path, context); + PrintAttributes attributes = options.toPrintAttributes(); + + getPrintService().print(jobName, adapter, attributes); + } + + /** + * Prints the specified image by file uri. + * + * @param path The path to the file to print. + * @param settings Additional settings how to render the content. + * @param callback The function to invoke once the job is done. + */ + private void printImage (String path, JSONObject settings, + @Nullable PrintHelper.OnPrintFinishCallback callback) + { + AssetUtil decoder = new AssetUtil(context); + Bitmap bitmap = decoder.decode(path); + + if (bitmap == null) return; + + Options options = new Options(settings); + PrintHelper printer = new PrintHelper(context); + String jobName = options.getJobName(); + + options.decoratePrintHelper(printer); + + printer.printBitmap(jobName, bitmap, callback); + } + + /** + * Returns the print service of the app. + */ + private @NonNull android.print.PrintManager getPrintService() + { + return (android.print.PrintManager) context.getSystemService(PRINT_SERVICE); + } +} diff --git a/src/android/Printer.java b/src/android/Printer.java index 54b115b..bd58ab1 100644 --- a/src/android/Printer.java +++ b/src/android/Printer.java @@ -21,83 +21,20 @@ package de.appplant.cordova.plugin.printer; -import android.app.Activity; -import android.content.Intent; -import android.os.Build; -import android.print.PrintAttributes; -import android.print.PrintDocumentAdapter; -import android.print.PrintJob; -import android.print.PrinterId; -import android.view.View; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.webkit.WebViewClient; +import android.support.annotation.Nullable; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaPlugin; -import org.apache.cordova.CordovaWebView; -import org.apache.cordova.CordovaInterface; import org.apache.cordova.PluginResult; import org.json.JSONArray; -import org.json.JSONException; import org.json.JSONObject; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.List; - -import de.appplant.cordova.plugin.printer.ext.PrintManager; -import de.appplant.cordova.plugin.printer.ext.PrintManager.OnPrintJobStateChangeListener; -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 android.print.PrintJobInfo.STATE_STARTED; -import static de.appplant.cordova.plugin.printer.ui.SelectPrinterActivity.ACTION_SELECT_PRINTER; -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 * the print adapter of that web view and initializes a print job. */ public class Printer extends CordovaPlugin { - CordovaWebView _webView; - - /** - * The web view that loads all the content. - */ - private WebView view; - - /** - * Reference is necessary to invoke the callback in the onresume event. - */ - private CallbackContext command; - - /** - * Instance of the print manager to listen for job status changes. - */ - private PrintManager pm; - - /** - * Invokes the callback once the job has reached a final state. - */ - private OnPrintJobStateChangeListener listener = new OnPrintJobStateChangeListener() { - /** - * Callback notifying that a print job state changed. - * - * @param job The print job. - */ - @Override - public void onPrintJobStateChanged(PrintJob job) { - notifyAboutPrintJobResult(job); - } - }; - - /** - * Default name of the printed document (PDF-Printer). - */ - private static final String DEFAULT_DOC_NAME = "unknown"; /** * Executes the request. @@ -112,298 +49,85 @@ public class Printer extends CordovaPlugin { * @param action The action to execute. * @param args The exec() arguments in JSON form. * @param callback The callback context used when calling back into JavaScript. + * * @return Whether the action was valid. */ @Override public boolean execute (String action, JSONArray args, - CallbackContext callback) throws JSONException { + CallbackContext callback) + { + boolean valid = true; - command = callback; - - if (action.equalsIgnoreCase("check")) { - check(); - return true; + if (action.equalsIgnoreCase("check")) + { + check(args.optString(0), callback); + } + else if (action.equalsIgnoreCase("utis")) + { + utis(callback); + } + else if (action.equalsIgnoreCase("print")) + { + print(args.optString(0), args.optJSONObject(1), callback); + } + else { + valid = false; } - if (action.equalsIgnoreCase("pick")) { - pick(); - return true; - } - - if (action.equalsIgnoreCase("print")) { - print(args); - return true; - } - - return false; + return valid; } /** - * Informs if the device is able to print documents. - * A Internet connection is required to load the cloud print dialog. + * If the print framework is able to render the referenced file. + * + * @param item Any kind of URL like file://, file:///, res:// or base64:// + * @param callback The plugin function to invoke with the result. */ - private void check () { - cordova.getThreadPool().execute(new Runnable() { - @Override - public void run() { - List services = pm.getEnabledPrintServices(); - Boolean available = services.size() > 0; + private void check (@Nullable String item, CallbackContext callback) + { + cordova.getThreadPool().execute(() -> { + PrintManager pm = new PrintManager(cordova.getContext()); + boolean printable = pm.canPrintItem(item); - PluginResult res1 = new PluginResult( - PluginResult.Status.OK, available); - PluginResult res2 = new PluginResult( - PluginResult.Status.OK, services.size()); - PluginResult res = new PluginResult( - PluginResult.Status.OK, Arrays.asList(res1, res2)); + PluginResult res = new PluginResult( + PluginResult.Status.OK, printable); - command.sendPluginResult(res); - } + callback.sendPluginResult(res); }); } /** - * Loads the HTML content into the web view and invokes the print manager. + * List of all printable document types (utis). * - * @param args - * The exec arguments as JSON + * @param callback The plugin function to invoke with the result. */ - private void print (final JSONArray args) { - final String content = args.optString(0); - final JSONObject props = args.optJSONObject(1); + private void utis (CallbackContext callback) + { + cordova.getThreadPool().execute(() -> { + JSONArray utis = PrintManager.getPrintableUTIs(); - cordova.getActivity().runOnUiThread( new Runnable() { - @Override - public void run() { - if( content.isEmpty() ) { - do_print((WebView)_webView.getView(), props); - } else { - initWebView(props); - loadContent(content); - } - } + PluginResult res = new PluginResult( + PluginResult.Status.OK, utis); + + callback.sendPluginResult(res); }); } /** - * 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. + * Sends the provided content to the printing controller and opens + * them. * - * @param content - * Either an HTML string or URI + * @param content The content or file to print. + * @param settings Additional settings how to render the content. + * @param callback The plugin function to invoke with the result. */ - private void loadContent(String content) { - if (content.startsWith("http") || content.startsWith("file:")) { - view.loadUrl(content); - } else { - String baseURL = webView.getUrl(); - baseURL = baseURL.substring(0, baseURL.lastIndexOf('/') + 1); - - // Set base URI to the assets/www folder - view.loadDataWithBaseURL( - baseURL, content, "text/html", "UTF-8", null); - } - } - - /** - * Configures the WebView components which will call the Google Cloud Print - * Service. - * - * @param props - * The JSON object with the containing page properties - */ - private void initWebView (JSONObject props) { - Activity ctx = cordova.getActivity(); - view = new WebView(ctx); - WebSettings settings = view.getSettings(); - final boolean jsEnabled = props.optBoolean("javascript", false); - - settings.setDatabaseEnabled(true); - settings.setGeolocationEnabled(true); - settings.setSaveFormData(true); - settings.setUseWideViewPort(true); - if (jsEnabled) { - settings.setJavaScriptEnabled(jsEnabled); - } - view.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY); - - if (Build.VERSION.SDK_INT >= 21) { - Method setMixedContentModeMethod = Meta.getMethod( - settings.getClass(), "setMixedContentMode", int.class); - - Meta.invokeMethod(settings, setMixedContentModeMethod, 2); - } - - setWebViewClient(props); - } - - private void do_print(WebView webView, JSONObject props) { - 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"); - - PrintAttributes.Builder builder = new PrintAttributes.Builder(); - PrintDocumentAdapter adapter = getAdapter(webView, docName); - - builder.setMinMargins(PrintAttributes.Margins.NO_MARGINS); - - builder.setColorMode(graystyle - ? PrintAttributes.COLOR_MODE_MONOCHROME - : PrintAttributes.COLOR_MODE_COLOR); - - builder.setMediaSize(landscape - ? PrintAttributes.MediaSize.UNKNOWN_LANDSCAPE - : PrintAttributes.MediaSize.UNKNOWN_PORTRAIT); - - if (!duplex.equals("none") && Build.VERSION.SDK_INT >= 23) { - boolean longEdge = duplex.equals("long"); - Method setDuplexModeMethod = Meta.getMethod( - builder.getClass(), "setDuplexMode", int.class); - - Meta.invokeMethod(builder, setDuplexModeMethod, - longEdge ? 2 : 4); - } - - pm.getInstance().print(docName, adapter, builder.build()); - } - - /** - * Creates the web view client which sets the print document. - * - * @param props - * The JSON object with the containing page properties - */ - private void setWebViewClient (JSONObject props) { - view.setWebViewClient(new WebViewClient() { - @Override - public boolean shouldOverrideUrlLoading (WebView view, String url) { - return false; - } - - @Override - public void onPageFinished (WebView webView, String url) { - do_print(webView, props); - view = null; - } + private void print (@Nullable String content, JSONObject settings, + CallbackContext callback) + { + cordova.getThreadPool().execute(() -> { + PrintManager pm = new PrintManager(cordova.getContext()); + pm.print(content, settings, callback::success); }); } - /** - * Invoke the callback send with `print` to inform about the result. - * - * @param job The print job. - */ - @SuppressWarnings("ConstantConditions") - private void notifyAboutPrintJobResult(PrintJob job) { - - if (job == null || command == null || - job.getInfo().getState() <= STATE_STARTED) { - return; - } - - PluginResult res = new PluginResult( - PluginResult.Status.OK, job.isCompleted()); - - command.sendPluginResult(res); - } - - /** - * 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 = Meta.getMethod( - WebView.class, "createPrintDocumentAdapter", String.class); - - return (PrintDocumentAdapter) Meta.invokeMethod( - webView, createPrintDocumentAdapterMethod, docName); - } else { - return (PrintDocumentAdapter) Meta.invokeMethod(webView, - "createPrintDocumentAdapter"); - } - } - - /** - * Called after plugin construction and fields have been initialized. - */ - @Override - protected void pluginInitialize() { - super.pluginInitialize(); - pm = new PrintManager(cordova.getActivity()); - pm.setOnPrintJobStateChangeListener(listener); - } - - @Override - public void initialize(CordovaInterface cordova, CordovaWebView webView) { - super.initialize(cordova, webView); - _webView = webView; - } - - /** - * The final call you receive before your activity is destroyed. - */ - @Override - public void onDestroy() { - if(pm != null && listener != null && command != null && view != null) { - pm.unsetOnPrintJobStateChangeListener(); - - pm = null; - listener = null; - command = null; - view = null; - - super.onDestroy(); - } - } - - /** - * Invoke the callback from `pick` method. - * - * @param requestCode The request code originally supplied to - * startActivityForResult(), allowing you to - * identify who this result came from. - * @param resultCode The integer result code returned by the child - * activity through its setResult(). - * @param intent An Intent, which can return result data to the - * caller (various data can be - */ - @Override - public void onActivityResult(int requestCode, int resultCode, Intent intent) { - super.onActivityResult(requestCode, resultCode, intent); - - if (command == null || intent == null) { - return; - } - - if (!intent.getAction().equals(ACTION_SELECT_PRINTER)) { - return; - } - - PrinterId printer = intent.getParcelableExtra(EXTRA_PRINTER_ID); - - PluginResult res = new PluginResult(PluginResult.Status.OK, - printer != null ? printer.getLocalId() : null); - - command.sendPluginResult(res); - } } diff --git a/src/android/ext/PrintManager.java b/src/android/ext/PrintManager.java deleted file mode 100644 index 1ef8322..0000000 --- a/src/android/ext/PrintManager.java +++ /dev/null @@ -1,257 +0,0 @@ -/* - 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 android.os.Build; -import android.print.PrintJob; -import android.print.PrintJobId; - -import java.lang.ref.WeakReference; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import de.appplant.cordova.plugin.printer.reflect.Meta; - -public final class PrintManager { - - public interface OnPrintJobStateChangeListener { - /** - * Callback notifying that a print job state changed. - * - * @param job The print job. - */ - void onPrintJobStateChanged(PrintJob job); - } - - /** - * Required to be able to register a listener of hidden interface. - */ - private class OnPrintJobStateChangeProxy implements InvocationHandler { - @Override - public Object invoke (Object o, Method method, Object[] objects) - throws Throwable { - - if (method.getName().equals("hashCode")) { - return listener.get().hashCode(); - } - - if (method.getName().equals("onPrintJobStateChanged")) { - notifyOnPrintJobStateChanged((PrintJobId) objects[0]); - return null; - } - - throw new Exception(); - } - } - - /** - * The application context. - */ - private WeakReference ctx; - - /** - * The registered listener for the state change event. - */ - private WeakReference listener; - - /** - * The proxy wrapper of the listener. - */ - private Object proxy; - - /** - * Constructor - * - * @param context The context where to look for. - */ - public PrintManager (Context context) { - this.ctx = new WeakReference(context); - } - - /** - * Get an instance from PrintManager service. - * - * @return A PrintManager instance. - */ - public final android.print.PrintManager getInstance () { - return (android.print.PrintManager) - ctx.get().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; - - if (Build.VERSION.SDK_INT < 24) { - printers = (List) Meta.invokeMethod(getInstance(), - "getInstalledPrintServices"); - } else { - Method getPrintServicesMethod = Meta.getMethod( - getInstance().getClass(), "getPrintServices", int.class); - - printers = (List) Meta.invokeMethod(getInstance(), - getPrintServicesMethod, 3); - } - - 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; - - if (Build.VERSION.SDK_INT < 24) { - printers = (List) Meta.invokeMethod(getInstance(), - "getEnabledPrintServices"); - } else { - Method getPrintServicesMethod = Meta.getMethod( - getInstance().getClass(), "getPrintServices", int.class); - - printers = (List) Meta.invokeMethod(getInstance(), - getPrintServicesMethod, 1); - } - - 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); - } - - /** - * Adds a listener for observing the state of print jobs. - * - * @param listener The listener to add. - */ - public void setOnPrintJobStateChangeListener( - OnPrintJobStateChangeListener listener) { - - if (this.listener == listener) - return; - - if (listener == null) { - unsetOnPrintJobStateChangeListener(); - return; - } - - Class interfaceCls = Meta.getClass( - "android.print.PrintManager$PrintJobStateChangeListener"); - - if (interfaceCls == null) - return; - - this.listener = - new WeakReference(listener); - - Method method = Meta.getMethod(getInstance().getClass(), - "addPrintJobStateChangeListener", interfaceCls); - - Class[] interfaces = {interfaceCls}; - - proxy = Proxy.newProxyInstance( - interfaceCls.getClassLoader(), - interfaces, - new OnPrintJobStateChangeProxy() - ); - - Meta.invokeMethod(getInstance(), method, proxy); - } - - /** - * Removes the listener from the observing the state of print jobs. - */ - public void unsetOnPrintJobStateChangeListener() { - Class interfaceCls = Meta.getClass( - "android.print.PrintManager$PrintJobStateChangeListener"); - - if (interfaceCls == null || proxy == null) - return; - - Method method = Meta.getMethod(getInstance().getClass(), - "removePrintJobStateChangeListener", interfaceCls); - - Meta.invokeMethod(getInstance(), method, proxy); - - proxy = null; - listener = null; - } - - /** - * Callback notifying that a print job state changed. - * - * @param printJobId The print job id. - */ - private void notifyOnPrintJobStateChanged(PrintJobId printJobId) { - if (listener != null && listener.get() != null) { - Method method = Meta.getMethod(getInstance().getClass(), - "getPrintJob", PrintJobId.class); - - PrintJob job = (PrintJob) Meta.invokeMethod(getInstance(), - method, printJobId); - - listener.get().onPrintJobStateChanged(job); - } - } - -} \ No newline at end of file diff --git a/src/android/ext/PrintServiceInfo.java b/src/android/ext/PrintServiceInfo.java deleted file mode 100644 index a32f7f3..0000000 --- a/src/android/ext/PrintServiceInfo.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - 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 accessibility service id. - * - * @return @return The id. - */ - public final String getId() { - return (String) Meta.invokeMethod(obj, "getId"); - } - - /** - * 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 deleted file mode 100644 index 7763e1d..0000000 --- a/src/android/ext/PrinterDiscoverySession.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - 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")) { - notifyOnPrintersChanged(); - return null; - } else throw new Exception(); - } - } - - /** - * 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 - */ - 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. - */ - private 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) { - - Object proxy = null; - Class interfaceCls = Meta.getClass( - "android.print.PrinterDiscoverySession$OnPrintersChangeListener"); - - if (interfaceCls == null) - return; - - this.listener = listener; - - 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() { - stopPrinterDiscovery(); - setOnPrintersChangeListener(null); - Meta.invokeMethod(session, "destroy"); - session = null; - } - - /** - * Get a list of all yet discovered printers. - * - * @return List of their basic infos - */ - @SuppressWarnings("unchecked") - private 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 deleted file mode 100644 index 83eeba9..0000000 --- a/src/android/reflect/Meta.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - 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 { - - /** - * Tries to find the class for the given name. - * - * @param fullName - * The full class name including the package scope. - * @return - * The found class or null. - */ - public static Class getClass (String fullName) { - try { - return Class.forName(fullName); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - return null; - } - } - - /** - * 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) { - e.printStackTrace(); - 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); - - if (resId == 0) { - resId = Resources.getSystem().getIdentifier(name, type, "android"); - } - - return resId; - } -} diff --git a/src/android/ui/SelectPrinterActivity.java b/src/android/ui/SelectPrinterActivity.java deleted file mode 100644 index f45a464..0000000 --- a/src/android/ui/SelectPrinterActivity.java +++ /dev/null @@ -1,361 +0,0 @@ -/* - 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.Build; -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"; - - /** - * The action of the intent. - */ - public static final String ACTION_SELECT_PRINTER = - "ACTION_SELECT_PRINTER"; - - /** - * Reference to the main view which lists all discovered printers. - */ - private ListView listView; - - /** - * Session for printer discovering within the network. - */ - private PrinterDiscoverySession session; - - /** - * 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); - - if (Build.VERSION.SDK_INT >= 21) { - setTheme(Meta.getResId( - this, "style", "Theme.Material.Settings")); - } - - setContentView(Meta.getResId( - this, "layout", "select_printer_activity")); - - session = 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() { - session.destroy(); - super.onDestroy(); - } - - /** - * Sent intent with empty result. - */ - @Override - public void onBackPressed() { - onPrinterSelected(null); - super.onBackPressed(); - } - - /** - * 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); - intent.setAction(ACTION_SELECT_PRINTER); - 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; - - session.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 (session.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() { - session.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; - } - } -}