I have some serious problem. We have in our application plugged in an external ole plugin in our eclipse rcp client. This plugin causes some error while running, which then kills the entire client.
Since we cannot fix the problem, we want to catch that error, so that the client at least won't crash. To be more precise, the client crashes because the JVM caused a fatal error. Is there any possibility to suppress those errors, to avoid the JVM from crashing?
This is the code where we include the plugin in our client, has anyone some idea how to achieve such a error-catch?
// create OLE frame and site (XMAX control).
clatFrame = new OleFrame(parent, SWT.NONE);
clatUtil = null;
try {
clatSite = new OleControlSite(clatFrame, SWT.NONE,
"Congree.XMax.Control");
clatSite.doVerb(OLE.OLEIVERB_INPLACEACTIVATE);
clatUtil = new OleUtil(clatSite);
// setzt die Dialogsprache
String dialogLanguage = Platform.getNL();
// Umgehung für Bug-20760
if (LOCALE_DE.equalsIgnoreCase(dialogLanguage)) {
// ClatIn Format 'de-DE'
dialogLanguage = dialogLanguage + "-DE"; //$NON-NLS-1$
} else {
dialogLanguage = dialogLanguage.replace("_", "-");
}
clatUtil.invokeMethod(null, "SetGuiLanguage", true, dialogLanguage);
GridDataFactory.fillDefaults().hint(SWT.DEFAULT, 25).grab(true,
false).align(SWT.FILL, SWT.TOP).applyTo(clatFrame);
} catch (SWTException e) {
clatFrame.dispose();
LOG.warn("OLE control (CLAT) not installed");
}
LOG
http://www.file-upload.net/download-8284055/jvm_errorlog.log.html
You can try to catch the Throwable instead of the SWTException. If that doesn't help, you can try a different library like jacob for the OLE/COM automation: http://sourceforge.net/projects/jacob-project/ The crash is most likely caused by some invalid state in the c code, therefore a different library might help.
Related
I have a method called createBufferedImageFromURI that takes a string that could be either a file system path or an URL, and creates a BufferedImage from the resource corresponding to that string.
The method code is the following:
private static BufferedImage createBufferedImageFromURI(String filePathOrUrl)
throws IOException
{
IHttpContext httpContext = com.genexus.ModelContext
.getModelContext().getHttpContext();
InputStream is = null;
try {
if (filePathOrUrl.toLowerCase().startsWith("http://") ||
filePathOrUrl.toLowerCase().startsWith("https://") ||
(httpContext.isHttpContextWeb() &&
filePathOrUrl.startsWith(httpContext.getContextPath())))
is = new URL(GXDbFile.pathToUrl(filePathOrUrl, httpContext))
.openStream();
else
is = getGXFile(filePathOrUrl).getStream();
return ImageIO.read(is);
} catch (IOException e) {
log.error("Failed to read image stream: " + filePathOrUrl);
throw e;
} finally {
is.close();
}
}
As you can see, the first part of the if - else block corresponds to the case where the string is an URL. It was working just fine until it suddenly didn't. The following started appearing in the webapp logs:
2023-02-14T09:25:30,286 [http-nio-8080-exec-13] ERROR com.genexus.GxImageUtil - getImageWidth https://static3.depositphotos.com/1000575/154/i/600/depositphotos_1549339-stock-photo-lithuania-landscape-panorama.jpg failed
java.lang.NullPointerException: Cannot invoke "java.awt.image.BufferedImage.getWidth()" because the return value of "com.genexus.GxImageUtil.createBufferedImageFromURI(String)" is null
at com.genexus.GxImageUtil.getImageWidth(GxImageUtil.java:79) ~[gxclassR-2.10-SNAPSHOT.jar:?]
at com.pruebasjavastable.webpanelimageapi_impl.e12042(webpanelimageapi_impl.java:771) ~[classes/:?]
at com.pruebasjavastable.webpanelimageapi_impl.strup040(webpanelimageapi_impl.java:736) ~[classes/:?]
at com.pruebasjavastable.webpanelimageapi_impl.start042(webpanelimageapi_impl.java:525) ~[classes/:?]
at com.pruebasjavastable.webpanelimageapi_impl.executeStartEvent(webpanelimageapi_impl.java:122) ~[classes/:?]
...
There is no other trace what could be the cause of this error. Neither on Tomcat's console or even in the browser console or network tab
I did some debugging to discard some obvious possible causes, but none of them where the case
GXDbFile.pathToUrl( filePathOrUrl, httpContext) builds the URL just fine. I even tried hard coding one and it still didn't work
Discarded some firewall or proxy issue by testing it on other PCs and networks. Even tried it on online playgrounds
Is there a problem with this implementation? As I said, it was working just fine until it suddenly didn't. Only change I can think of is that I changed my JDK from 17.0.4 to 17.0.6 but I read the release notes and nothing seems to affect my case.
I am using Epson_JavaPOS_ADK_11414_for_Linux_x64 for installing the JavaPOS with pcs ( sh installJavaPOSFull-64.sh )
I ran this program one after the other in different shells and the second process crashes for some reason:
import jpos.JposException;
import jpos.POSPrinter;
import jpos.util.JposPropertiesConst;
import java.time.Duration;
import java.time.Instant;
public class POSPrinterClaimTest {
static POSPrinter posPrinter;
private static Boolean isOpen = false;
private static Boolean isClaimed = false;
private static Boolean isEnabled = false;
static {
System.setProperty(
JposPropertiesConst.JPOS_POPULATOR_FILE_PROP_NAME, "jpos.xml");
}
public static void main(String[] args) {
try {
posPrinter = new POSPrinter();
Instant start = Instant.now();
Instant finish = null;
Long timeElapsed = null;
openConnection("POSPrinter1");
finish = Instant.now();
timeElapsed = Duration.between(start, finish).toMillis();
System.out.println("Time taken to connect : " + timeElapsed.toString());
Thread.sleep(100000L);
terminate();
System.out.println("terminated from try block");
} catch (JposException | InterruptedException e) {
e.printStackTrace();
try {
terminate();
System.out.println("terminated from catch block");
Thread.sleep(5000);
} catch (JposException | InterruptedException jposException) {
jposException.printStackTrace();
}
} catch(Throwable t){
t.printStackTrace();
} finally {
posPrinter = null;
}
}
private static void openConnection(String printerName) throws JposException {
try {
String printerNamesss = printerName;
/**
* open the printer object according to the device logical name defined in jpos.xml
*/
posPrinter.open(printerName);
isOpen = true;
System.out.println("opened");
/**
* Get the exclusive control right for the opened device.
* Then the device is disable from other application.
* */
posPrinter.claim(3000);
isClaimed = true;
System.out.println("claimed");
/**
* enable the device for input and output
*/
posPrinter.setDeviceEnabled(true);
isEnabled = true;
System.out.println("enabled");
} catch (JposException jposException) {
System.out.println("jpos exception in open : " + jposException.getMessage());
throw jposException;
} catch(Throwable t) {
System.out.println("unknown throwable open: " + t.getMessage());
}
}
public static void terminate() throws JposException {
try {
if(isOpen && isClaimed) {
posPrinter.clearOutput();
System.out.println("cleared output");
}
if(isEnabled) {
posPrinter.setDeviceEnabled(false);
isEnabled = false;
System.out.println("setDeviceEnabled false");
}
if(isClaimed) {
posPrinter.release();
isClaimed = false;
System.out.println("released");
}
if(isOpen) {
posPrinter.close();
isOpen = false;
System.out.println("closed");
}
} catch (JposException jposException) {
jposException.printStackTrace();
throw jposException;
} catch(Throwable t) {
System.out.println("unknown throwable terminate: " + t.getMessage());
}
}
}
First process output
opened
claimed
enabled
Time taken to connect : 7928 --> now I start the second process.
cleared output
setDeviceEnabled false
released
closed
terminated from try block
Second process output
opened
jpos exception in open : The port is already open.
jpos.JposException: The port is already open.
at jp.co.epson.upos.core.v1_14_0001.pntr.CommonUPOSExceptionCreator.createJposException(CommonUPOSExceptionCreator.java:138)
at jp.co.epson.upos.core.v1_14_0001.pntr.CommonUPOSExceptionCreator.createJposException(CommonUPOSExceptionCreator.java:99)
at jp.co.epson.upos.core.v1_14_0001.pntr.CommonPrinterService.openPort(CommonPrinterService.java:3341)
at jp.co.epson.upos.core.v1_14_0001.pntr.CommonPrinterService.claim(CommonPrinterService.java:3103)
at jpos.BaseJposControl.claim(Unknown Source)
at POSPrinterClaimTestThreads.openConnection(POSPrinterClaimTestThreads.java:73)
at POSPrinterClaimTestThreads.test(POSPrinterClaimTestThreads.java:36)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
closed
terminated from catch block
#
# A fatal error has been detected by the Java Runtime Environment:
#
# SIGSEGV (0xb) at pc=0x00007f43880ea9df, pid=12119, tid=0x00007f43690b0700
#
# JRE version: OpenJDK Runtime Environment (8.0_252-b09) (build 1.8.0_252-b09)
# Java VM: OpenJDK 64-Bit Server VM (25.252-b09 mixed mode linux-amd64 compressed oops)
# Problematic frame:
# C [libethernetio31.so+0x1f9df] CCommonPort::PortEvent(unsigned int, unsigned int, unsigned int*, unsigned int, unsigned char*)+0xf
#
# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# An error report file with more information is saved as:
# /POSPrinterTest/JavaPOS/hs_err_pid12119.log
#
# If you would like to submit a bug report, please visit:
# http://bugreport.java.com/bugreport/crash.jsp
#
Aborted
If you see the second process throws a JposException which then initiates its catch and closes the connection. Then it suddenly crashes instead of sleeping for 5sec.
Any help?
After discussing this problem with EPSON team, they were able to provide a fix for the same with a new libepsonjpos.so and epsonjpos.jar
It solved two problems for me:
The original claim issue in the post - resolved after using the latest libepsonjpos.so
Multi-threaded printing in a single process in non-install mode (No-SetupPOS-insall)
the Epson team said to synchronize the POSPrinter.open() in all the threads and update the epsonjpos.jar with their fix
Some quotes on the issue from Epson Team.
Multi-thread issue - "Change History
The fix that has been applied is in the device sharing logic. Only affects lite-mode (No-SetupPOS version)
The different objects falsely assumed as equal. As result only the first connected printer has been used.
The fix corrects that comparison logic."
Claim Issue - "We are glad we could help you.
As for the background of the issues. The multi-process usage with non-install mode was a corner case for our JavaPOS ADK. With your help we could improve our testing cases."
Epson team said that they will release the patches in the next version.
It seems that the exclusive control processing of JavaPOS Service Object does not work well.
You should get a timeout error instead of an error that the port is already open.
However, your program is also bad.
The range of one try, catch is too wide.
JposException takes the form of an exception, but it's really just an error code notification.
Just because a JposException was signaled does not mean it crashes.
Most of the time, it will work if you eliminate the cause of the error and try again.
To be correct, try and catch each method call and property access.
The Epson_JavaPOS_ADK sample program should have been made that way.
Please code the same as the sample program.
In Addition:
Is what you see different?
It does try and catch in small units for each method and property, and does not propagate it to the top with throw.
Some of the sources I have are:
from "Epson_JavaPOS_ADK_11414_for_Linux_x64\Sample\Samples\Printer\PrinterSample_Step15\src\printersample_step15\Step15Frame.java"
// JavaPOS's code for Step7
// Set OutputCompleteEvent listener
ptr.addOutputCompleteListener(this);
// JavaPOS's code for Step7--END
// JavaPOS's code for Step10
try {
//Open the device.
//Use the name of the device that connected with your computer.
ptr.open("POSPrinter");
}
catch(JposException ex){
JOptionPane.showMessageDialog(this, "This device has not been registered, or cannot use.",
"",JOptionPane.WARNING_MESSAGE);
//Nothing can be used.
changeButtonStatus();
return;
}
try {
//Get the exclusive control right for the opened device.
//Then the device is disable from other application.
ptr.claim(1000);
bCoverSensor = ptr.getCapCoverSensor();
}
catch(JposException ex){
JOptionPane.showMessageDialog(this, "Fails to get the exclusive access for the device.",
"",JOptionPane.WARNING_MESSAGE);
//Nothing can be used.
changeButtonStatus();
return;
}
Extracting the situation from the comment and posting:
Well, the error reported by Claim may be wrong, so why not contact EPSON support with detailed information about such a situation?
From OP:
Done that already but we don't know if they will respond back. So trying to find answer here; somewhere. :D
From OP:
I ran the same program in a single process with multiple threads in two pulses. I do now get the timeout error as you said. Only in different processes scenario, I am getting a crash
If so, there may be a problem with the interprocess exclusive control of the JavaPOS service object. That's the problem with it, but the crash could be a cleanup issue at the end of the exception, as I commented earlier. When an exception occurs in the Claim method, instead of ending the process by propagating the exception, call the Close method and try other cleanup to end normally.
From OP:
I wrote the posPrinter.close() in claim catch block. It worked and crash frequency reduced significantly when doing with two processes single threads. Still the crash happened once or twice. But in two processes each with 10 threads trying to claim printer results in one process able to claim and the other process crashing. try{ posPrinter.claim(3000); } catch(JposException ex) { posPrinter.close();}
It seems that the problem of exclusive control of JavaPOS service object of EPSON may remain. Please make additional inquiries to EPSON based on such survey information.
We are now developing a payment card with NXP NQ220 (has embedded SE, called eSE) on Android N. The platform is MTK. Now, we can interact with eSE using OMA (using org.simalliance.openmobileapi.jar). It works as expected.
I was wondering if there is any ways to open channel in session without AID? Besides, is there any ways to control the power of eSE(power-on and power-off) and reset eSE in some situations?
My investigation as follows:
About open channel without AID, I have found following sentences in page 16 of Open Mobile API specification V3.
(h)Method: Channel openLogicalChannel(byte[] aid, Byte P2)
Open a logical channel with the SE, selecting the applet represented by the >given AID. If the AID is null, which means no applet is to be selected on >this channel, the default applet is used. It's up to the SE to choose which >logical channel will be used.
However, if we set aid to null in openLogicalChannel(byte[] aid), following exception will be shows. What happens about it? Is the default applet or eSE have problems?
01-30 01:06:39.941 V/SmartcardService( 2587): OpenLogicalChannel Exception: Access Control Enforcer: no APDU access allowed!
01-30 01:06:39.947 E/SeControlClient( 3239): Error occured:
01-30 01:06:39.947 E/SeControlClient( 3239): java.lang.SecurityException: Access Control Enforcer: no APDU access allowed!
01-30 01:06:39.947 E/SeControlClient( 3239): at org.simalliance.openmobileapi.SEService.checkForException(SEService.java:255)
01-30 01:06:39.947 E/SeControlClient( 3239): at org.simalliance.openmobileapi.Session.openLogicalChannel(Session.java:295)
It seems there is no method in OMA to reset eSE. But I found reset() method in INxpNfcAdapterExtras. However, when I use INxpNfcAdapterExtras.reset(), it always return false. Following codes is how we get INxpNfcAdapterExtras.
private INxpNfcAdapterExtras getNxpNfcAdapterExtras() {
if (mNfcAdapter != null) {
try {
INxpNfcAdapter nxpNfcAdapter =
mNfcAdapter.getService().getNxpNfcAdapterInterface();
return nxpNfcAdapter.getNxpNfcAdapterExtrasInterface();
} catch (Exception e) {
Log.e(LOGTAG, "Exception occured:", e);
}
} else {
Log.e(LOGTAG, "Please initialize NfcAdapter first.");
}
return null;
}
About control the power of eSE, is it related to the platform? Can you give me some suggestions? Thank you very much.
Dont known
To access SE functions your application must be execute with owner of android device.
You could check this in : https://github.com/NXPNFCLinux/android_nxp-nci/blob/1d95fe24334fa12c9d9eccd1141f8739972c4288/aosp/packages/apps/Nfc/src/com/android/nfc/NfcService.java
The reset method check permission before:
public boolean reset(String pkg) throws RemoteException {
NfcService.this.enforceNfceeAdminPerm(pkg);
Bundle result;
boolean stat = false;
try {
stat = _nfcEeReset();
result = writeNoException();
} catch (IOException e) {
result = writeEeException(EE_ERROR_IO, e.getMessage());
}
Log.d(TAG,"reset" + stat);
return stat;
}
The check permission method:
public void enforceNfceeAdminPerm(String pkg) {
if (pkg == null) {
throw new SecurityException("caller must pass a package name");
}
NfcPermissions.enforceUserPermissions(mContext);
if (!mNfceeAccessControl.check(Binder.getCallingUid(), pkg)) {
throw new SecurityException(NfceeAccessControl.NFCEE_ACCESS_PATH +
" denies NFCEE access to " + pkg);
}
if (UserHandle.getCallingUserId() != UserHandle.USER_OWNER) {
throw new SecurityException("only the owner is allowed to call SE APIs");
}
}
To execute your app with device owner, you could follow my anwser here:
Device Admin API, how to be a device owner?
I'm not sure about what you mean "control the power of eSE". If it's on/off eSE, then eSE is integrated with NFC chip so if you disable NFC in Android eSE will be power off.
I have found another way to solve this issue. It used NXP's own class NxpNfcAdapterExtrasService.
1.I still don't know why the exception happens when we open channel use the default Applet(without AID). But, with the method in NxpNfcAdapterExtrasService, we can establish connection with eSE.
2.About the second question. The codes is right but the way of how to use INxpNfcAdapterExtras.reset() is wrong. This method will return true only when you do something with eSE. Like transmit and execute APDU commands. So you can use this method when you want to disconnect the connection with eSE.
3.About the third question, I don't know whether the openUicc()/closeUicc() method can control the eSE power. But, it seems this two method works as expected.
I'm using grph library for a university project (www.i3s.unice.fr/~hogie/grph/)
but i have a problem only on Linux with that library, when i create a new Graph object, i receive the following exception:
Exception in thread "main" java.lang.ExceptionInInitializerError
at org.elendev.wesproject.graph.GraphFactory.main(GraphFactory.java:19)
Caused by: java.lang.NullPointerException
at toools.os.OperatingSystem.getLocalOS(OperatingSystem.java:47)
at grph.Grph.setCompilationDirectory(Grph.java:353)
at grph.Grph.<clinit>(Grph.java:246)
... 1 more
I tried to call directly getLocalOS function, with:
System.out.println(toools.os.OperatingSystem.getLocalOS());
and i receive the same exception. I cannot find information about that library, and the project launched on a macbook works perfectly.
The operating system i'm currently using is gentoo linux 32bit.
And the jdk version is: 1.7.0_65
Any idea of what could be the problem?
Not sure whether this can count as an answer, but it could at least help to solve the issue:
The exception comes from the toools.os.OperatingSystem.getLocalOS method. Although the .JAR file from the website that you mentioned has a whopping 39 megabytes, the source code of this class is not contained in it.
There seems to be no information available about this class at all. Neither Google nor Maven finds anything related to the toools package. One has to assume that it is an abandoned utility class that passed away a long time ago.
However, the method in question can be disassembled to the following code:
public static OperatingSystem getLocalOS()
{
if (localOS == null)
{
if (new RegularFile("/etc/passwd").exists())
{
if (new Directory("/proc").exists())
{
if (new RegularFile("/etc/fedora-release").exists()) {
localOS = new FedoraLinux();
} else if (ExternalProgram.commandIsAvailable("ubuntu-bug")) {
localOS = new UbuntuLinux();
} else {
localOS = new Linux();
}
}
else if (new Directory("/Applications").exists()) {
localOS = new MacOSX();
} else {
localOS = new Unix();
}
}
else if (System.getProperty("os.name").startsWith("Windows")) {
localOS = new Windows();
} else {
localOS = new OperatingSystem();
}
localOS.name = System.getProperty("os.name");
localOS.version = System.getProperty("os.version");
}
return localOS;
}
From this, you can possibly derive the conditions that must be met in order to properly detect your OS as a linux OS. Particularly, when there is a file named /etc/passwd, and a directory /proc, this should be sufficient to identify the OS as a Linux. You may want to give it a try...
While trying to find an answer to Android Jasper Reporting I found out that there are two other questions to be answered therefor, which I been asked to ask as a question, not as an answer ;):
My questions are now: "Is there any compiler to use directly on the device" AND "how to execute such without rooting the device.
If anybody could give me a hint I would really appreciate it...
I looked a little time forward on this approach, and found apps which makes it possible to create APKs directly on an Android device which is NOT rooted:
TerminalIDE - https://play.google.com/store/apps/details?id=com.spartacusrex.spartacuside&hl=de
JavaIDEdroid - http://code.google.com/p/java-ide-droid/
AIDE - https://play.google.com/store/apps/details?id=com.aide.ui&hl=en
Looks like they're using the compiler from eclipse and a ported dex converter. Now I'm trying to figure out how to do the same.
Sure: get the source code and look into it. But while I'm having curious problems to get a connection to the servers and trying to solve it, I follow the plea to ask this question here. Hoping both to help others with it and also getting an answer for myself ;)
I took the org.eclipse.jdt.core_3.7.3.v20120119-1537.jar from the plugin directory of my indigo and tried following code:
org.eclipse.jdt.internal.compiler.batch.Main ecjMain = new org.eclipse.jdt.internal.compiler.batch.Main(new PrintWriter(System.out), new PrintWriter(System.err), false/*noSystemExit*/, null, progress);
System.err.println("compiling...");
ecjMain.compile(new String[] {"-classpath", "/system/framework", storage.getAbsolutePath()+"/Test.java"});
ecjMain.compile(new String[] {storage.getAbsolutePath()+"/Test.java"});
System.err.println("compile succeeded!!!");
Sometimes the Exception was thrown that java.lang.Object could not be found and othertimes it stuck doing nothing while heating up my processor with 100% usage ... ...
At this time i could not figure out what is happening and why. And in cause that i have other work to do this part has to wait a little.
I succeeded after taking inspiration from source of JavaIDEdroid and realizing that I'm dumb (for a time I tried to uses the compiler with the dexified framework classes on the device - which naturtally could not work).
After i succeeded compiling my Test.java with a copy of ADTs android-jar on sdcard I just had to load the classes with the DexClassLoader.
While informing myselft about how to do that I found this nice article Custom Class Loading in Dalvik which inspired me at least to write this piece of code:
File storage = getDir("all41", Context.MODE_PRIVATE);
System.err.println("copying the android.jar from asssets to the internal storage to make it available to the compiler");
BufferedInputStream bis = null;
OutputStream dexWriter = null;
int BUF_SIZE = 8 * 1024;
try {
bis = new BufferedInputStream(getAssets().open("android.jar"));
dexWriter = new BufferedOutputStream(
new FileOutputStream(storage.getAbsolutePath() + "/android.jar"));
byte[] buf = new byte[BUF_SIZE];
int len;
while((len = bis.read(buf, 0, BUF_SIZE)) > 0) {
dexWriter.write(buf, 0, len);
}
dexWriter.close();
bis.close();
} catch (Exception e) {
System.err.println("Error while copying from assets: " + e.getMessage());
e.printStackTrace();
}
System.err.println("instantiating the compiler and compiling the java file");
org.eclipse.jdt.internal.compiler.batch.Main ecjMain = new org.eclipse.jdt.internal.compiler.batch.Main(new PrintWriter(System.out), new PrintWriter(System.err), false/*noSystemExit*/, null);
ecjMain.compile(new String[] {"-classpath", storage.getAbsolutePath()+"/android.jar", Environment.getExternalStorageDirectory().getAbsolutePath() + "/Test.java"});
System.err.println("calling DEX and dexifying the test class");
com.android.dx.command.Main.main(new String[] {"--dex", "--output=" + storage.getAbsolutePath() + "/Test.zip", Environment.getExternalStorageDirectory().getAbsolutePath() + "/./Test.class"});
System.err.println("instantiating DexClassLoader, loading class and invoking toString()");
DexClassLoader cl = new DexClassLoader(storage.getAbsolutePath() + "/Test.zip", storage.getAbsolutePath(), null, getClassLoader());
try {
Class libProviderClazz = cl.loadClass("Test");
Object instance = libProviderClazz.newInstance();
System.err.println(instance.toString());
} catch (Exception e) {
System.err.println("Error while instanciating object: " + e.getMessage());
e.printStackTrace();
}
The Test.java only contains one method:
public String toString() {
return "Hallo Welt!";
}
To get it running you need the jars jdt-compiler-x.x.x.jar (found in plugins directory of eclipse) and dx.jar (found in directory platform-tools/lib of Android SDK)
Not really hard ;) And now I will find out what to change in source of JasperReports to get it work on our beloved Android devices :D