Friday, December 10, 2010

Touch Menu

Cros-posted at Technè - Blog de Tecnologia do C.E.S.A.R.

Continuing with the details on how to develop touch enabled Java ME applications, today we will see a way of using such screens as input: if the user touchs an area it will trigger an action, like a menu.

This menu will also adapt to screen orientation.

First lets create a class to represent a menu entry. It knows how to paint the entry and stores a Command to be delivered when the user touchs the area:

public class TouchItem {

Command command;

public TouchItem(Command command) {
this.command = command;
}

// for this to work it is necessary that
// Graphics origin to be translated, so it
// is on the top left corner of the item,
// and that the clipping area has the
// width and height of the item
public void paint (Graphics g) {
g.drawString(command.getLabel(),
g.getClipWidth() / 2, g.getClipHeight() / 2,
Graphics.HCENTER | Graphics.BASELINE);
g.drawRect(0, 0, g.getClipWidth(), g.getClipHeight());
}
}

Then we create a class responsible for painting all menu entries. These first two classes can also be used to implement board games like Chess, for example.
public class TouchGrid {

private final TouchItem items[][];
private int itemWidth, itemHeight;
private int x, y;
private int nextLine, nextColumn;

public TouchGrid(int x, int y, int width, int height, int columns, int lines) {
this.items = new TouchItem[columns][lines];
setRectangle(x, y, width, height);
}

public void setRectangle (int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.itemWidth = width / this.items.length;
this.itemHeight = height / this.items[0].length;
}

// fill all columns in a line before moving to
// the next line. Calling this method four times
// will end up with the following menu:
// [ 1, 2 ]
// [ 3, 4 ]
// on a grid with two columns and two lines
public void add (TouchItem item) {
this.items[nextColumn][nextLine] = item;
nextColumn = (nextColumn + 1) % items.length;
if (nextColumn == 0) {
nextLine = (nextLine + 1) % items[0].length;
}
}

public void paint (Graphics g) {
Clip clip = new Clip(g);
int itemX = x;
for (int i = 0; i < items.length; i++) {
int itemY = y;
for (int j = 0; j < items[i].length; j++) {
if (items[i][j] != null) {
// needed by TouchItem to
// paint itself properly
g.setClip(itemX, itemY, itemWidth, itemHeight);
g.translate(itemX, itemY);
items[i][j].paint(g);
// reset Graphics
g.translate(-g.getTranslateX(), -g.getTranslateY());
clip.applyTo(g);
}
itemY += itemHeight;
}
itemX += itemWidth;
}
}

// here we map the point pressed to an item
public TouchItem getTouchItemAt (int x, int y) {
if (x < this.x || y < this.y) {
return null;
}

int i = (x - this.x) / this.itemWidth;
int j = (y - this.y) / this.itemHeight;

if (i >= items.length || j >= items[0].length) {
return null;
}

return items[i][j];
}
}

At paint method above we used below class to store the clip information and restore it:
public class Clip {

private int x, y;
private int width, height;

public Clip (Graphics g) {
x = g.getClipX();
y = g.getClipY();
width = g.getClipWidth();
height = g.getClipHeight();
}

public void applyTo (Graphics g) {
g.setClip(x, y, width, height);
}
}

We need a class that extends Canvas to paint the grid. It will also initialize the grid with the menu options:
public class TouchMenuCanvas extends Canvas {

private TouchGrid touchGrid;
private CommandListener commandListener;

public TouchMenuCanvas () {
final int columns = 2;
final int lines = 3;
int margin = getWidth() / 25;
// 2 = left and right margins
int width = getWidth() - (2 * margin);
// 2 = top and bottom margins
int height = getHeight() - (2 * margin);
touchGrid = new TouchGrid(margin, margin, width, height, columns, lines);
for (int i = 0; i < lines * columns; i++) {
Command c = new Command("item " + (i+1), Command.SCREEN, 1);
touchGrid.add(new TouchItem(c));
}
}

// when orientation changes we need to update
// grid rectangle
protected void sizeChanged(int width, int height) {
int margin = width / 25;
width = width - (2 * margin);
height = height - (2 * margin);
touchGrid.setRectangle(margin, margin, width, height);
}

// we need to override this method because there
// is no getCommandListener on API
public void setCommandListener(CommandListener listener) {
super.setCommandListener(listener);
commandListener = listener;
}

protected void paint(Graphics g) {
g.setColor(0xffffff); // white
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(0); // black
this.touchGrid.paint(g);
}

protected void pointerPressed(int x, int y) {
TouchItem item = this.touchGrid.getTouchItemAt(x, y);
if (item != null) {
commandListener.commandAction(item.command, this);
}
}
}

The last piece is a MIDlet to show the Canvas:
public class TouchMenuMIDlet extends MIDlet implements CommandListener {

TouchMenuCanvas menuCanvas;

public TouchMenuMIDlet() {
menuCanvas = new TouchMenuCanvas();
menuCanvas.setCommandListener(this);
menuCanvas.addCommand(new Command("Exit", Command.EXIT, 1));
}

public void commandAction(Command c, Displayable d) {
if (c.getCommandType() == Command.EXIT) {
this.notifyDestroyed();
} else {
System.out.println(c.getLabel());
}
}

protected void destroyApp(boolean unconditional) {
}

protected void pauseApp() {
}

protected void startApp() throws MIDletStateChangeException {
Display.getDisplay(this).setCurrent(menuCanvas);
}

}

Below is the application running on DefaultCldcPhone1 of Java Platform Micro Edition SDK 3.0 on both orientations.








We hope this helps. See you next time.

Thursday, December 9, 2010

Touch Screen support

Cros-posted at Technè - Blog de Tecnologia do C.E.S.A.R.

More and more cell phones have touch screens. And when we hear about them the first thing that we think are the most expensive smartphones. Big screens and amazing computer power.

But there are also feature phones with such screens. And amongst them there is a lot supporting Java ME. So, how do you enable an application to get the user's "point"?

Since MIDP 1.0 Canvas already had the following callback methods: pointerPressed, pointerDragged and pointerReleased. And for information there were also hasPointerEvents and hasPointerMotionEvents.

TIP: SDK 3.0 emulator already have touch support, but if you are using WTK 2.5 go to wtklib\devices\DefaultColorPhone\DefaultColorPhone.properties and change the value of touch_screen to true.

To better understand these pointer methods here is a simple example. A Canvas where we can draw freely. It bevahes like Microsoft Paint pencil.

public class TouchCanvas extends Canvas {
// starting point of the line drawn at paint method
int x1 = -1, y1 = -1;
// line final point
int x2, y2;
// flag used to know when to clear the screen
boolean clear = true;

protected void pointerPressed(int x, int y) {
x1 = x;
y1 = y;
}
protected void pointerReleased(int x, int y) {
x1 = y1 = -1;
}
protected void pointerDragged(int x, int y) {
x2 = x;
y2 = y;
repaint();
}

protected void paint(Graphics g) {
if (clear) {
g.setColor(0xffffff); // white
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(0); // black
clear = false;
}

if (x1 >= 0 && y1 >= 0) {
g.drawLine(x1, y1, x2, y2);
x1 = x2;
y1 = y2;
}
}
}

The MIDlet below shows the Canvas and give the user a option to clear the screen.

public class TouchMIDlet extends MIDlet implements CommandListener {

TouchCanvas touchCanvas = new TouchCanvas();

public TouchMIDlet() {
touchCanvas.addCommand(new Command("Clear", Command.OK, 1));
touchCanvas.addCommand(new Command("Exit", Command.EXIT, 1));
touchCanvas.setCommandListener(this);
}

protected void destroyApp(boolean unconditional) { }

protected void pauseApp() { }

protected void startApp() {
Display.getDisplay(this).setCurrent(touchCanvas);
}

public void commandAction(Command c, Displayable d) {
if (c.getCommandType() == Command.EXIT) {
notifyDestroyed();
} else {
touchCanvas.clear = true;
touchCanvas.repaint();
}
}
}

For a more complex sample lets change two classes from Bar Chart to add touch dragging. First we add the following method to BarChart class:
public int getBarWidth (Font font) {
return font.stringWidth(widestName);
}

Now we add the following code to BarChartCanvas:
private int lastX, lastY;
protected void pointerPressed(int x, int y) {
lastX = x;
lastY = y;
}
protected void pointerDragged(int x, int y) {
int w = barChart.getBarWidth(Font.getDefaultFont());
// dragged a distance bigger than the bars width
if (lastX - x >= w || lastY - y >= w) {
barChart.nextBar();
repaint();
lastX = x;
} if (x - lastX >= w || y - lastY >= w) {
barChart.previousBar();
repaint();
lastX = x;
}
}

The smaller the bars be smoother the movement will be. This code also works on landscape mode (SDK 3.0 Emulator: View .. Orientation .. 270).

Another common action is the long press. As we do not have a callback method for it we need to count the time the user is pressing and not dragging or releasing. Below is a way to change our TouchCanvas:

private Timer timer;
protected void showNotify() {
timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
// long press x and y
int lpx = -1, lpy = -1;
public void run() {
if (x1 >= 0) {
if (lpx == x1 && lpy == y1) {
lpx = lpy = -1;
System.out.println("long press");
} else {
lpx = x1;
lpy = y1;
}
}
}
},
// starting now and checking at every second
0, 1000);
}
protected void hideNotify() {
timer.cancel();
timer = null;
}

We hope this helps. See you next time.

Related topics:

Tuesday, December 7, 2010

Bar Chart

Cros-posted at Technè - Blog de Tecnologia do C.E.S.A.R.

When developing a mobile application one must consider screen size fragmentation. If Apple have only two screen sizes for iPhone until now, this is the exception. All other manufacturers vary the screen sizes a lot.

To make developers life easier each technology provides widgets and ways to combine them. But there are times when you need an specific look and fell. What to do? Low level graphics. 2D, 3D, does not matter.

Even if you only have one target screen size it is a good practice to code using proportions. Avoid absolute values of x, y, width and height. Here we show how to do this with a BarChart and Java ME.

It is good to point that there already are components that do this. Our main point here is to present good low level visual programming.

First thing is to have a class for our data.


class ChartEntry {
String name;
float value;
public BarChartEntry(String name, float value) {
this.name = name;
this.value = value;
}
}

And another class to draw the data. Values on top of the bars and labels at the bottom. But if an entry has negative value we do the opposite and draw the value at the bottom.

public class BarChart {
private Vector entries = new Vector();
private float maxValue;
private float minValue;
private String widestName = "";
private int firstBar;

// Every time an entry is added we check for the limits
public void addEntry(BarChartEntry entry) {
entries.addElement(entry);
if (maxValue < entry.value) {
maxValue = entry.value;
}
if (minValue > entry.value) {
minValue = entry.value;
}
if (widestName.length() < entry.name.length()) {
widestName = entry.name;
}
}

public void nextBar () {
this.setFirstBar(this.firstBar +1);
}
public void previousBar () {
this.setFirstBar(this.firstBar -1);
}
private void setFirstBar(int firstBar) {
if (firstBar >= 0 && firstBar < this.entries.size())
this.firstBar = firstBar;
}

// similar to Graphics.drawRect.
// x and y is the top left corner of a rectangle
// w is for width and h is for height
public void draw(Graphics g, int x, int y, int w, int h) {
Font font = g.getFont();
// 2 = value and label strings drawn at top and bottom
int availableHeight = h - (2 * font.getHeight());
int barWidth = font.stringWidth(widestName);
// empty space between bars
int barMargin = w / 25;
int maxBars = (int) Math.floor(w / (barWidth + barMargin));
float valuesRange = maxValue - minValue;
int barOrigin = y + h - font.getHeight();
if (minValue < 0) {
// move origin line up
barOrigin = barOrigin
+ (int) ((availableHeight * minValue) / valuesRange);
}

g.drawLine(x, barOrigin, x + w, barOrigin);

// draw chart entries
BarChartEntry drawEntries [] = new BarChartEntry[this.entries.size()];
int barX = x;
int barCount = 0;
this.entries.copyInto(drawEntries);
for (int i = 0; i < maxBars && this.firstBar + i < drawEntries.length; i++) {
BarChartEntry chartEntry = drawEntries[this.firstBar + i];
// we get current bar height with a simple rule of three
// availableHeight -> valuesRange
// barHeight -> chartEntry.value
int barHeight = (int) ((availableHeight * chartEntry.value) / valuesRange);
int textCenterX = barX + barMargin + (barWidth / 2);
if (barHeight > 0) {
g.drawString(chartEntry.name, textCenterX, barOrigin,
Graphics.HCENTER | Graphics.TOP);
g.fillRect(barX + barMargin, barOrigin - barHeight, barWidth,
barHeight);
g.drawString(String.valueOf(chartEntry.value), textCenterX,
barOrigin - barHeight, Graphics.HCENTER
| Graphics.BOTTOM);
} else {
g.drawString(chartEntry.name, textCenterX, barOrigin,
Graphics.HCENTER | Graphics.BOTTOM);
g.fillRect(barX + barMargin, barOrigin, barWidth, -barHeight);
g.drawString(String.valueOf(chartEntry.value), textCenterX,
barOrigin - barHeight, Graphics.HCENTER | Graphics.TOP);
}
barX = barX + barMargin + barWidth;
barCount++;
}
}
}

Below is a Canvas that shows a bar chart and changes the first bar shown according to pressed keys.

public class BarChartCanvas extends Canvas {

BarChart barChart = new BarChart();

public BarChartCanvas() {
barChart.addEntry(new BarChartEntry("J", 8.0f));
barChart.addEntry(new BarChartEntry("FE", 10.0f));
barChart.addEntry(new BarChartEntry("MAR", 6.0f));
barChart.addEntry(new BarChartEntry("ABRI", 9.0f));
barChart.addEntry(new BarChartEntry("MAIO", -2.0f));
barChart.addEntry(new BarChartEntry("JUNHO", -4.0f));
barChart.addEntry(new BarChartEntry("JULHO", 6.0f));
barChart.addEntry(new BarChartEntry("AGOSTO", 9.0f));
}

protected void paint(Graphics g) {
g.setColor(0xffffff);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(0);
barChart.draw(g, 0, 0, getWidth(), getHeight());
}

protected void keyPressed(int keyCode) {
switch (keyCode) {
case Canvas.KEY_NUM2:
case Canvas.KEY_NUM4:
barChart.previousBar();
break;
case Canvas.KEY_NUM6:
case Canvas.KEY_NUM8:
barChart.nextBar();
break;
default:
switch (super.getGameAction(keyCode)) {
case Canvas.LEFT:
case Canvas.UP:
barChart.previousBar();
break;
case Canvas.RIGHT:
case Canvas.DOWN:
barChart.nextBar();
break;
}
break;
}
this.repaint();
}
}

And the MIDlet used to show this Canvas.

public class BarChartMIDlet extends MIDlet implements
CommandListener {

BarChartCanvas chartCanvas = new BarChartCanvas();

public BarChartMIDlet() {
chartCanvas.addCommand(new Command("Exit", Command.EXIT, 1));
chartCanvas.setCommandListener(this);
}

protected void destroyApp(boolean unconditional) { }

protected void pauseApp() { }

protected void startApp() {
Display.getDisplay(this).setCurrent(chartCanvas);
}

public void commandAction(Command c, Displayable d) {
this.notifyDestroyed();
}

}

Below are screens of this application running on DefaultCldcPhone1 emulator of Java Platform Micro Edition SDK 3.0.






As expected in landscape mode we can draw one more bar. We can change the screen orientation with menu View .. Orientation.
We hope this helps.

Saturday, November 13, 2010

XML Data Binding

Cross-posted (in Portuguese) at Technè - Blog de Tecnologia do C.E.S.A.R.

It is an easy task to load XML data as Java Objects if you are using SE or EE versions. You can use, for example, JAXB or Castor. Is it possible to do it as easily on Java ME? Yes, let me show you how.

The first restriction we have to face is the reduced number of reflection features available on CLDC. It is not possible to use constructors with parameters or call methods. And we should avoid using Class.forName, because we would need to add exceptions to the obfuscator.

This proposal uses two classes to implement the Data Binding Unmarshall. One to represent each tag found and the other to deal with XML parsing details. The code of the first classe is:


public class XMLTag {
// if you do not have enough memory, use lazy
// instantiation on these attributes
private Hashtable attributes = new Hashtable();
private Vector childs = new Vector();

public void setAttributeValue(String attribute, String value) {
if (attribute != null && value != null) {
attributes.put(attribute, value);
}
}
public String getAttributeValue (String attribute) {
return (String) attributes.get(attribute);
}

public void addChild (XMLTag child) {
childs.addElement(child);
}
public Enumeration getChilds () {
return childs.elements();
}
public XMLTag getChildAt (int index) {
return (XMLTag) childs.elementAt(index);
}
}

Lets take an RSS version 2.0 XML sample:

<rss version="2.0">
<channel>
<item>
<title>item title</title>
<link>http://site.com</link>
</item>
<item>
<title>item title 2</title>
<link>http://site.com/2</link>
</item>
</channel>
</rss>

Below are the necessary classes to deal with above tags:

class RSS extends XMLTag {
Channel channel;
public void addChild(XMLTag child) {
if (child instanceof Channel) {
channel = (Channel) child;
}
}
}
class Channel extends XMLTag {
public void addChild(XMLTag child) {
if (child instanceof Item) {
super.addChild(child);
}
}
}
class Item extends XMLTag {
}

We do not need to create classes for title and link tags because these tags are considered attributes of item. Their values will be stored at XMLTag.attributes.

To prevent the use of Class.forName we use a map with tags and classes. The key is a String with the tag name and the value is a Class that extends XMLTag. This map will be passed as parameter to the class responsible for the XML parsing. Below is our map:


Hashtable map = new Hashtable();
map.put("rss", RSS.class);
map.put("channel", Channel.class);
map.put("item", Item.class);

The chosen parser is SAX from JSR 172. It notifies a Handler every time it finds an XML snippet. At the tag begin it calls startElement, at the tag end it calls endElement and when it finds text it calls characters.

A Stack is used to pile each tag found during the parsing. This way, when rss tag is found an RSS instance is added to the Stack. Below are the Stack changes until we find item tag:

As title tag does not have a corresponding Class it is treated as an attribute of the tag at the top of the Stack. The value we receive at characters method is saved in a buffer until we find the end of title tag. When we reach the end of title tag we store the value of buffer using XMLTag.setAttributeValue.

When we reach the end of a mapped tag we remove it from the Stack and add it as a child of the new Stack top.

At the end of the XML data the Stack will be empty and all instances will be related:

Below is the source code of the second class:


class XMLBinder extends org.xml.sax.helpers.DefaultHandler {

private Hashtable map = new Hashtable();
private Stack stack = new Stack();
private XMLTag rootElement;

private String attribute;
private StringBuffer value = new StringBuffer();

/**
* @param map with String keys and XMLTag values
*/
public XMLBinder(Hashtable map) {
Enumeration e = map.keys();
while (e.hasMoreElements()) {
Object key = e.nextElement();
Object tag = map.get(key);
if (validateMapping(key, tag)) {
this.map.put(key, tag);
} else {
throw new IllegalArgumentException("key " + key);
}
}
}
private boolean validateMapping (Object key, Object tag) {
return key instanceof String
&& tag instanceof Class
&& XMLTag.class.isAssignableFrom((Class) tag);
}

public XMLTag unmarshall (InputStream in) throws IOException {
try {
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
parser.parse(in, this);
return rootElement;
} catch (Exception ex) {
throw new IOException("caused by " + ex);
}
}

public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
Class tag = (Class) map.get(qName);
if (tag != null) {
try {
XMLTag newTag = (XMLTag) tag.newInstance();
addAttributesToXMLTag(attributes, newTag);
stack.push(newTag);
} catch (Exception e) {
throw new SAXException("caused by " + e);
}
} else {
attribute = qName;
}
}
private void addAttributesToXMLTag (Attributes attributes, XMLTag newTag) {
if (attributes != null) {
for (int i = attributes.getLength() - 1; i >= 0; i--) {
String attrName = attributes.getQName(i);
String attrValue = attributes.getValue(i);
newTag.setAttributeValue(attrName, attrValue);
}
}
}

public void characters(char[] ch, int start, int length) {
if (attribute != null) {
value.append(ch, start, length);
}
}

public void endElement(String uri, String localName, String qName)
throws SAXException {
if (stack.isEmpty()) {
throw new SAXException("no mapping for " + qName);
}
if (attribute != null && attribute.equals(qName)) {
XMLTag parent = (XMLTag) stack.peek();
parent.setAttributeValue(attribute, value.toString());
attribute = null;
value.setLength(0);
} else {
XMLTag child = (XMLTag) stack.pop();
if (stack.isEmpty() == false) {
XMLTag parent = (XMLTag) stack.peek();
parent.addChild(child);
} else {
rootElement = (XMLTag) child;
}
}
}
}

For example, if we want to read RSS feed from Technè – Blog de Tecnologia do c.e.s.a.r – we use the following code:

String url = "http://techne.cesar.org.br/feed/?lang=en";
InputStream in = Connector.openInputStream(url);
XMLBinder binder = new XMLBinder(map);

rss = (RSS) binder.unmarshall(in);
Enumeration e = rss.channel.getChilds();
while (e.hasMoreElements()) {
Item i = (Item) e.nextElement();
// i.getAttributeValue("title")
// i.getAttributeValue("link")
}

Hope this helps.

Tuesday, November 9, 2010

Check for updates

Cross-posted (in Portuguese) at Technè - Blog de Tecnologia do C.E.S.A.R.

Nowadays it is very common to have a "Check for updates" feature on mobile apps. Specially if they are Android or iPhone apps, because their platform already provide this. And what about Java ME apps? Here is a way.

First we need an official site and a public file in it. For example, http://mysite.com/myapp.txt. This file will have the application latest version number and the url from where it can be downloaded. The following lines will have the description of what have changed:

"[version] [url]

[description]"

Then we add an UI option for this feature, say, "Check for updates". When user selects this option the app downloads the file content with:


InputStream in = Connector.openInputStream("http://mysite.com/myapp.txt");
ByteArrayOutputStream out = new ByteArrayOutputStream();
int i = 0;
while ((i = in.read()) >= 0) {
out.write(i);
}
String content = new String(out.toByteArray());

Current application version may be stored in a constant (final) attribute, but can be read dinamically with MIDlet.getAppProperty(“MIDlet-Version”). To trigger an update the versions need only to be different.

IMPORTANT: description must be used. This clarifies to the user the benefits of updating.

If the user selects to go on with the update the application only need to call
MIDlet.platformRequest(url).
This will open the handset browser to present the url.

As most users do not care about manually checking for new versions we might also add an automatic verifier. If the application is connected this verification can be done in between other connections. If the application is offline it can check how much time has passed since the last check.

Add the following auxiliar methods:


private long byteArrayToLong(byte [] buf) throws IOException {
ByteArrayInputStream in = new ByteArrayInputStream(buf);
DataInputStream dis = new DataInputStream(in);
return dis.readLong();
}
private byte[] longToByteArray(long value) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(out);
dos.writeLong(value);
return out.toByteArray();
}

And the following code on MIDlet.startApp():


RecordStore rs = RecordStore.openRecordStore("lastUpdateCheck", true);
long now = System.currentTimeMillis();
if (rs.getNumRecords() == 0) {
// first application execution
byte[] data = longToByteArray(now);
rs.addRecord(data, 0, data.length);
} else {
final long MONTH = 30*24*60*60*1000;
long before = byteArrayToLong(rs.getRecord(1));
if (now - before > MONTH) {
// show "Check for Update" Alert
// save current time
byte[] data = longToByteArray(now);
rs.setRecord(1, data, 0, data.length);
}
}
rs.closeRecordStore();

When developing an update remember to keep the same RecordStore names and how they are used. Users do not like to loose their configurations. To make sure that this will happen use the same MIDlet-Name and MIDlet-Vendor on jad file.

Combined with Crash Reports, continuous updates add credibility to the application and the development team.
Hope this helps.

Related topics:

Sunday, November 7, 2010

Adding Crash Report

Cross-posted (in Portuguese) at Technè - Blog de Tecnologia do C.E.S.A.R.

Using only Twitter API for Java ME it is possible to add Crash Report to a Java ME application.

Crash Report is a set of information with details of an issue that made an application stop working.
Operating Systems like Microsoft Windows, Mac OS and Linux distros have this feature.
We browsers like Mozilla Firefox and Google Chrome also do. Android too since version 2.2 .

It is recommended that the application has its own Twitter account. This way all development team can follow application reports.

What should go inside a Crash Report?


  1. Application name and version: MIDlet-Name e Version

  2. Name and value of attributes and variables

  3. Exception that caused the issue

  4. Handset model running the app: microedition.platform



Below is a class that will handle all this info:

public class CrashReport {
private StringBuffer report = new StringBuffer();
private CrashReport (String content) {
report.append(content);
}
public CrashReport (MIDlet midlet, String details, Throwable e) {
if (midlet != null) {
report.append(midlet.getAppProperty("MIDlet-Name"));
report.append(' ');
report.append(midlet.getAppProperty("MIDlet-Version"));
report.append(' ');
}
if (details != null) {
report.append(details).append(' ');
}
if (e != null) {
report.append(e).append(' ');
}
String platform = System.getProperty("microedition.platform");
if (platform != null) {
report.append(platform);
}
}
public String toString () {
return report.toString();
}
public byte [] toByteArray () {
return toString().getBytes();
}
public static CrashReport fromByteArray (byte[] data) {
return new CrashReport(new String(data));
}
}


As soon as we get an unexpected exception we show a dialog stating that there was an error as asking if the details can be sent to the development team. If the user answers no, the report is discarded. If the answer is yes, the app tries to send the message right away.

Below is the class responsible to send the report:


public class CrashReportTwitterSender {
private TweetER tweetEr;
public CrashReportTwitterSender () throws IOException {
// check previous post on how to get a credential instance
UserAccountManager accountManager = UserAccountManager.getInstance(credential);
try {
if (accountManager.verifyCredential()) {
tweetEr = TweetER.getInstance(accountManager);
} else {
throw new IOException("Could not verify credential");
}
} catch (LimitExceededException ex) {
throw new IOException("caused by " + ex.getClass().getName());
}
}
public boolean sendReport (CrashReport report) {
try {
tweetEr.post(new Tweet(report.toString()));
return true;
} catch (Exception ex) {
return false;
}
}
}


If an error happens when the app is trying to send the report we ask the user to save it and send later.
If the user answers no, the report is discarded. If the answer is yes, the app saves the report for future usage.

Below is the class responsible to save CrashReport instances at RecordStore:


public class CrashReportStore {
private RecordStore reports;
public CrashReportStore () throws IOException {
try {
reports = RecordStore.openRecordStore("reports", true);
} catch (RecordStoreException ex) {
throw new IOException("caused by " + ex.getClass().getName());
}
}
public void addReport (CrashReport report) throws IOException {
byte [] data = report.toByteArray();
try {
reports.addRecord(data, 0, data.length);
} catch (RecordStoreException ex) {
throw new IOException("caused by " + ex);
}
}
public boolean hasReports () {
try {
return this.reports.getNumRecords() > 0;
} catch (RecordStoreNotOpenException ex) {
return false;
}
}
public void close() throws IOException {
try {
this.reports.closeRecordStore();
} catch (RecordStoreException ex) {
throw new IOException("caused by " + ex);
}
}
public void sendAllReports (CrashReportTwitterSender sender) throws IOException {
try {
RecordEnumeration re = this.reports.enumerateRecords(null, null, false);
while (re.hasNextElement()) {
sendReportAndDeleteRecord(sender, re.nextRecordId());
}
} catch (Exception ex) {
throw new IOException("caused by " + ex);
}
}
private void sendReportAndDeleteRecord (CrashReportTwitterSender sender, int id)
throws RecordStoreException
{
byte [] data = this.reports.getRecord(id);
CrashReport report = CrashReport.fromByteArray(data);
if (sender.sendReport(report)) {
this.reports.deleteRecord(id);
}
}
}


Next time the application is started it can check if CrashReportStore.hasReports().
If there is none we do not need to bother. If there is some, we ask the user to send the reports now or later.

Below is a sample code to test what we wrote here. It should be pasted at some class that extends MIDlet.


CrashReport report = new CrashReport(this, "crash report test", null);
CrashReportTwitterSender sender = new CrashReportTwitterSender();
if (sender.sendReport(report) == false) {
CrashReportStore store = new CrashReportStore();
store.addReport(report);
store.close();
}


Related topics:

Friday, October 22, 2010

Saturday, September 25, 2010

Nine Slice Scalling

Below is the public contract (constructor and methods) of my NineSliceImage component. It may be used to draw Buttons, Title bars and Frames.

import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;

public class NineSliceImage {

public NineSliceImage (Image img, int vMargin, int hMargin) { ... }

public void paintFrameAround (Graphics g, int x, int y, int width, int height) { ... }
public Image createFrameFor (int width, int height) { ... }

}

And below is a sample usage:

class MyClass extends Canvas {
MyClass () throws java.io.IOException {
Image i = Image.createImage("/pattern.PNG");
this.nsi = new NineSliceImage(i, 5, 5);
}
protected void paint(Graphics g) {
nsi.paintFrameAround(g, 20, 20, 20, 20);
}
}

Where patter is the following image:

And the resulting frame is:

Full component source code below:

import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;

public class NineSliceImage {

private Image originalImage;
private int verticalMargin;
private int horizontalMargin;
int middlePieceHeight;
int middlePieceWidth;

public NineSliceImage (Image img, int vMargin, int hMargin) {
validateConstructorParameters(img, vMargin, hMargin);
setAttributesValuesFor(img, vMargin, hMargin);
}

public Image createFrameFor (int width, int height) {
validateWidthAndHeight(width, height);

int middleHeight = getMiddleHeight(height);
int middleWidth = getMiddleWidth(width);
Image frame = Image.createImage(middleWidth + (2 * this.horizontalMargin),
middleHeight + (2 * this.verticalMargin));
Graphics g = frame.getGraphics();

this.paintFrameAround(g, horizontalMargin, verticalMargin, middleWidth, middleHeight);

return frame;
}

public void paintFrameAround (Graphics g, int x, int y, int width, int height) {
validateWidthAndHeight(width, height);

int middleWidth = getMiddleWidth(width);
int middleHeight = getMiddleWidth(height);
int totalWidth = middleWidth + (2 * this.horizontalMargin);
int totalHeight = middleHeight + (2 * this.verticalMargin);
x = x - ((middleWidth - width) / 2) - this.horizontalMargin;
y = y - ((middleHeight - height) / 2) - this.verticalMargin;

drawFrameCorners(g, x, y, middleWidth, middleHeight, totalWidth, totalHeight);
drawTopAndBottomLines(g, x, y, width, totalHeight);
drawLeftAndRightColumns(g, x, y, height, totalWidth);
}

private void validateConstructorParameters (Image img, int vMargin, int hMargin) {
if (img == null || vMargin <= 0 || hMargin <= 0) {
throw new IllegalArgumentException();
}
if (vMargin >= img.getHeight() / 2) {
throw new IllegalArgumentException();
}
if (hMargin >= img.getWidth() / 2) {
throw new IllegalArgumentException();
}
}

private void setAttributesValuesFor (Image img, int vMargin, int hMargin) {
this.originalImage = img;
this.verticalMargin = vMargin;
this.horizontalMargin = hMargin;
this.middlePieceHeight = this.originalImage.getHeight()
- (2 * this.verticalMargin);
this.middlePieceWidth = this.originalImage.getWidth()
- (2 * this.horizontalMargin);
}

private void validateWidthAndHeight (int width, int height) {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException();
}
}

private int getMiddleWidth (int width) {
return getHorizontalRepetitions(width) * middlePieceWidth;
}

private int getHorizontalRepetitions (int width) {
int horizontalRepetitions = width / middlePieceWidth;

if (width % middlePieceWidth > 0) {
horizontalRepetitions++;
}

return horizontalRepetitions;
}

private int getMiddleHeight (int height) {
return getVerticalRepetitions(height) * middlePieceHeight;
}

private int getVerticalRepetitions (int height) {
int verticalRepetitions = height / middlePieceHeight;

if (height % middlePieceHeight > 0) {
verticalRepetitions++;
}

return verticalRepetitions;
}

private void drawFrameCorners (Graphics g, int x, int y, int middleWidth,
int middleHeight, int totalWidth, int totalHeight) {
g.setClip(x, y,
this.horizontalMargin, this.verticalMargin);
g.drawImage(this.originalImage, x, y,
Graphics.LEFT | Graphics.TOP);
g.setClip(x, y + this.verticalMargin + middleHeight,
this.horizontalMargin, this.verticalMargin);
g.drawImage(this.originalImage, x, y + totalHeight,
Graphics.LEFT | Graphics.BOTTOM);
g.setClip(x + this.horizontalMargin + middleWidth, y,
this.horizontalMargin, this.verticalMargin);
g.drawImage(this.originalImage, x + totalWidth, y,
Graphics.RIGHT | Graphics.TOP);
g.setClip(x + this.horizontalMargin + middleWidth, y + this.verticalMargin + middleHeight,
this.horizontalMargin, this.verticalMargin);
g.drawImage(this.originalImage, x + totalWidth, y + totalHeight,
Graphics.RIGHT | Graphics.BOTTOM);
}

private void drawTopAndBottomLines (Graphics g, int x, int y, int width, int totalHeight) {
int horizontalRepetitions = getHorizontalRepetitions(width);
x += horizontalMargin;
for (int i = 0; i < horizontalRepetitions; i++) {
g.setClip(x, y, middlePieceWidth, this.verticalMargin);
g.drawImage(this.originalImage, x - this.horizontalMargin, y,
Graphics.LEFT | Graphics.TOP);
g.setClip(x, y + totalHeight - this.verticalMargin,
middlePieceWidth, this.verticalMargin);
g.drawImage(this.originalImage, x - this.horizontalMargin,
y + totalHeight - this.verticalMargin,
Graphics.LEFT | Graphics.TOP);
x += middlePieceWidth;
}
}

private void drawLeftAndRightColumns (Graphics g, int x, int y, int height, int totalWidth) {
int verticalRepetitions = getVerticalRepetitions(height);

y += this.verticalMargin;
for (int i = 0; i < verticalRepetitions; i++) {
g.setClip(x, y, this.horizontalMargin, middlePieceHeight);
g.drawImage(this.originalImage, x, y - this.verticalMargin,
Graphics.LEFT | Graphics.TOP);
g.setClip(x + totalWidth - this.horizontalMargin, y,
this.horizontalMargin, middlePieceHeight);
g.drawImage(this.originalImage, x + totalWidth - this.horizontalMargin,
y - this.verticalMargin,
Graphics.LEFT | Graphics.TOP);
y += middlePieceHeight;
}
}

}

Sunday, August 22, 2010

Manipulate PNG palette

I am not a graphic designer, thus I don't know how to create fancy icons/images
for my apps and Microsoft
Paint
is just enough for my skills.

For all my apps I've used black-and-white PNG files without transparency and that is ok, but... black and white?! I should be
adding colors.

Image class does not have a method to easily change colors. A workaround could be to call getRGB method and iterate the rgbData array.
A better way is to read the file content, change the palette bytes and create an image from the resulting data. First lets create a helper
method to read a whole InputStream and return a byte array with its content:


private byte [] readStream (InputStream in)
throws java.io.IOException
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte [] buff = new byte [1024];
int size = in.read(buff);

while (size >= 0) {
baos.write(buff, 0, size);
size = in.read(buff);
}

return baos.toByteArray();
}

Next we have to find where is the palette chunk inside the byte array. Here is another helper method:


// return index where P of PLTE is found at buff array or -1
private int getPLTEIndex (byte [] buff) {
int i = -1;
// 4 == "PLTE".size()
if (buff != null && buff.length >= 4) {
boolean foundPalete = false;
boolean endOfBuff = false;
do {
i++;
foundPalete = buff[i] == 'P'
&& buff[i +1] == 'L'
&& buff[i +2] == 'T'
&& buff[i +3] == 'E';
endOfBuff = (i +4 >= buff.length);
} while (!foundPalete && !endOfBuff);
if (endOfBuff) {
i = -1;
}
}
return i;
}

And, finally, a method to change a color from the palette of PNG files with color type 3:


private byte [] setRGBColor (byte [] buff, int colorIndex, int colorNewValue) {
int i = getPLTEIndex(buff);
if (i >= 0) {
i += 4; // 4 == "PLTE".size()
i += (colorIndex * 3); // 3 == RGB bytes
if (i + 3 <= buff.length) {
buff[i] = (byte) (((colorNewValue & 0x00ff0000) >> 16) & 0xff);
buff[i +1] = (byte) (((colorNewValue & 0x0000ff00) >> 8) & 0xff);
buff[i +2] = (byte) ((colorNewValue & 0x000000ff) & 0xff);
}
}
return buff;
}

Below is a sample on how to use all the methods:

InputStream in = getClass().getResourceAsStream("/e.png");
try {
byte [] buff = readStream(in);
Image original = Image.createImage(buff, 0, buff.length);
buff = setRGBColor(buff, 0, 0x00ff0000); // set 1st color to red
buff = setRGBColor(buff, 1, 0x0000ff00); // set 2nd color to green
Image updated = Image.createImage(buff, 0, buff.length);
} catch (IOException ex) {
ex.printStackTrace();
}

Related topics:

Sunday, July 11, 2010

Blog audience 2T10

From 2010 March, 15 to June, 14 this blog had 215 visits from 54 countries.

The top 10 countries are:


  • Brazil: 34

  • Indonesia: 25

  • India: 15

  • United States: 11

  • Italy: 7

  • Russia: 7

  • Germany: 6

  • Portugual: 6

  • Poland: 4



Romania, United Kingdom, Sweden and Canada has left the top 10 list. New to the list are Italy, Russia and Poland.

Related topics:

Saturday, July 10, 2010

Alert setTimeout FOREVER

So you are showing error messages, but no emails are coming in. Is your application faultless? Or all users do not bother to write down some lines and click send?

Maybe your users do not have a chance to see the error message because it was automatically dismissed before they could read it. How can it happen? - you ask.

Not calling setTimeout method on an Alert object will leave the timeout as the default value. Which value? The Virtual Machine implementation will decide, but the more important part: it is not Alert.FOREVER!

From Alert documentation (the very first line): "An alert is a screen that shows data to the user and waits for a certain period of time before proceeding to the next Displayable."

See? If you, sloppy developer, does not set a timeout the Alert will be automatically dismissed and your user will blink and ask "What was that?".

To the rescue an updated version of showExceptionAlert method below:


public void showExceptionAlert (Exception e) {
StringBuffer msg = new StringBuffer(getErrorMessage());
Display d = Display.getDisplay(this);

msg.append(e.getClass().getName());
if (e.getMessage() != null) {
msg.append("\n").append(e.getMessage());
}

Alert a = new Alert(getErrorTitle(), msg.toString(),
null /*alertImage*/, AlertType.ERROR);
a.setTimeout(Alert.FOREVER);
d.setCurrent(a, d.getCurrent());
}


getErrorMessage() will load the message prefix and getErrorTitle() will load the Alert title based on current locale (i18n).

Related topics:

Monday, May 31, 2010

Books: version 1.4

Yesterday I shared Books version 1.4, new features are listed below on Related Topics.
I also updated my blog entry on Books.

Related Topics:

Saturday, April 24, 2010

Avoiding OutOfMemoryError

Some Java ME applications load all their resources from its own jar file, but other apps load resources dinamically, for example,
from a network connection.

My Books application can open text files using an
Open File Dialog I have created.
But what happens if a file is too big to open?

If the Java Virtual Machine raises an Exception current version (1.3)
will show an error message requesting the user to send me an email with the details: exception type and message (if available).
Because of this I have already received many emails with IOException details.

But I was not treating Java Errors, only Exceptions. Because of this some users might see "Unhandled Error" messages shown by Java
Virtual Machine and will not know what to do. Next version will fix this and show a better message.
That is a good fix but does not solve the problem.

One thing I have learned is that reacting to Exceptions and Errors is not a good approach. The best thing to do is to antecipate
such situations and a situation we can antecipate is the OutOfMemoryError.

First, we need to know how much free memory is available. This can be done with
Runtime class.
Before trying to open a file I check if its size fits the current available memory with the following method:


boolean isThereEnoughMemoryToOpen (FileConnection file)
throws IOException
{
return Runtime.getRuntime().freeMemory() < file.fileSize();
}


If the method returns false (or throw an Exception) I do not open the file and show an error message to the user.

Related topics:

Saturday, April 17, 2010

Using pointerDragged method

Some cell phones do not have a keyboard. They only have a touch screen.
In this case, if you have a Canvas
screen on your application you will have to rely only on pointer methods.

This fact came to me because a user of my Books
application have such a phone and sent me an email informing that he could not go back to a previous page.
I checked the application source code and found out the reason. The code used pointer methods only to go forward, never backwards.
I have developed (but not released) a solution using pointerDragged and here are the details.

For functional backward compatibility I had to differ from two user actions:


  • touch and release - methods call order is: first pointerPressed and then pointerReleased.
  • touch, dragg and release - methods call order is: first pointerPressed, then a lot of pointerDragged calls and then pointerReleased.

A new boolean attribute was added: pointerDragging. This attribute receives true at pointerDragged and is checked on pointerReleased.
If it is false I go forward to next book page. Below is how I use this attribute:

protected void pointerDragged(int x, int y) {
pointerDragging = true;
// ... more code. I will show this in a while
}
protected void pointerReleased(int x, int y) {
if (pointerDragging == false) {
// move the text down. Torwards the end of the file
this.keyPressed(Canvas.KEY_NUM8);
}
pointerDragging = false;
}


Now I had to add the new user action: text dragging. To help know where the dragging is moving a new class was created: Point.

class Point {

int x, y;

void set (int x, int y) {
this.x = x;
this.y = y;
}

boolean rightOf (int x) { return x < this.x; }

boolean leftOf (int x) { return x > this.x; }

boolean above (int y) { return y > this.y; }

boolean below (int y) { return y < this.y; }
}

A new Point attribute was added: lastPoint. It is initiated on constructor and used on pointerPressed to store the first point the user touched the screen.

protected void pointerPressed(int x, int y) {
this.lastPoint.set(x, y);
}

As pointerDragged method is called a lot of times I could not move to previous/next page at each call to this method.
I had to check if current x,y point was far enough from the lastPoint stored on pointerPressed.
And "far enough" came to me as font.getHeight(), where font is the Font instance used to draw the text on paint method.

Below is my final source code for pointerDragged method:


protected void pointerDragged(int x, int y) {
pointerDragging = true;

if (this.lastPoint.rightOf(x + font.getHeight())
|| this.lastPoint.above(y - font.getHeight())) {
// move the text up. Torwards the beginning of the file
this.keyPressed(Canvas.KEY_NUM2);
this.lastPoint.set(x, y);
}
else if (this.lastPoint.leftOf(x - font.getHeight())
|| this.lastPoint.below(y + font.getHeight())){
// move the text down. Torwards the end of the file
this.keyPressed(Canvas.KEY_NUM8);
this.lastPoint.set(x, y);
}
}

I check both x and y values because the user will be able to move between pages using up/down or left/right touch movements.
Next release of Books is planned for this week. Stay tuned!

Related topics:

Monday, March 15, 2010

Blog audience 1T10

Since 2009 December, 15 this blog had 241 visits from 59 countries.

Visiting countries from 2009 December, 15 to 2010 March, 14

The top 10 countries are:


  • Brazil: 37

  • India: 22

  • United States: 18

  • Indonesia: 12

  • Romania: 8

  • Germany: 8

  • United Kingdom: 6

  • Sweden: 6

  • Portugual: 5

  • Canada: 5



Poland, Turkey and Russia has left the top 10 list. New to the list are Indonesia, Romania and Canada.

Related topics:

Saturday, March 13, 2010

Adapting to sizeChanged

Cell phones with accelerometers may automatically change the screen orientation from portrait to landscape.
If you use LCDUI screens on your application there is nothing to worry.
But if you use Canvas these changes are notified by sizeChanged method.

At my Books application I show text in fullscreen landscape.
The user will need to rotate the handset to read properly.
If the cell phone automatically change the screen orientation the user will not be able to read.

What I had to do is to implement sizeChanged method to check if Sprite rotation is needed or not:


public void sizeChanged (int w, int h) {
if (sprite == null) return;
if (super.getWidth() < super.getHeight()) { // portrait screen
sprite.setTransform(Sprite.TRANS_ROT90);
} else {
sprite.setTransform(Sprite.TRANS_NONE);
}
sprite.setPosition(0, 0);
}

Related Topics: