Initial reimplementation for Android

This commit is contained in:
Sebastián Katzer 2019-01-28 22:28:55 +01:00
parent 652c6c62c2
commit 6c16959267
13 changed files with 853 additions and 1514 deletions

View File

@ -26,7 +26,7 @@
<plugin id="cordova-plugin-printer" <plugin id="cordova-plugin-printer"
xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns="http://apache.org/cordova/ns/plugins/1.0"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
version="0.8.0.beta"> version="0.8.0.alpha">
<name>Printer</name> <name>Printer</name>
@ -97,37 +97,24 @@
</feature> </feature>
</config-file> </config-file>
<config-file target="AndroidManifest.xml" parent="/manifest/application"> <preference name="ANDROID_SUPPORT_V4_VERSION" default="28.+"/>
<activity
android:name="de.appplant.cordova.plugin.printer.ui.SelectPrinterActivity" <framework src="com.android.support:support-v4:$ANDROID_SUPPORT_V4_VERSION"/>
android:label="Printer"
android:exported="false"> <source-file src="src/android/AssetUtil.java"
</activity> target-dir="src/de/appplant/cordova/plugin/printer" />
</config-file>
<source-file src="src/android/Options.java"
target-dir="src/de/appplant/cordova/plugin/printer" />
<source-file src="src/android/PdfAdapter.java"
target-dir="src/de/appplant/cordova/plugin/printer" />
<source-file src="src/android/Printer.java" <source-file src="src/android/Printer.java"
target-dir="src/de/appplant/cordova/plugin/printer" /> target-dir="src/de/appplant/cordova/plugin/printer" />
<source-file src="src/android/ui/SelectPrinterActivity.java" <source-file src="src/android/PrintManager.java"
target-dir="src/de/appplant/cordova/plugin/printer/ui" /> target-dir="src/de/appplant/cordova/plugin/printer" />
<source-file src="src/android/reflect/Meta.java"
target-dir="src/de/appplant/cordova/plugin/printer/reflect" />
<source-file src="src/android/ext/PrinterDiscoverySession.java"
target-dir="src/de/appplant/cordova/plugin/printer/ext" />
<source-file src="src/android/ext/PrintManager.java"
target-dir="src/de/appplant/cordova/plugin/printer/ext" />
<source-file src="src/android/ext/PrintServiceInfo.java"
target-dir="src/de/appplant/cordova/plugin/printer/ext" />
<resource-file src="res/android/layout/printer_list_item.xml"
target="res/layout/printer_list_item.xml" />
<resource-file src="res/android/layout/select_printer_activity.xml"
target="res/layout/select_printer_activity.xml" />
</platform> </platform>
<!-- windows --> <!-- windows -->

View File

@ -1,80 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
* Copyright (c) 2013-2016 by appPlant GmbH. All rights reserved.
*
* @APPPLANT_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apache License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://opensource.org/licenses/Apache-2.0/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPPLANT_LICENSE_HEADER_END@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:minHeight="?android:attr/listPreferredItemHeight"
android:orientation="horizontal"
android:gravity="start|center_vertical">
<ImageView
android:id="@android:id/icon"
android:layout_width="32dip"
android:layout_height="32dip"
android:layout_gravity="center_vertical"
android:layout_marginEnd="8dip"
android:duplicateParentState="true"
android:contentDescription="@null"
android:visibility="invisible">
</ImageView>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:duplicateParentState="true">
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:singleLine="true"
android:ellipsize="end"
android:textIsSelectable="false"
android:gravity="top|start"
android:textColor="?android:attr/textColorPrimary"
android:duplicateParentState="true">
</TextView>
<TextView
android:id="@android:id/hint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:singleLine="true"
android:ellipsize="end"
android:textIsSelectable="false"
android:visibility="gone"
android:textColor="?android:attr/textColorPrimary"
android:duplicateParentState="true">
</TextView>
</LinearLayout>
</LinearLayout>

View File

@ -1,74 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
* Copyright (c) 2013-2016 by appPlant GmbH. All rights reserved.
*
* @APPPLANT_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apache License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://opensource.org/licenses/Apache-2.0/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPPLANT_LICENSE_HEADER_END@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scrollbarStyle="outsideOverlay"
android:cacheColorHint="@android:color/transparent"
android:scrollbarAlwaysDrawVerticalTrack="true" >
</ListView>
<FrameLayout
android:id="@android:id/empty"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:visibility="gone">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="?android:attr/textColorSecondary"
android:text="Searching for printers">
</TextView>
<ProgressBar
android:id="@android:id/progress"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
style="@android:style/Widget.DeviceDefault.Light.ProgressBar.Horizontal">
</ProgressBar>
</LinearLayout>
</FrameLayout>
</LinearLayout>

343
src/android/AssetUtil.java Normal file
View File

@ -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();
}
}

167
src/android/Options.java Normal file
View File

@ -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);
}
}
}
}

108
src/android/PdfAdapter.java Normal file
View File

@ -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 });
}
}

View File

@ -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);
}
}

View File

@ -21,83 +21,20 @@
package de.appplant.cordova.plugin.printer; package de.appplant.cordova.plugin.printer;
import android.app.Activity; import android.support.annotation.Nullable;
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 org.apache.cordova.CallbackContext; import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin; import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.PluginResult; import org.apache.cordova.PluginResult;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject; 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 * 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 * 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. * the print adapter of that web view and initializes a print job.
*/ */
public class Printer extends CordovaPlugin { 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. * Executes the request.
@ -112,298 +49,85 @@ public class Printer extends CordovaPlugin {
* @param action The action to execute. * @param action The action to execute.
* @param args The exec() arguments in JSON form. * @param args The exec() arguments in JSON form.
* @param callback The callback context used when calling back into JavaScript. * @param callback The callback context used when calling back into JavaScript.
*
* @return Whether the action was valid. * @return Whether the action was valid.
*/ */
@Override @Override
public boolean execute (String action, JSONArray args, public boolean execute (String action, JSONArray args,
CallbackContext callback) throws JSONException { CallbackContext callback)
{
boolean valid = true;
command = callback; if (action.equalsIgnoreCase("check"))
{
if (action.equalsIgnoreCase("check")) { check(args.optString(0), callback);
check(); }
return true; 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")) { return valid;
pick();
return true;
}
if (action.equalsIgnoreCase("print")) {
print(args);
return true;
}
return false;
} }
/** /**
* Informs if the device is able to print documents. * If the print framework is able to render the referenced file.
* A Internet connection is required to load the cloud print dialog. *
* @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 () { private void check (@Nullable String item, CallbackContext callback)
cordova.getThreadPool().execute(new Runnable() { {
@Override cordova.getThreadPool().execute(() -> {
public void run() { PrintManager pm = new PrintManager(cordova.getContext());
List<PrintServiceInfo> services = pm.getEnabledPrintServices(); boolean printable = pm.canPrintItem(item);
Boolean available = services.size() > 0;
PluginResult res1 = new PluginResult( PluginResult res = new PluginResult(
PluginResult.Status.OK, available); PluginResult.Status.OK, printable);
PluginResult res2 = new PluginResult(
PluginResult.Status.OK, services.size());
PluginResult res = new PluginResult(
PluginResult.Status.OK, Arrays.asList(res1, res2));
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 * @param callback The plugin function to invoke with the result.
* The exec arguments as JSON
*/ */
private void print (final JSONArray args) { private void utis (CallbackContext callback)
final String content = args.optString(0); {
final JSONObject props = args.optJSONObject(1); cordova.getThreadPool().execute(() -> {
JSONArray utis = PrintManager.getPrintableUTIs();
cordova.getActivity().runOnUiThread( new Runnable() { PluginResult res = new PluginResult(
@Override PluginResult.Status.OK, utis);
public void run() {
if( content.isEmpty() ) { callback.sendPluginResult(res);
do_print((WebView)_webView.getView(), props);
} else {
initWebView(props);
loadContent(content);
}
}
}); });
} }
/** /**
* Presents a list with all enabled print services and invokes the * Sends the provided content to the printing controller and opens
* callback with the selected one. * them.
*/
private void pick () {
Intent intent = new Intent(
cordova.getActivity(), SelectPrinterActivity.class);
cordova.startActivityForResult(this, intent, 0);
}
/**
* Loads the content into the web view.
* *
* @param content * @param content The content or file to print.
* Either an HTML string or URI * @param settings Additional settings how to render the content.
* @param callback The plugin function to invoke with the result.
*/ */
private void loadContent(String content) { private void print (@Nullable String content, JSONObject settings,
if (content.startsWith("http") || content.startsWith("file:")) { CallbackContext callback)
view.loadUrl(content); {
} else { cordova.getThreadPool().execute(() -> {
String baseURL = webView.getUrl(); PrintManager pm = new PrintManager(cordova.getContext());
baseURL = baseURL.substring(0, baseURL.lastIndexOf('/') + 1); pm.print(content, settings, callback::success);
// 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;
}
}); });
} }
/**
* 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);
}
} }

View File

@ -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<Context> ctx;
/**
* The registered listener for the state change event.
*/
private WeakReference<OnPrintJobStateChangeListener> 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>(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<PrintServiceInfo> 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<PrintServiceInfo> services =
new ArrayList<PrintServiceInfo>();
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<PrintServiceInfo> 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<PrintServiceInfo> services =
new ArrayList<PrintServiceInfo>();
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<OnPrintJobStateChangeListener>(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);
}
}
}

View File

@ -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");
}
}

View File

@ -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<PrinterInfo> 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<PrinterInfo> getPrinters() {
Method method = Meta.getMethod(session.getClass(), "getPrinters");
return (List<PrinterInfo>) Meta.invokeMethod(session, method);
}
/**
* Notifies the listener about the occurred event.
*/
private void notifyOnPrintersChanged() {
if (listener != null) {
listener.onPrintersChanged(getPrinters());
}
}
}

View File

@ -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;
}
}

View File

@ -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<PrinterInfo> printers = new ArrayList<PrinterInfo>();
/**
* Constructor registers a listener to monitor about newly discovered
* printers.
*/
ListViewAdapter() {
session.setOnPrintersChangeListener(new OnPrintersChangeListener() {
@Override
public void onPrintersChanged (List<PrinterInfo> 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;
}
}
}