Support for picking a printer for iOS and Android

This commit is contained in:
Sebastián Katzer 2016-07-27 14:24:48 +02:00
parent 049f72dea6
commit 4251c0639d
11 changed files with 1064 additions and 116 deletions

View File

@ -2,8 +2,9 @@
#### Version 0.7.2 (not yet released) #### Version 0.7.2 (not yet released)
- [__change__:] Changed plugin ID to `cordova-plugin-printer` - [__change__:] Changed plugin ID to `cordova-plugin-printer`
- [__change__:] Plugin requires Android KitKat or newer - [__change__:] Plugin requires Android KitKat or newer
- [__change__:] `isAvailable` returns false if no enabled print services can be found (Android) - [__change__:] `isAvailable` returns false if no enabled service can be found (Android)
- [enhancement:] `isAvailable` returns additional list of available print services (Android) - [feature:] New `pick` interface to pick a printer for future usage
- [enhancement:] `isAvailable` returns count of available services (Android)
- [enhancement:] `print` returns bool value to indicate the result - [enhancement:] `print` returns bool value to indicate the result
- [enhancement:] Added missing `duplex` support (Android) - [enhancement:] Added missing `duplex` support (Android)
- [__change__:] `duplex` requires a string (`none`, `long` or `short`) - [__change__:] `duplex` requires a string (`none`, `long` or `short`)

View File

@ -73,7 +73,37 @@
</feature> </feature>
</config-file> </config-file>
<config-file target="AndroidManifest.xml" parent="/manifest/application">
<activity
android:name="de.appplant.cordova.plugin.printer.ui.SelectPrinterActivity"
android:label="Printer"
android:theme="@android:style/Theme.Material.Settings"
android:exported="false">
</activity>
</config-file>
<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"
target-dir="src/de/appplant/cordova/plugin/printer/ui" />
<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>
</plugin> </plugin>

View File

@ -0,0 +1,80 @@
<?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

@ -0,0 +1,74 @@
<?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>

View File

@ -22,13 +22,12 @@
package de.appplant.cordova.plugin.printer; package de.appplant.cordova.plugin.printer;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.print.PrintAttributes; import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter; import android.print.PrintDocumentAdapter;
import android.print.PrintJob; import android.print.PrintJob;
import android.print.PrintManager; import android.print.PrinterId;
import android.view.View; import android.view.View;
import android.webkit.WebSettings; import android.webkit.WebSettings;
import android.webkit.WebView; import android.webkit.WebView;
@ -41,13 +40,17 @@ import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import de.appplant.cordova.plugin.printer.ext.PrintManager;
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 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
@ -102,6 +105,11 @@ public class Printer extends CordovaPlugin {
return true; return true;
} }
if (action.equalsIgnoreCase("pick")) {
pick();
return true;
}
if (action.equalsIgnoreCase("print")) { if (action.equalsIgnoreCase("print")) {
print(args); print(args);
return true; return true;
@ -118,13 +126,14 @@ public class Printer extends CordovaPlugin {
cordova.getThreadPool().execute(new Runnable() { cordova.getThreadPool().execute(new Runnable() {
@Override @Override
public void run() { public void run() {
List<String> ids = getEnabledPrintServiceIds(); PrintManager pm = new PrintManager(cordova.getActivity());
Boolean available = ids.size() > 1; List<PrintServiceInfo> services = pm.getEnabledPrintServices();
Boolean available = services.size() > 0;
PluginResult res1 = new PluginResult( PluginResult res1 = new PluginResult(
PluginResult.Status.OK, available); PluginResult.Status.OK, available);
PluginResult res2 = new PluginResult( PluginResult res2 = new PluginResult(
PluginResult.Status.OK, new JSONArray(ids)); PluginResult.Status.OK, services.size());
PluginResult res = new PluginResult( PluginResult res = new PluginResult(
PluginResult.Status.OK, Arrays.asList(res1, res2)); PluginResult.Status.OK, Arrays.asList(res1, res2));
@ -152,6 +161,17 @@ public class Printer extends CordovaPlugin {
}); });
} }
/**
* Presents a list with all enabled print services and invokes the
* callback with the selected one.
*/
private void pick () {
Intent intent = new Intent(
cordova.getActivity(), SelectPrinterActivity.class);
cordova.startActivityForResult(this, intent, 0);
}
/** /**
* Loads the content into the web view. * Loads the content into the web view.
* *
@ -190,10 +210,10 @@ public class Printer extends CordovaPlugin {
view.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY); view.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
if (Build.VERSION.SDK_INT >= 21) { if (Build.VERSION.SDK_INT >= 21) {
Method setMixedContentModeMethod = getMethod(settings.getClass(), Method setMixedContentModeMethod = Meta.getMethod(
"setMixedContentMode", int.class); settings.getClass(), "setMixedContentMode", int.class);
invokeMethod(settings, setMixedContentModeMethod, 2); Meta.invokeMethod(settings, setMixedContentModeMethod, 2);
} }
setWebViewClient(props); setWebViewClient(props);
@ -219,7 +239,7 @@ public class Printer extends CordovaPlugin {
@Override @Override
public void onPageFinished (WebView webView, String url) { public void onPageFinished (WebView webView, String url) {
PrintManager printManager = getPrintMgr(); PrintManager pm = new PrintManager(cordova.getActivity());
PrintAttributes.Builder builder = new PrintAttributes.Builder(); PrintAttributes.Builder builder = new PrintAttributes.Builder();
PrintDocumentAdapter adapter = getAdapter(webView, docName); PrintDocumentAdapter adapter = getAdapter(webView, docName);
@ -235,14 +255,14 @@ public class Printer extends CordovaPlugin {
if (!duplex.equals("none") && Build.VERSION.SDK_INT >= 23) { if (!duplex.equals("none") && Build.VERSION.SDK_INT >= 23) {
boolean longEdge = duplex.equals("long"); boolean longEdge = duplex.equals("long");
Method setDuplexModeMethod = getMethod(builder.getClass(), Method setDuplexModeMethod = Meta.getMethod(
"setDuplexMode", int.class); builder.getClass(), "setDuplexMode", int.class);
invokeMethod(builder, setDuplexModeMethod, Meta.invokeMethod(builder, setDuplexModeMethod,
longEdge ? 2 : 4); longEdge ? 2 : 4);
} }
job = printManager.print(docName, adapter, builder.build()); job = pm.getInstance().print(docName, adapter, builder.build());
view = null; view = null;
} }
}); });
@ -265,14 +285,24 @@ public class Printer extends CordovaPlugin {
command.sendPluginResult(res); command.sendPluginResult(res);
} }
/** @Override
* Get a PrintManager instance. public void onActivityResult(int requestCode, int resultCode, Intent intent) {
* super.onActivityResult(requestCode, resultCode, intent);
* @return A PrintManager instance.
*/ if (command == null)
private PrintManager getPrintMgr () { return;
return (PrintManager) cordova.getActivity()
.getSystemService(Context.PRINT_SERVICE); String url = null;
if (intent != null) {
PrinterId printerId = intent.getParcelableExtra(EXTRA_PRINTER_ID);
url = printerId.toString();
}
PluginResult res = new PluginResult(
PluginResult.Status.OK, url);
command.sendPluginResult(res);
} }
/** /**
@ -290,99 +320,14 @@ public class Printer extends CordovaPlugin {
*/ */
private PrintDocumentAdapter getAdapter (WebView webView, String docName) { private PrintDocumentAdapter getAdapter (WebView webView, String docName) {
if (Build.VERSION.SDK_INT >= 21) { if (Build.VERSION.SDK_INT >= 21) {
Method createPrintDocumentAdapterMethod = getMethod( Method createPrintDocumentAdapterMethod = Meta.getMethod(
WebView.class, "createPrintDocumentAdapter", String.class); WebView.class, "createPrintDocumentAdapter", String.class);
return (PrintDocumentAdapter) invokeMethod( return (PrintDocumentAdapter) Meta.invokeMethod(
webView, createPrintDocumentAdapterMethod, docName); webView, createPrintDocumentAdapterMethod, docName);
} else { } else {
Method createPrintDocumentAdapterMethod = getMethod( return (PrintDocumentAdapter) Meta.invokeMethod(webView,
WebView.class, "createPrintDocumentAdapter"); "createPrintDocumentAdapterMethod");
return (PrintDocumentAdapter) invokeMethod(
webView, createPrintDocumentAdapterMethod);
} }
} }
/**
* Get a list of ids of all installed and enabled print services. For
* that it uses reflections to call public but hidden methods from the
* PrintManager.
*
* @return A list of found print service ids.
*/
private List<String> getEnabledPrintServiceIds () {
try {
PrintManager printMgr = getPrintMgr();
Class<?> printerCls = Class.forName(
"android.printservice.PrintServiceInfo");
Method getPrinterMethod = getMethod(printMgr.getClass(),
"getEnabledPrintServices");
Method getIdMethod = getMethod(printerCls,
"getId");
List printers = (List) invokeMethod(printMgr, getPrinterMethod);
ArrayList<String> printerIds = new ArrayList<String>();
printerIds.add("android.print.pdf");
if (printers == null)
return printerIds;
for (Object printer : printers) {
String printerId = (String) invokeMethod(printer, getIdMethod);
printerIds.add(printerId);
}
return printerIds;
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return Collections.emptyList();
}
/**
* 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.
*/
private Method getMethod (Class<?> cls, String name, Class<?>... params) {
try {
return cls.getDeclaredMethod(name, params);
} catch (NoSuchMethodException e) {
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.
*/
private 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;
}
} }

View File

@ -0,0 +1,115 @@
/*
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 java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import de.appplant.cordova.plugin.printer.reflect.Meta;
public final class PrintManager {
/**
* The application context.
*/
private Context ctx;
/**
* Constructor
*
* @param context The context where to look for.
*/
public PrintManager (Context context) {
this.ctx = context;
}
/**
* Get an instance from PrintManager service.
*
* @return A PrintManager instance.
*/
public final android.print.PrintManager getInstance () {
return (android.print.PrintManager)
ctx.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 = (List) Meta.invokeMethod(getInstance(),
"getInstalledPrintServices");
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 = (List) Meta.invokeMethod(getInstance(),
"getEnabledPrintServices");
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);
}
}

View File

@ -0,0 +1,62 @@
/*
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 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

@ -0,0 +1,178 @@
/*
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")) {
onPrintersChanged();
return null;
} else throw new Exception();
}
/**
* Delegate the event to the listener.
*/
public void onPrintersChanged() {
notifyOnPrintersChanged();
}
}
/**
* 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
*/
public 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.
*/
public final 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) {
Class<?> interfaceCls = null;
Object proxy = null;
this.listener = listener;
try {
interfaceCls = Class.forName(
"android.print.PrinterDiscoverySession$OnPrintersChangeListener");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
if (interfaceCls == null)
return;
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() {
Meta.invokeMethod(session, "destroy");
}
/**
* Get a list of all yet discovered printers.
*
* @return List of their basic infos
*/
@SuppressWarnings("unchecked")
public final 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

@ -0,0 +1,123 @@
/*
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 {
/**
* 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) {
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);
return resId;
}
}

View File

@ -0,0 +1,339 @@
/*
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.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";
/**
* Reference to the main view which lists all discovered printers.
*/
private ListView listView;
/**
* Session for printer discovering within the network.
*/
private PrinterDiscoverySession discoverySession;
/**
* 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);
setContentView(Meta.getResId(
this, "layout", "select_printer_activity"));
discoverySession = 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() {
discoverySession.destroy();
super.onDestroy();
}
/**
* 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);
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;
discoverySession.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 (discoverySession.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() {
discoverySession.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;
}
}
}

View File

@ -46,7 +46,8 @@
[self.commandDelegate runInBackground:^{ [self.commandDelegate runInBackground:^{
CDVPluginResult* pluginResult; CDVPluginResult* pluginResult;
BOOL isAvailable = [self isPrintingAvailable]; BOOL isAvailable = [self isPrintingAvailable];
NSArray *multipart = @[[NSNumber numberWithBool:isAvailable], @[]]; NSArray *multipart = @[[NSNumber numberWithBool:isAvailable],
[NSNumber numberWithInt:-1]];
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
messageAsMultipart:multipart]; messageAsMultipart:multipart];