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"/>
<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" />
<source-file src="src/android/Printer.java"
@ -112,9 +115,6 @@
<source-file src="src/android/PrintOptions.java"
target-dir="src/de/appplant/cordova/plugin/printer" />
<source-file src="src/android/PrintPdfAdapter.java"
target-dir="src/de/appplant/cordova/plugin/printer" />
</platform>
<!-- windows -->

View File

@ -19,7 +19,6 @@
package de.appplant.cordova.plugin.printer;
import android.content.Context;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
@ -28,6 +27,8 @@ import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter;
import android.print.PrintDocumentInfo;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.print.PrintHelper;
import java.io.FileOutputStream;
import java.io.IOException;
@ -39,23 +40,30 @@ import static android.print.PrintDocumentInfo.CONTENT_TYPE_DOCUMENT;
/**
* Document adapter to render and print PDF files.
*/
class PrintPdfAdapter extends PrintDocumentAdapter {
class PrintAdapter extends PrintDocumentAdapter {
// The application context
private @NonNull Context context;
// The name of the print job
private final @NonNull String jobName;
// The path to the PDF file
private @NonNull String path;
// The input stream to render
private final @NonNull InputStream input;
// The callback to inform once the job is done
private final @Nullable PrintHelper.OnPrintFinishCallback callback;
/**
* 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.context = context;
this.jobName = jobName;
this.input = input;
this.callback = callback;
}
@Override
@ -70,11 +78,13 @@ class PrintPdfAdapter extends PrintDocumentAdapter {
if (cancellationSignal.isCanceled())
return;
pdi = new PrintDocumentInfo.Builder("test")
pdi = new PrintDocumentInfo.Builder(jobName)
.setContentType(CONTENT_TYPE_DOCUMENT)
.build();
callback.onLayoutFinished(pdi, true);
boolean changed = !newAttributes.equals(oldAttributes);
callback.onLayoutFinished(pdi, changed);
}
@Override
@ -86,18 +96,10 @@ class PrintPdfAdapter extends PrintDocumentAdapter {
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());
OutputStream output = new FileOutputStream(dest.getFileDescriptor());
try {
AssetUtil.copy(in, out);
PrintContent.copy(input, output);
} catch (IOException e) {
callback.onWriteFailed(e.getMessage());
return;
@ -105,4 +107,19 @@ class PrintPdfAdapter extends PrintDocumentAdapter {
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 java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLConnection;
class AssetUtil {
class PrintContent {
// List of supported content types
enum ContentType { PLAIN, HTML, IMAGE, PDF, SELF }
enum ContentType { PLAIN, HTML, IMAGE, PDF, UNSUPPORTED }
// Application context
private final @NonNull Context context;
@ -48,8 +50,8 @@ class AssetUtil {
*
* @param ctx The application context.
*/
AssetUtil (@NonNull Context ctx) {
this.context = ctx;
private PrintContent (@NonNull Context ctx) {
context = ctx;
}
/**
@ -59,21 +61,60 @@ class AssetUtil {
*
* @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;
if (path == null || path.isEmpty())
{
type = ContentType.SELF;
}
else if (path.charAt(0) == '<')
if (path == null || path.isEmpty() || path.charAt(0) == '<')
{
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;
@ -83,10 +124,25 @@ class AssetUtil {
* Opens a file://, res:// or base64:// Uri as a stream.
*
* @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.
*/
@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;
@ -114,10 +170,25 @@ class AssetUtil {
* 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 Bitmap decode (@NonNull String path)
@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.
*
* @param path The file path to decode.
*
* @return A bitmap or null if the path is not valid
*/
@Nullable
private Bitmap decode (@NonNull String path)
{
Bitmap bitmap;
@ -150,7 +221,8 @@ class AssetUtil {
* @param input The readable input 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,
@NonNull OutputStream output) throws IOException
@ -162,8 +234,22 @@ class AssetUtil {
output.write(buf, 0, bytesRead);
}
output.close();
input.close();
input.reset();
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.
*/
private @Nullable InputStream openFile (@NonNull String path)
@Nullable
private InputStream openFile (@NonNull String path)
{
String absPath = path.substring(7);
@ -191,7 +278,8 @@ class AssetUtil {
*
* @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);
@ -205,7 +293,8 @@ class AssetUtil {
*
* @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");
@ -223,7 +312,8 @@ class AssetUtil {
*
* @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);
Bitmap bitmap;
@ -233,11 +323,7 @@ class AssetUtil {
bitmap = BitmapFactory.decodeStream(stream);
try {
stream.close();
} catch (IOException e) {
// ignore
}
close(stream);
return bitmap;
}
@ -249,7 +335,8 @@ class AssetUtil {
*
* @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);
int resId = getResId(resPath);
@ -264,7 +351,8 @@ class AssetUtil {
*
* @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);
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.
*/
private @NonNull InputStream openBase64 (@NonNull String path)
@NonNull
private InputStream openBase64 (@NonNull String path)
{
String data = path.substring(9);
byte[] bytes = Base64.decode(data, 0);
@ -294,7 +383,8 @@ class AssetUtil {
*
* @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);
byte[] bytes = Base64.decode(data, 0);
@ -333,10 +423,16 @@ class AssetUtil {
return resId;
}
/**
* Returns the asset manager for the app.
*/
private AssetManager getAssets() {
return context.getAssets();
}
/**
* Returns the resource bundle for the app.
*/
private Resources getResources() {
return context.getResources();
}

View File

@ -31,12 +31,15 @@ import android.support.v4.print.PrintHelper;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.InputStream;
import static android.content.Context.PRINT_SERVICE;
import static de.appplant.cordova.plugin.printer.PrintContent.ContentType.UNSUPPORTED;
class PrintManager {
// The application context
private @NonNull Context context;
private final @NonNull Context context;
/**
* Constructor
@ -61,15 +64,16 @@ class PrintManager {
if (item != null)
{
supported = new AssetUtil(context).open(item) != null;
supported = PrintContent.getContentType(item, context) != UNSUPPORTED;
}
return supported;
}
/**
* @return List of all printable document types (utis).
* List of all printable document types (utis).
*/
@NonNull
static JSONArray getPrintableUTIs()
{
JSONArray utis = new JSONArray();
@ -82,8 +86,6 @@ class PrintManager {
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;
}
@ -96,19 +98,20 @@ class PrintManager {
* @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,
void print (@Nullable String content, @NonNull JSONObject settings,
@Nullable PrintHelper.OnPrintFinishCallback callback)
{
switch (AssetUtil.getContentType(content))
switch (PrintContent.getContentType(content, context))
{
case IMAGE:
//noinspection ConstantConditions
printImage(content, settings, callback);
break;
case PDF:
//noinspection ConstantConditions
printPdf(content, settings, callback);
break;
case HTML:
case SELF:
case PLAIN:
// TODO
}
@ -121,15 +124,19 @@ class PrintManager {
* @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,
private void printPdf (@NonNull String path, @NonNull JSONObject settings,
@Nullable PrintHelper.OnPrintFinishCallback callback)
{
InputStream stream = PrintContent.open(path, context);
if (stream == null) return;
PrintOptions options = new PrintOptions(settings);
String jobName = options.getJobName();
PrintPdfAdapter adapter = new PrintPdfAdapter(path, context);
PrintAttributes attributes = options.toPrintAttributes();
PrintAdapter adapter = new PrintAdapter(jobName, stream, callback);
PrintAttributes attrs = options.toPrintAttributes();
getPrintService().print(jobName, adapter, attributes);
getPrintService().print(jobName, adapter, attrs);
}
/**
@ -139,11 +146,10 @@ class PrintManager {
* @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,
private void printImage (@NonNull String path, @NonNull JSONObject settings,
@Nullable PrintHelper.OnPrintFinishCallback callback)
{
AssetUtil decoder = new AssetUtil(context);
Bitmap bitmap = decoder.decode(path);
Bitmap bitmap = PrintContent.decode(path, context);
if (bitmap == null) return;
@ -159,7 +165,8 @@ class PrintManager {
/**
* 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);
}

View File

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

View File

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

View File

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

View File

@ -26,7 +26,7 @@ exports._defaults = {
// name: 'unknown',
// duplex: 'none',
// landscape: false,
// graystyle: false,
// monochrome: false,
// border: true,
// copies: 1,