Java tutorial
/* Copyright 2013 appPlant UG 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. */ /* iText 4.2 License: MPL/LGPL see: https://github.com/ymasory/iText-4.2.0 */ /* Enhancements (Pretty Print on Android < 4.4) - 2014 Modern Alchemists OG License: http://www.apache.org/licenses/LICENSE-2.0 */ package de.appplant.cordova.plugin.printer; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaPlugin; import org.apache.cordova.PluginResult; import org.json.JSONArray; import org.json.JSONException; import com.lowagie.text.Document; import com.lowagie.text.DocumentException; import com.lowagie.text.Element; import com.lowagie.text.Image; import com.lowagie.text.PageSize; import com.lowagie.text.pdf.PdfWriter; import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Picture; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.os.Handler; import android.print.PrintAttributes; import android.print.PrintDocumentAdapter; import android.print.PrintManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.LinearLayout.LayoutParams; import android.widget.RelativeLayout; import android.util.Log; @TargetApi(19) public class Printer extends CordovaPlugin { private CallbackContext ctx; private Intent printIntent; // change your path on the sdcard here private String publicTmpDir = ".de.appplant.cordova.plugin.printer"; // prepending the dot "." will make it hidden private String printTitle = "print"; private final String LOG_TAG = "PRINT"; // set to true to see the webview (useful for debugging) private final boolean showWebViewForDebugging = false; /** * List of print App ids. */ private String printAppIds[] = { "kr.co.iconlab.BasicPrintingProfile", // Bluetooth Smart Printing "com.blueslib.android.app", // Bluetooth SPP Printer API "com.brother.mfc.brprint", // Brother iPrint&Scan "com.brother.ptouch.sdk", // Brother Print Library "jp.co.canon.bsd.android.aepp.activity", // Canon Easy-PhotoPrint "com.pauloslf.cloudprint", // Cloud Print "com.dlnapr1.printer", // CMC DLNA Print Client "com.dell.mobileprint", // Dell Mobile Print "com.printjinni.app.print", // PrintJinni "epson.print", // Epson iPrint "jp.co.fujixerox.prt.PrintUtil.PCL", // Fuji Xerox Print Utility "jp.co.fujixerox.prt.PrintUtil.Karin", // Fuji Xeros Print&Scan (S) "com.hp.android.print", // HP ePrint" "com.hp.android.print "com.blackspruce.lpd", // Let's Print Droid "com.threebirds.notesprint", // NotesPrint print your notes "com.xerox.mobileprint", // Print Portal (Xerox) "com.zebra.kdu", // Print Station (Zebra) "net.jsecurity.printbot", // PrintBot "com.sec.print.mobileprint", // Samsung Mobile Print "com.dynamixsoftware.printhand", // PrintHand Mobile Print "com.dynamixsoftware.printhand.premium", // PrintHand Mobile Print Premium "com.rcreations.send2printer", // Send 2 Printer "com.ivc.starprint", // StarPrint "com.threebirds.easyviewer", // WiFi Print "com.woosim.android.print", // Woosim BT printer "com.woosim.bt.app", // WoosimPrinter "com.zebra.android.zebrautilities", // Zebra Utilities }; @Override public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { // Lets check if a print service is available if (action.equalsIgnoreCase("isServiceAvailable")) { isServiceAvailable(callbackContext); return true; } // Lets print something if (action.equalsIgnoreCase("print")) { print(args, callbackContext); return true; } // Returning false results in a "MethodNotFound" error. return false; } /** * Checks if a print service is available. */ private void isServiceAvailable(CallbackContext ctx) { Boolean supported = false; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { supported = true; } else { supported = (this.getFirstInstalledAppId() != null); } PluginResult result = new PluginResult(PluginResult.Status.OK, supported); ctx.sendPluginResult(result); } /** * Prints the html content. */ private void print(final JSONArray args, CallbackContext ctx) { final Printer self = this; final String content = args.optString(0, "<html></html>"); this.ctx = ctx; if (showWebViewForDebugging) { Log.v(LOG_TAG, "java print html called"); Log.v(LOG_TAG, "Html start:" + content.substring(0, 500)); Log.v(LOG_TAG, "Html end:" + content.substring(content.length() - 500)); } cordova.getActivity().runOnUiThread(new Runnable() { public void run() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) // Android 4.4 { /* * None-Kitkat printing (Android < 4.4) */ // print with the help of a third party app String appId = self.getFirstInstalledAppId(); if (appId != null) { Log.v(LOG_TAG, "Print app found: " + appId); self.printIntent = self.getPrintController(appId); self.loadContentIntoPrintController(content, self.printIntent); } else { Log.v(LOG_TAG, "Error: No print app found."); PluginResult result = new PluginResult(PluginResult.Status.ERROR, "No print app found."); self.ctx.sendPluginResult(result); } } else { /* * Kitkat printing (Android >= 4.4) */ // Create a WebView object specifically for printing WebView page = new WebView(cordova.getActivity()); page.getSettings().setJavaScriptEnabled(false); page.setDrawingCacheEnabled(true); // Auto-scale the content to the webview's width. page.getSettings().setLoadWithOverviewMode(true); page.getSettings().setUseWideViewPort(true); page.setInitialScale(0); // Disable android text auto fit behaviour page.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL); // show if debug if (showWebViewForDebugging) { page.setVisibility(View.VISIBLE); } else { page.setVisibility(View.INVISIBLE); } page.setWebViewClient(new WebViewClient() { public boolean shouldOverrideUrlLoading(WebView view, String url) { return false; } @Override public void onPageFinished(WebView view, String url) { // Get a PrintManager instance PrintManager printManager = (PrintManager) self.cordova.getActivity() .getSystemService(Context.PRINT_SERVICE); // Get a print adapter instance PrintDocumentAdapter printAdapter = view.createPrintDocumentAdapter(); // Get a print builder attributes instance PrintAttributes.Builder builder = new PrintAttributes.Builder(); builder.setMinMargins(PrintAttributes.Margins.NO_MARGINS); // builder.setMediaSize(PrintAttributes.MediaSize.ISO_A4); // builder.setColorMode(PrintAttributes.COLOR_MODE_COLOR); // builder.setResolution(new PrintAttributes.Resolution("default", "print", 600, 600)); // send success result to cordova PluginResult result = new PluginResult(PluginResult.Status.OK); result.setKeepCallback(false); self.ctx.sendPluginResult(result); // Create & send a print job printManager.print(self.printTitle, printAdapter, builder.build()); } }); // Reverse engineer base url (assets/www) from the cordova webView url String baseURL = self.webView.getUrl(); baseURL = baseURL.substring(0, baseURL.lastIndexOf('/') + 1); // show if debug if (showWebViewForDebugging) { cordova.getActivity().addContentView(page, new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); } // Load content into the print webview page.loadDataWithBaseURL(baseURL, content, "text/html", "utf-8", null); } } }); // send "no-result" result PluginResult pluginResult = new PluginResult(PluginResult.Status.NO_RESULT); pluginResult.setKeepCallback(true); ctx.sendPluginResult(pluginResult); } /** * Check if an app is installed. */ private boolean isAppInstalled(String appId) { PackageManager pm = cordova.getActivity().getPackageManager(); try { PackageInfo pi = pm.getPackageInfo(appId, 0); if (pi != null) { return true; } } catch (PackageManager.NameNotFoundException e) { } return false; } /** * Get the first installed print app. */ private String getFirstInstalledAppId() { for (int i = 0; i < printAppIds.length; i++) { String appId = printAppIds[i]; Boolean isInstalled = this.isAppInstalled(appId); if (isInstalled) { return appId; } } return null; } /** * Creates the print intent (aka print controller). */ private Intent getPrintController(String appId) { String intentId = "android.intent.action.SEND"; if (appId.equals("com.rcreations.send2printer")) { intentId = "com.rcreations.send2printer.print"; } else if (appId.equals("com.dynamixsoftware.printershare")) { intentId = "android.intent.action.VIEW"; } else if (appId.equals("com.hp.android.print")) { intentId = "org.androidprinting.intent.action.PRINT"; } Intent intent = new Intent(intentId); if (appId != null) intent.setPackage(appId); String mimeType = "application/pdf"; // Check for special cases that can receive HTML if (appId.equals("com.rcreations.send2printer") || appId.equals("com.dynamixsoftware.printershare")) { mimeType = "text/html"; } intent.setType(mimeType); return intent; } /** * Loads the content into the print controller. Either as pdf file or plain html. */ private void loadContentIntoPrintController(String content, Intent intent) { String mimeType = intent.getType(); if (mimeType.equals("text/html")) { loadContentAsHtmlIntoPrintController(content, intent); } else { loadContentAsPdfIntoPrintController(content, intent); } } /** * Loads the content into the print intents "EXTRA_TEXT" and calls startPrinterApp() once its done. */ private void loadContentAsHtmlIntoPrintController(String content, Intent intent) { // Add html text to the intent intent.putExtra(Intent.EXTRA_TEXT, content); // start the print app (trigger the print intent) startPrinterApp(this.printIntent); } /** * Loads the html content into a WebView, saves it as a single multi page pdf file and * calls startPrinterApp() once its done. */ private void loadContentAsPdfIntoPrintController(String content, final Intent intent) { Activity activity = cordova.getActivity(); final WebView page = new Html2PdfWebView(activity); final Printer self = this; // show if debug if (showWebViewForDebugging) { page.setVisibility(View.VISIBLE); } else { page.setVisibility(View.INVISIBLE); } page.getSettings().setJavaScriptEnabled(false); page.setDrawingCacheEnabled(true); // Dont auto-scale the content to the webview's width. page.getSettings().setLoadWithOverviewMode(false); page.getSettings().setUseWideViewPort(false); page.setInitialScale(100); // Disable android text auto fit behaviour page.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL); page.setWebViewClient(new WebViewClient() { @Override public void onPageFinished(final WebView page, String url) { new Handler().postDelayed(new Runnable() { @Override public void run() { // slice the web screenshot into pages and save as pdf Bitmap b = getWebViewAsBitmap(page); if (b != null) { File tmpFile = self.saveWebViewAsPdf(b); // add pdf as stream to the print intent intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(tmpFile)); // remove the webview if (!self.showWebViewForDebugging) { ViewGroup vg = (ViewGroup) (page.getParent()); vg.removeView(page); } // start the print app (trigger the print intent) self.startPrinterApp(self.printIntent); } } }, 500); } }); // Set base URI to the assets/www folder String baseURL = webView.getUrl(); baseURL = baseURL.substring(0, baseURL.lastIndexOf('/') + 1); /** We make it this small on purpose (is resized after page load has finished). * Making it small in the beginning has some effects on the html <body> (body * width will always remain 100 if not set explicitly). */ if (!showWebViewForDebugging) { activity.addContentView(page, new ViewGroup.LayoutParams(100, 100)); } else { activity.addContentView(page, new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); } page.loadDataWithBaseURL(baseURL, content, "text/html", "utf-8", null); } /** * Start the printer app (sends the printer intent). */ private void startPrinterApp(Intent intent) { // return success answer to cordova PluginResult result = new PluginResult(PluginResult.Status.OK, true); result.setKeepCallback(false); ctx.sendPluginResult(result); // start intent cordova.startActivityForResult(this, intent, 0); } /** * Takes a WebView and returns a Bitmap representation of it (takes a "screenshot"). * @param WebView * @return Bitmap */ Bitmap getWebViewAsBitmap(WebView view) { Bitmap b; // prepare drawing cache view.setDrawingCacheEnabled(true); view.buildDrawingCache(); //Get the dimensions of the view so we can re-layout the view at its current size //and create a bitmap of the same size int width = ((Html2PdfWebView) view).getContentWidth(); int height = view.getContentHeight(); if (width == 0 || height == 0) { // return error answer to cordova String msg = "Width or height of webview content is 0. Webview to bitmap conversion failed."; Log.e(LOG_TAG, msg); PluginResult result = new PluginResult(PluginResult.Status.ERROR, msg); result.setKeepCallback(false); ctx.sendPluginResult(result); return null; } Log.v(LOG_TAG, "Print.getWebViewAsBitmap -> Content width: " + width + ", height: " + height); //Cause the view to re-layout view.measure(width, height); view.layout(0, 0, width, height); //Create a bitmap backed Canvas to draw the view into b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas c = new Canvas(b); // draw the view into the canvas view.draw(c); return b; } /** * Slices the screenshot into pages, merges those into a single pdf * and saves it in the public accessible /sdcard dir. */ private File saveWebViewAsPdf(Bitmap screenshot) { try { File sdCard = Environment.getExternalStorageDirectory(); File dir = new File(sdCard.getAbsolutePath() + "/" + this.publicTmpDir + "/"); dir.mkdirs(); File file; FileOutputStream stream; double pageWidth = PageSize.A4.getWidth() * 0.85; // width of the image is 85% of the page double pageHeight = PageSize.A4.getHeight() * 0.80; // max height of the image is 80% of the page double pageHeightToWithRelation = pageHeight / pageWidth; // e.g.: 1.33 (4/3) Bitmap currPage; int totalSize = screenshot.getHeight(); int currPos = 0; int currPageCount = 0; int sliceWidth = screenshot.getWidth(); int sliceHeight = (int) Math.round(sliceWidth * pageHeightToWithRelation); while (totalSize > currPos && currPageCount < 100) // max 100 pages { currPageCount++; Log.v(LOG_TAG, "Creating page nr. " + currPageCount); // slice bitmap currPage = Bitmap.createBitmap(screenshot, 0, currPos, sliceWidth, (int) Math.min(sliceHeight, totalSize - currPos)); // save page as png stream = new FileOutputStream(new File(dir, "print-page-" + currPageCount + ".png")); currPage.compress(Bitmap.CompressFormat.PNG, 100, stream); stream.close(); // move current position indicator currPos += sliceHeight; } // create pdf Log.v(LOG_TAG, "Creating pdf"); Document document = new Document(); File filePdf = new File(dir, this.printTitle + ".pdf"); // change the output name of the pdf here PdfWriter.getInstance(document, new FileOutputStream(filePdf)); document.open(); for (int i = 1; i <= currPageCount; ++i) { Log.v(LOG_TAG, "Adding page nr. " + i + " to the pdf file."); file = new File(dir, "print-page-" + i + ".png"); Image image = Image.getInstance(file.getAbsolutePath()); image.scaleToFit((float) pageWidth, 9999); image.setAlignment(Element.ALIGN_CENTER); document.add(image); document.newPage(); } document.close(); // delete tmp image files for (int i = 1; i <= currPageCount; ++i) { file = new File(dir, "print-page-" + i + ".png"); file.delete(); } return filePdf; } catch (IOException e) { Log.e(LOG_TAG, "ERROR: " + e.getMessage()); e.printStackTrace(); // return error answer to cordova PluginResult result = new PluginResult(PluginResult.Status.ERROR, e.getMessage()); result.setKeepCallback(false); ctx.sendPluginResult(result); } catch (DocumentException e) { Log.e(LOG_TAG, "ERROR: " + e.getMessage()); e.printStackTrace(); // return error answer to cordova PluginResult result = new PluginResult(PluginResult.Status.ERROR, e.getMessage()); result.setKeepCallback(false); ctx.sendPluginResult(result); } Log.e(LOG_TAG, "Uncaught ERROR!"); return null; } } class Html2PdfWebView extends WebView { public Html2PdfWebView(Context context) { super(context); } public int getContentWidth() { return this.computeHorizontalScrollRange(); } }