Improvements and fixes for Android

This commit is contained in:
Sebastián Katzer 2019-01-29 12:22:20 +01:00
parent fc2bbeca53
commit 884e5c4862
8 changed files with 211 additions and 94 deletions

View File

@ -101,7 +101,10 @@
<framework src="com.android.support:support-v4:$ANDROID_SUPPORT_V4_VERSION"/> <framework src="com.android.support:support-v4:$ANDROID_SUPPORT_V4_VERSION"/>
<source-file src="src/android/AssetUtil.java" <source-file src="src/android/PrintAdapter.java"
target-dir="src/de/appplant/cordova/plugin/printer" />
<source-file src="src/android/PrintContent.java"
target-dir="src/de/appplant/cordova/plugin/printer" /> target-dir="src/de/appplant/cordova/plugin/printer" />
<source-file src="src/android/Printer.java" <source-file src="src/android/Printer.java"
@ -112,9 +115,6 @@
<source-file src="src/android/PrintOptions.java" <source-file src="src/android/PrintOptions.java"
target-dir="src/de/appplant/cordova/plugin/printer" /> target-dir="src/de/appplant/cordova/plugin/printer" />
<source-file src="src/android/PrintPdfAdapter.java"
target-dir="src/de/appplant/cordova/plugin/printer" />
</platform> </platform>
<!-- windows --> <!-- windows -->

View File

@ -19,7 +19,6 @@
package de.appplant.cordova.plugin.printer; package de.appplant.cordova.plugin.printer;
import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.os.CancellationSignal; import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
@ -28,6 +27,8 @@ import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter; import android.print.PrintDocumentAdapter;
import android.print.PrintDocumentInfo; import android.print.PrintDocumentInfo;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.print.PrintHelper;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
@ -39,23 +40,30 @@ import static android.print.PrintDocumentInfo.CONTENT_TYPE_DOCUMENT;
/** /**
* Document adapter to render and print PDF files. * Document adapter to render and print PDF files.
*/ */
class PrintPdfAdapter extends PrintDocumentAdapter { class PrintAdapter extends PrintDocumentAdapter {
// The application context // The name of the print job
private @NonNull Context context; private final @NonNull String jobName;
// The path to the PDF file // The input stream to render
private @NonNull String path; private final @NonNull InputStream input;
// The callback to inform once the job is done
private final @Nullable PrintHelper.OnPrintFinishCallback callback;
/** /**
* Constructor * Constructor
* *
* @param context The context where to look for. * @param jobName The name of the print job.
* @param input The input stream to render.
* @param callback The callback to inform once the job is done.
*/ */
PrintPdfAdapter (@NonNull String path, @NonNull Context context) PrintAdapter (@NonNull String jobName, @NonNull InputStream input,
@Nullable PrintHelper.OnPrintFinishCallback callback)
{ {
this.path = path; this.jobName = jobName;
this.context = context; this.input = input;
this.callback = callback;
} }
@Override @Override
@ -70,11 +78,13 @@ class PrintPdfAdapter extends PrintDocumentAdapter {
if (cancellationSignal.isCanceled()) if (cancellationSignal.isCanceled())
return; return;
pdi = new PrintDocumentInfo.Builder("test") pdi = new PrintDocumentInfo.Builder(jobName)
.setContentType(CONTENT_TYPE_DOCUMENT) .setContentType(CONTENT_TYPE_DOCUMENT)
.build(); .build();
callback.onLayoutFinished(pdi, true); boolean changed = !newAttributes.equals(oldAttributes);
callback.onLayoutFinished(pdi, changed);
} }
@Override @Override
@ -86,18 +96,10 @@ class PrintPdfAdapter extends PrintDocumentAdapter {
if (cancellationSignal.isCanceled()) if (cancellationSignal.isCanceled())
return; return;
AssetUtil io = new AssetUtil(context); OutputStream output = new FileOutputStream(dest.getFileDescriptor());
InputStream in = io.open(path);
if (in == null) {
callback.onWriteFailed("File not found: " + path);
return;
}
OutputStream out = new FileOutputStream(dest.getFileDescriptor());
try { try {
AssetUtil.copy(in, out); PrintContent.copy(input, output);
} catch (IOException e) { } catch (IOException e) {
callback.onWriteFailed(e.getMessage()); callback.onWriteFailed(e.getMessage());
return; return;
@ -105,4 +107,19 @@ class PrintPdfAdapter extends PrintDocumentAdapter {
callback.onWriteFinished(new PageRange[]{ PageRange.ALL_PAGES }); callback.onWriteFinished(new PageRange[]{ PageRange.ALL_PAGES });
} }
/**
* Close input stream once the printing is done.
*/
@Override
public void onFinish ()
{
super.onFinish();
PrintContent.close(input);
if (callback != null) {
callback.onFinish();
}
}
} }

View File

@ -29,16 +29,18 @@ import android.support.annotation.Nullable;
import android.util.Base64; import android.util.Base64;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.URLConnection;
class AssetUtil { class PrintContent {
// List of supported content types // List of supported content types
enum ContentType { PLAIN, HTML, IMAGE, PDF, SELF } enum ContentType { PLAIN, HTML, IMAGE, PDF, UNSUPPORTED }
// Application context // Application context
private final @NonNull Context context; private final @NonNull Context context;
@ -48,8 +50,8 @@ class AssetUtil {
* *
* @param ctx The application context. * @param ctx The application context.
*/ */
AssetUtil (@NonNull Context ctx) { private PrintContent (@NonNull Context ctx) {
this.context = ctx; context = ctx;
} }
/** /**
@ -59,21 +61,60 @@ class AssetUtil {
* *
* @return The content type even the file does not exist. * @return The content type even the file does not exist.
*/ */
static @NonNull ContentType getContentType (@Nullable String path) @NonNull
static ContentType getContentType (@Nullable String path,
@NonNull Context context)
{
return new PrintContent(context).getContentType(path);
}
/**
* 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.
*/
@NonNull
private ContentType getContentType (@Nullable String path)
{ {
ContentType type = ContentType.PLAIN; ContentType type = ContentType.PLAIN;
if (path == null || path.isEmpty()) if (path == null || path.isEmpty() || path.charAt(0) == '<')
{
type = ContentType.SELF;
}
else if (path.charAt(0) == '<')
{ {
type = ContentType.HTML; type = ContentType.HTML;
} }
else if (path.matches("^[a-z]+://.+")) else if (path.matches("^[a-z0-9]+://.+"))
{ {
return path.endsWith(".pdf") ? ContentType.PDF : ContentType.IMAGE; String mime;
if (path.startsWith("base64:")) {
try {
mime = URLConnection.guessContentTypeFromStream(openBase64(path));
} catch (IOException e) {
return ContentType.UNSUPPORTED;
}
} else {
mime = URLConnection.guessContentTypeFromName(path);
}
switch (mime)
{
case "image/bmp":
case "image/png":
case "image/jpeg":
case "image/jpeg2000":
case "image/jp2":
case "image/gif":
case "image/x-icon":
case "image/vnd.microsoft.icon":
case "image/heif":
return ContentType.IMAGE;
case "application/pdf":
return ContentType.PDF;
default:
return ContentType.UNSUPPORTED;
}
} }
return type; return type;
@ -83,10 +124,25 @@ class AssetUtil {
* Opens a file://, res:// or base64:// Uri as a stream. * Opens a file://, res:// or base64:// Uri as a stream.
* *
* @param path The file path to decode. * @param path The file path to decode.
* @param context The application context.
* *
* @return An open IO stream or null if the file does not exist. * @return An open IO stream or null if the file does not exist.
*/ */
@Nullable InputStream open (@NonNull String path) @Nullable
static InputStream open (@NonNull String path, @NonNull Context context)
{
return new PrintContent(context).open(path);
}
/**
* 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
private InputStream open (@NonNull String path)
{ {
InputStream stream = null; InputStream stream = null;
@ -110,6 +166,20 @@ class AssetUtil {
return stream; return stream;
} }
/**
* Decodes a file://, res:// or base64:// Uri to bitmap.
*
* @param path The file path to decode.
* @param context The application context.
*
* @return A bitmap or null if the path is not valid
*/
@Nullable
static Bitmap decode (@NonNull String path, @NonNull Context context)
{
return new PrintContent(context).decode(path);
}
/** /**
* Decodes a file://, res:// or base64:// Uri to bitmap. * Decodes a file://, res:// or base64:// Uri to bitmap.
* *
@ -117,7 +187,8 @@ class AssetUtil {
* *
* @return A bitmap or null if the path is not valid * @return A bitmap or null if the path is not valid
*/ */
@Nullable Bitmap decode (@NonNull String path) @Nullable
private Bitmap decode (@NonNull String path)
{ {
Bitmap bitmap; Bitmap bitmap;
@ -150,7 +221,8 @@ class AssetUtil {
* @param input The readable input stream. * @param input The readable input stream.
* @param output The writable output stream. * @param output The writable output stream.
* *
* @throws IOException * @throws IOException If the input stream is not readable,
* or the output stream is not writable.
*/ */
static void copy (@NonNull InputStream input, static void copy (@NonNull InputStream input,
@NonNull OutputStream output) throws IOException @NonNull OutputStream output) throws IOException
@ -162,8 +234,22 @@ class AssetUtil {
output.write(buf, 0, bytesRead); output.write(buf, 0, bytesRead);
} }
output.close(); input.reset();
input.close(); close(output);
}
/**
* Closes the stream.
*
* @param stream The stream to close.
*/
static void close (@NonNull Closeable stream)
{
try {
stream.close();
} catch (IOException e) {
// ignore
}
} }
/** /**
@ -173,7 +259,8 @@ class AssetUtil {
* *
* @return An open IO stream or null if the file does not exist. * @return An open IO stream or null if the file does not exist.
*/ */
private @Nullable InputStream openFile (@NonNull String path) @Nullable
private InputStream openFile (@NonNull String path)
{ {
String absPath = path.substring(7); String absPath = path.substring(7);
@ -191,7 +278,8 @@ class AssetUtil {
* *
* @return A bitmap or null if the path is not valid * @return A bitmap or null if the path is not valid
*/ */
private @Nullable Bitmap decodeFile (@NonNull String path) @Nullable
private Bitmap decodeFile (@NonNull String path)
{ {
String absPath = path.substring(7); String absPath = path.substring(7);
@ -205,7 +293,8 @@ class AssetUtil {
* *
* @return An open IO stream or null if the file does not exist. * @return An open IO stream or null if the file does not exist.
*/ */
private @Nullable InputStream openAsset (@NonNull String path) @Nullable
private InputStream openAsset (@NonNull String path)
{ {
String resPath = path.replaceFirst("file:/", "www"); String resPath = path.replaceFirst("file:/", "www");
@ -223,7 +312,8 @@ class AssetUtil {
* *
* @return A bitmap or null if the path is not valid * @return A bitmap or null if the path is not valid
*/ */
private @Nullable Bitmap decodeAsset (@NonNull String path) @Nullable
private Bitmap decodeAsset (@NonNull String path)
{ {
InputStream stream = openAsset(path); InputStream stream = openAsset(path);
Bitmap bitmap; Bitmap bitmap;
@ -233,11 +323,7 @@ class AssetUtil {
bitmap = BitmapFactory.decodeStream(stream); bitmap = BitmapFactory.decodeStream(stream);
try { close(stream);
stream.close();
} catch (IOException e) {
// ignore
}
return bitmap; return bitmap;
} }
@ -249,7 +335,8 @@ class AssetUtil {
* *
* @return An open IO stream or null if the file does not exist. * @return An open IO stream or null if the file does not exist.
*/ */
private @NonNull InputStream openResource (@NonNull String path) @NonNull
private InputStream openResource (@NonNull String path)
{ {
String resPath = path.substring(6); String resPath = path.substring(6);
int resId = getResId(resPath); int resId = getResId(resPath);
@ -264,7 +351,8 @@ class AssetUtil {
* *
* @return A bitmap or null if the path is not valid * @return A bitmap or null if the path is not valid
*/ */
private @Nullable Bitmap decodeResource (@NonNull String path) @Nullable
private Bitmap decodeResource (@NonNull String path)
{ {
String data = path.substring(9); String data = path.substring(9);
byte[] bytes = Base64.decode(data, 0); byte[] bytes = Base64.decode(data, 0);
@ -279,7 +367,8 @@ class AssetUtil {
* *
* @return An open IO stream or null if the file does not exist. * @return An open IO stream or null if the file does not exist.
*/ */
private @NonNull InputStream openBase64 (@NonNull String path) @NonNull
private InputStream openBase64 (@NonNull String path)
{ {
String data = path.substring(9); String data = path.substring(9);
byte[] bytes = Base64.decode(data, 0); byte[] bytes = Base64.decode(data, 0);
@ -294,7 +383,8 @@ class AssetUtil {
* *
* @return A bitmap or null if the path is not valid * @return A bitmap or null if the path is not valid
*/ */
private @Nullable Bitmap decodeBase64 (@NonNull String path) @Nullable
private Bitmap decodeBase64 (@NonNull String path)
{ {
String data = path.substring(9); String data = path.substring(9);
byte[] bytes = Base64.decode(data, 0); byte[] bytes = Base64.decode(data, 0);
@ -333,10 +423,16 @@ class AssetUtil {
return resId; return resId;
} }
/**
* Returns the asset manager for the app.
*/
private AssetManager getAssets() { private AssetManager getAssets() {
return context.getAssets(); return context.getAssets();
} }
/**
* Returns the resource bundle for the app.
*/
private Resources getResources() { private Resources getResources() {
return context.getResources(); return context.getResources();
} }

View File

@ -31,12 +31,15 @@ import android.support.v4.print.PrintHelper;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.InputStream;
import static android.content.Context.PRINT_SERVICE; import static android.content.Context.PRINT_SERVICE;
import static de.appplant.cordova.plugin.printer.PrintContent.ContentType.UNSUPPORTED;
class PrintManager { class PrintManager {
// The application context // The application context
private @NonNull Context context; private final @NonNull Context context;
/** /**
* Constructor * Constructor
@ -61,15 +64,16 @@ class PrintManager {
if (item != null) if (item != null)
{ {
supported = new AssetUtil(context).open(item) != null; supported = PrintContent.getContentType(item, context) != UNSUPPORTED;
} }
return supported; return supported;
} }
/** /**
* @return List of all printable document types (utis). * List of all printable document types (utis).
*/ */
@NonNull
static JSONArray getPrintableUTIs() static JSONArray getPrintableUTIs()
{ {
JSONArray utis = new JSONArray(); JSONArray utis = new JSONArray();
@ -82,8 +86,6 @@ class PrintManager {
utis.put("public.heif"); utis.put("public.heif");
utis.put("com.compuserve.gif"); utis.put("com.compuserve.gif");
utis.put("com.microsoft.ico"); utis.put("com.microsoft.ico");
utis.put("com.microsoft.bmp");
utis.put("com.microsoft.bmp");
return utis; return utis;
} }
@ -96,19 +98,20 @@ class PrintManager {
* @param settings Additional settings how to render the content. * @param settings Additional settings how to render the content.
* @param callback The function to invoke once the job is done. * @param callback The function to invoke once the job is done.
*/ */
void print (@Nullable String content, JSONObject settings, void print (@Nullable String content, @NonNull JSONObject settings,
@Nullable PrintHelper.OnPrintFinishCallback callback) @Nullable PrintHelper.OnPrintFinishCallback callback)
{ {
switch (AssetUtil.getContentType(content)) switch (PrintContent.getContentType(content, context))
{ {
case IMAGE: case IMAGE:
//noinspection ConstantConditions
printImage(content, settings, callback); printImage(content, settings, callback);
break; break;
case PDF: case PDF:
//noinspection ConstantConditions
printPdf(content, settings, callback); printPdf(content, settings, callback);
break; break;
case HTML: case HTML:
case SELF:
case PLAIN: case PLAIN:
// TODO // TODO
} }
@ -121,15 +124,19 @@ class PrintManager {
* @param settings Additional settings how to render the content. * @param settings Additional settings how to render the content.
* @param callback The function to invoke once the job is done. * @param callback The function to invoke once the job is done.
*/ */
private void printPdf (String path, JSONObject settings, private void printPdf (@NonNull String path, @NonNull JSONObject settings,
@Nullable PrintHelper.OnPrintFinishCallback callback) @Nullable PrintHelper.OnPrintFinishCallback callback)
{ {
PrintOptions options = new PrintOptions(settings); InputStream stream = PrintContent.open(path, context);
String jobName = options.getJobName();
PrintPdfAdapter adapter = new PrintPdfAdapter(path, context);
PrintAttributes attributes = options.toPrintAttributes();
getPrintService().print(jobName, adapter, attributes); if (stream == null) return;
PrintOptions options = new PrintOptions(settings);
String jobName = options.getJobName();
PrintAdapter adapter = new PrintAdapter(jobName, stream, callback);
PrintAttributes attrs = options.toPrintAttributes();
getPrintService().print(jobName, adapter, attrs);
} }
/** /**
@ -139,11 +146,10 @@ class PrintManager {
* @param settings Additional settings how to render the content. * @param settings Additional settings how to render the content.
* @param callback The function to invoke once the job is done. * @param callback The function to invoke once the job is done.
*/ */
private void printImage (String path, JSONObject settings, private void printImage (@NonNull String path, @NonNull JSONObject settings,
@Nullable PrintHelper.OnPrintFinishCallback callback) @Nullable PrintHelper.OnPrintFinishCallback callback)
{ {
AssetUtil decoder = new AssetUtil(context); Bitmap bitmap = PrintContent.decode(path, context);
Bitmap bitmap = decoder.decode(path);
if (bitmap == null) return; if (bitmap == null) return;
@ -159,7 +165,8 @@ class PrintManager {
/** /**
* Returns the print service of the app. * Returns the print service of the app.
*/ */
private @NonNull android.print.PrintManager getPrintService() @NonNull
private android.print.PrintManager getPrintService()
{ {
return (android.print.PrintManager) context.getSystemService(PRINT_SERVICE); return (android.print.PrintManager) context.getSystemService(PRINT_SERVICE);
} }

View File

@ -43,7 +43,7 @@ import static android.support.v4.print.PrintHelper.SCALE_MODE_FIT;
class PrintOptions { class PrintOptions {
// The print job settings // The print job settings
private @NonNull JSONObject spec; private final @NonNull JSONObject spec;
/** /**
* Constructor * Constructor
@ -140,9 +140,9 @@ class PrintOptions {
break; break;
} }
if (spec.has("graystyle")) if (spec.has("monochrome"))
{ {
if (spec.optBoolean("graystyle")) if (spec.optBoolean("monochrome"))
{ {
printer.setColorMode(PrintHelper.COLOR_MODE_MONOCHROME); printer.setColorMode(PrintHelper.COLOR_MODE_MONOCHROME);
} }
@ -152,16 +152,13 @@ class PrintOptions {
} }
} }
if (spec.has("autoFit")) if (spec.optBoolean("autoFit", true))
{ {
if (spec.optBoolean("autoFit")) printer.setScaleMode(SCALE_MODE_FIT);
{ }
printer.setScaleMode(SCALE_MODE_FIT); else
} {
else printer.setScaleMode(SCALE_MODE_FILL);
{
printer.setScaleMode(SCALE_MODE_FILL);
}
} }
} }
} }

View File

@ -38,7 +38,7 @@
info.orientation = UIPrintInfoOrientationLandscape; info.orientation = UIPrintInfoOrientationLandscape;
} }
if ([spec[@"graystyle"] boolValue]) if ([spec[@"monochrome"] boolValue])
{ {
if ([spec[@"photo"] boolValue]) if ([spec[@"photo"] boolValue])
{ {

View File

@ -93,7 +93,7 @@ exports.onPrintTaskRequested = function (event) {
args.setSource(exports._page); args.setSource(exports._page);
}); });
if (config.graystyle) { if (config.monochrome) {
task.options.colorMode = Printing.PrintColorMode.grayscale; task.options.colorMode = Printing.PrintColorMode.grayscale;
} else { } else {
task.options.colorMode = Printing.PrintColorMode.color; task.options.colorMode = Printing.PrintColorMode.color;

View File

@ -23,12 +23,12 @@ var exec = require('cordova/exec'),
// Defaults // Defaults
exports._defaults = { exports._defaults = {
// name: 'unknown', // name: 'unknown',
// duplex: 'none', // duplex: 'none',
// landscape: false, // landscape: false,
// graystyle: false, // monochrome: false,
// border: true, // border: true,
// copies: 1, // copies: 1,
ui: { ui: {
hideNumberOfCopies: false, hideNumberOfCopies: false,