http://www.developer.com/

Back to article

Building a Java ME CDC Application Using the SavaJe Phone


October 24, 2006

If you were at JavaOne this year, you couldn't have missed the new all-Java mobile phone platform announced by SavaJe. Along with the platform, they made available a developer's edition of a new phone based on SavaJe, called the Jasper S20. The phone was named "device of the show" for JavaOne 2006.

Of course, for a Java developer, the most interesting aspect of the device is that nearly all of the platform is itself written in Java. Because all of the phone's services, applications, and APIs are in Java, it is that much easier to write compelling applications that take advantage of the device's capabilities and provide features that users expect.

Java Micro Edition JSR Soup

Java ME has been running on phones for quite some time now, starting with hardware that was barely capable and evolving as technology improved. With each new step in platform capabilities, there were JSRs to specify the corresponding Java APIs. The SavaJe platform is no slouch, and implements so many of these JSRs that I can only list a few.

The more limited collection of APIs that most phones implement revolve around the Connected Limited Device Configuration (CLDC) and the MIDP profile, with Midlets being the unit of application deployment. SavaJe implements the following JSRs in CLDC mode, primarily defined by JSR-185:

Java Technology for the Wireless Industry (JSR-185):

  • CLDC 1.1 () (JSR-139) defines language, networking, and utility APIs
  • MIDP 2.0 (JSR-118) defines midlets...
  • J2ME Wireless Messaging API 2.0 (JSR-205, JSR-120)
  • MMAPI 1.1 (JSR-135) mobile media API
  • Mobile 3D Graphics for J2ME (JSR-184)
  • The PDA optional packages for Personal Information Management (PIM) and file access (JSR-75)
  • J2ME Bluetooth (JSR-82) javax.bluetooth APIs only (not javax.obex)

Now, even more interesting is the Connected Device Configuration (CDC), with Swing Xlets being the unit of application deployment. SavaJe is unique as a phone platform in providing so much functionality to Java developers. Just take a look at all these goodies!

  • J2ME Mobile 3D Graphics (JSR-184)
  • J2ME Wireless Messaging 2.0 (JSR-205, JSR-120)
  • J2ME Mobile Media (JSR-135)
  • XML Parsing option from Web Services (JSR-172)
  • The javax.microedition.xlet package from Personal Basis Profile 1.1 (JSR-185)
  • J2ME PDA optional packages for Personal Information Management (PIM) and file access (JSR-75)
  • J2ME Bluetooth/OBEX (JSR-82)
  • Advanced Graphics and User Interface (AGUI) Package for J2ME (JSR-209)
  • J2ME updates for J2SE 1.4 compatibility: Personal Basis Profile 1.1 (JSR-217), J2ME CDC 1.1 (JSR-218), and J2ME Foundation Profile 1.1 (JSR-219)
  • Application Protocol Data Units (APDU) support from J2ME Security and Trust Services (JSR-177)
  • J2ME JAIN Presence and Instant Messaging (JSR-186, JSR-187)
  • J2ME Advanced Multimedia (JSR-234)

This article will focus on Xlets and AGUI. Xlets will give you the means of deploying your application and controlling its lifecycle; AGUI will give you the APIs to create a snazzy look.

Java ME CDC Tools

The most conventional approach to developing Java ME is to use a phone platform emulator combined with plugins for an IDE. For SavaJe as well as any other CDC device, Sun provides a CDC Toolkit that contains the emulators and libraries, along with the Mobility Pack for CDC for NetBeans 5.5. This is a nice package, and provides a turnkey development cycle.

Unfortunately, Sun's CDC Toolkit is only available on Windows, which is a problem for me because I have only Mac and Linux machines available. Although it is possible to pull pieces out of the toolkit and assemble a working environment, I chose to drop down to the metal, creating the necessary bundles by hand and developing in my preferred IDE: Eclipse. Yes, I can hear the Sun engineers gnashing their teeth, but you can't knock an approach that works, and until the CDC Toolkit works on Mac or Linux, this is the best option.

About the Application

I hunted around awhile, trying to think of an interesting killer application to write for the SavaJe. There are so many possibilities given the rich APIs, but in the end I thought I'd pick something small, fun, and easy to get started. I've been hooked on the puzzle game Sudoku recently, and thought the phone display and keyboard would make perfect interfaces. (And allow me to sneak in play time almost anywhere.) The application that you'll build is a simple Sudoku playing GUI that allows predefined puzzles to be solved using the phone's joystick and number keys.

The display, living in a desktop testing frame, looks like this:

and is implemented using a simple JPanel and Java 2D methods. The yellow square indicates the selection where numbers are entered using the number pad, and is moved using the joystick that sends arrow key events.

Adding puzzle generation, fancier game play, and wizzy graphics is something I'll have to leave to a future time. In this article, I'll keep it simple and focus on the details of getting Xlets running on the phone. All of the source code for this application is available as open source on Google Code. Follow the directions there to check out the project source.

Setting Up the Development Environment

The first thing you'll need to get started is Eclipse. You can download Eclipse 3.2.1 for your OS. Versions are readily available for Windows, Mac, and Linux. Install it in the default location with default settings. No special plugins are required, but one easy way to get the project source is to use the Subclipse plugin that can be found at http://subclipse.tigris.org/.

The next thing you'll need is the SavaJe libraries, bundled into the one: jar SavaJeDeveloper.jar. These are provided on the developer CD that comes with the phone, or can be downloaded from saveje.com once you've registered as a developer.

Organizing the Projects

To simplify development, and make testing easier, I have broken the application down into four small projects:

  • sudoku-eng: The "engine" that knows how to load and save games
  • sudoku-ui: The core UI written using portable Swing/Java 2D. Depends on sudoku-eng.
  • sudoku-se: A Java SE-based main and frame to help with testing. Depends on sudoku-ui.
  • sudoku-xlet: The Xlet-based application and frame for the phone. Depends on sudoku-ui.

This is how they look in the Eclipse project pane.

Building and running sudoku-se runs the Java SE application main() that creates a Swing frame for testing. This is a quick and easy way to try out new game features on the desktop without the overhead of deploying to the phone.

Building sudoku-xlet creates all of the Java binaries needed to run on the phone. The last few Xlet bundling steps are done by using a shell script to zip up the classes from the three projects into a jar, and copy it and other files into the right layout for deployment.

Each of the projects has a src folder for the Java and other sources. Here, you can see the two additional files needed for Xlets: bundle.jnlp, which controls the bundle contents and startup class; and bundle.policy, which requests security permissions for the bundle. I chose to name the Java packages to match the project names to make things easier to remember, but this is not critical.

One thing that is crucial, though, and all too easy to overlook, is that the phone only supports Java 1.4.2.

Note: If you build against Java 1.5 libraries or use the Java 5 compiler setting, really bad things will happen and your Xlet will mysteriously refuse to start. You must set the compiler to 1.4.2 and select the 1.4.2 JRE to build against.

A Bundle of Xlets

The building, bundling, and deploying of an Xlet for SavaJe is refreshingly simple. The first step is to create an Xlet bundles directory on the phone's SD memory card. The next step is to create a sub directory under that for each Xlet. When the SD card is inserted into the phone, all of these Xlets will become available under the "My Applications" section of the phone's menu. In your case, the tree looks like this:

/savaJe/bundles/
   Sudoku/
      bundle.jnlp
      bundle.policy
      lib/
         classes.jar

The bundle.jnlp file is an XML file with a few interesting values that must be defined. For this application, the file looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<jnlp codebase="sb:///Sudoku/">

   <resources>
      <j2se version="1.4+"/>
      <jar href="lib/classes.jar"/>
   </resources>

   <information>
      <title>Sudoku</title>
      <vendor>Carl</vendor>
      <description>Sudoku Puzzle</description>
   </information>

   <application-desc main-class="org.quinn.sudoku.xlet.SudokuXlet">
   </application-desc>
</jnlp>

The codebase attribute defines a URI that is cross-referenced in the policy file. The two uses must match, but are otherwise unimportant. The resources elements describe which JRE the application needs and where its jar lives. It can be left with the default values shown above for most applications. The information elements should be filled in for your application and are pretty self-explanatory. These may be shown to the user when browsing installed Xlets. Finally, the application-desc element indicates which class in the jar is the actual Xlet to launch.

The bundle.policy file is a standard Java policy file for the Xlet. For this application, you simply request full permissions for all classes in the jar.

grant codeBase "sb:///Sudoku/lib/classes.jar" {
   permission java.security.AllPermission;
};

The classes.jar file is a plain Java jar containing all the Xlet's classes built using the regular tools. No special manifest is needed.

For my environment, I put together a simple shell script to build the bundle tree and place it under a dist directory:

# dist is where we'll build our complete distribution image
rm -rf dist
mkdir -p dist/lib

# staging is where we merge the class tree to build the jar
rm -rf staging
mkdir -p staging/org/quinn
cp -R classes/org/quinn/* staging/org/quinn
cp -R ../sudoku-ui/classes/org/quinn/* staging/org/quinn
cp -R ../sudoku-eng/classes/org/quinn/* staging/org/quinn

cd staging
zip -0 -r ../dist/lib/classes.jar *
cd ..
cp src/bundle.* dist

You can see where you first aggregate all of the classes under staging so that they can be zipped together into classes.jar. The jar plus the two bundle files then get copied into the dist tree. The final step of deploying to the phone is to simply copy this tree to the phone's SD card. I use this one-liner:

cp -R dist/* /Volumes/PHONESD/savaJe/bundles/Sudoku/

You'll likely need to adjust the paths to fit your system setup and the volume name of your SD card.

Coding the Xlet and the Main

I started out with the SavaJe HelloWorld sample Xlet, and turned it into a reusable abstract superclass to simplify writing small Xlets. All Xlets must implement the javax.microedition.xlet.Xlet interface that defines the four methods explained below, and this abstract class takes care of almost all of the work.

public abstract class SimpleXlet implements Xlet {

The first thing that happens after your Xlet is constructed is that your initXlet method is called. It is called exactly once during application startup and provides the context object for the Xlet. You'll use the context to retrieve your visual AWT Container object, and squirrel it away for later. Not much else should be done during this early phase of initialization.

public void initXlet(final XletContext xletContext)
   context = xletContext;
   if (rootContainer == null) {
      try {
         rootContainer = context.getContainer();

The next thing that happens during startup is that your startXlet method is called. This is where you can really kick off your Xlet by creating its main screen and getting it situated in the root container and making both windows visible.

public void startXlet() throws XletStateChangeException {
   if (screen == null) {
      EventQueue.invokeAndWait(new Runnable() {
         public void run() {
            screen = newScreen();
            screen.setVisible(true);
            rootContainer.add(screen);
            // This is needed - or nothing will be displayed.
            rootContainer.setVisible(true);
         }
      });

Your SimpleXlet doesn't actually know how to make a screen that does anything, so I have left that method abstract. It will need to be overridden in a real subclass that then is responsible for returning a fully populated InternalFrame that you add to the root container above.

protected abstract JInternalFrame newScreen();

An interesting thing to note with the Xlet lifecycle is that the application can be paused and restarted any number of times, resulting in pauseXlet and then startXlet being called alternately. This corresponds to the user switching away to another application and returning. In your simple Xlet, you'll take the easy road and discard your whole screen on pause, and rebuild it later on start as needed. You could, instead, choose to keep some of it around, but in general when an Xlet is paused it should be a good citizen and release resources that it doesn't really need.

public void pauseXlet() {
   EventQueue.invokeAndWait(new Runnable() {
      public void run() {
         rootContainer.remove(screen);
         screen = null;
      }
   });

The last call made into your Xlet is to the destroyXlet method. Here you could do any remaining cleanup that you didn't do in pauseXlet. In your case, though, you've already cleaned up and don't need to do anything.

public void destroyXlet(final boolean unconditional)

When your Xlet wants to shut itself down, there are a couple of things that it has to do, and so you've bundled them into a single handy method. The root container needs to be hidden, and the context notified that you are destroying yourself. That will trigger the shutdown sequence and everything will get cleaned up.

protected void exit() {
   rootContainer.setVisible(false);
   context.notifyDestroyed();
}

Your concrete SudokuXlet subclass extends the SimpleXlet and only needs to override the one abstract method to create your frame.

public class SudokuXlet extends SimpleXlet {

Your real newScreen implementation creates a JInternalFrame, adds a menubar with menus, and pops an instance of the actual SudokuPanel in the center. The phone itself has two menus, one for each of the soft keys. The left menu added is a generic exit item that delegates to your exit method defined above. The right menu is a game menu that is retrieved from the SudokuPanel, allowing the panel to remain unaware of its container's type or where its game menu actually goes.

protected JInternalFrame newScreen() {
   JInternalFrame screen = new JInternalFrame();

   screen.setTitle("Micro Sudoku!");
   final SudokuPanel panel = new SudokuPanel();
   screen.getContentPane().add(panel, BorderLayout.CENTER);

   JMenuBar menubar = new JMenuBar();
   menubar.add(new JMenu(new AbstractAction("Exit") {
      public void actionPerformed(final ActionEvent e) {
         System.out.println("Exiting...");
         exit();
      }
   }));
   menubar.add(panel.newGameMenu());
   screen.setJMenuBar(menubar);

   return screen;
}

Coding the Test Main

For the Java SE test application, the same general steps are followed. There, though, the entry point is main, and the panel's container is a JFrame. The meat of this single class looks remarkably similar to the Xlet screen construction, using the same SudokuPanel in the same way.

private void createAndShowGUI() {
   JFrame frame = new JFrame("Micro Sudoku");
   frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

   SudokuPanel panel = new SudokuPanel();
   frame.getContentPane().add(panel, BorderLayout.CENTER);

   JMenuBar menubar = new JMenuBar();
   menubar.add(newFileMenu(frame));
   menubar.add(panel.newGameMenu());
   frame.setJMenuBar(menubar);

   frame.pack();
   frame.setVisible(true);
}

public static void main(String[] args) {
   javax.swing.SwingUtilities.invokeLater(new Runnable() {
      public void run() {
         new GameApp().createAndShowGUI();
      }
   });
}

And, the best part is that the actual game SudokuPanel is blissfully unaware of the application launch details or the specific frame class. Sweet.

Create the UI JPanel

The game's main UI is implemented using a straightforward Swing JPanel subclass.

public class SudokuPanel extends JPanel {

On construction, you set up the focus properties and add a keyboard listener to handle navigation and digit entry. The arrow keys update the selection row or column, and the digit keys modify the user's answer for the currently selected cell. After any of these changes, you call repaint() to get your changes painted.

public SudokuPanel() {
   setFocusable(true);
   addKeyListener(new KeyAdapter() {
      public void keyPressed(KeyEvent e) {
         switch (e.getKeyCode()) {
         case KeyEvent.VK_LEFT:  if (selc > 0) selc--; break;
         ...
         case KeyEvent.VK_9:
            if (!puzzle.given[selr][selc]) {
               puzzle.answer[selr][selc] = e.getKeyCode()
                  - KeyEvent.VK_1 + 1;
            }
      }
      repaint();

Before the above listener is ever invoked, you need to make sure that you have a valid Puzzle instance created. You call over to the Puzzles factory class to create a puzzle instance for you, identified by an index.

puzzle = Puzzles.newPuzzle(puzIndex);

When the user has finished a puzzle (or gets stuck and gives up), they can use the Game/New menu item that invokes this method to create a new Puzzle.

void newPuzzle() {
   puzzle = Puzzles.newPuzzle(++puzIndex);
   invalidate();
   repaint();
}

Implementing getPreferredSize is important when running on the desktop so that the outer frame ends up with a reasonable size and not squished down to nothing.

public Dimension getPreferredSize() {
   new Dimension(9*PCW+2*MW+3, 9*PCW+2*MH+3);
}

The paintComponent method is the heart of the panel and performs all of the rendering. The first step in painting is to grab the graphics context in its Graphics2D form. Once you have that, you can use regular Java 2D calls to paint your panel.

public void paintComponent(final Graphics g) {
   final Graphics2D graphics = (Graphics2D) g;
   final int panelw = getWidth();
   final int panelh = getHeight();

After a few dimension calculations, you paint the white background and the yellow selection cursor:

// background rectangle
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, panelw, panelh);

// selection rectangle
int sx = origx + selc*cellw;
int sy = origy + selr*cellh;
graphics.setColor(Color.YELLOW);
graphics.fillRect(sx,sy,cellw,cellh);

Then the black grid lines:

// grid lines
graphics.setColor(Color.BLACK);
for (int v = 0; v < 10; v++) {
   graphics.drawLine(origx+v*cellw,origy,origx+v*cellw,
                     origy+boardh);
   if (v%3 == 0) {

   graphics.drawLine(origx+v*cellw+1,origy,origx+v*cellw+1,
                     origy+boardh+1);

   graphics.drawLine(origx+v*cellw+2,origy,origx+v*cellw+2,
                     origy+boardh+1);
   }
}
for (int h = 0; h < 10; h++) {
   graphics.drawLine(origx,origy+h*cellh,origx+boardw,
                     origy+h*cellh);
   if (h%3 == 0) {
      graphics.drawLine(origx,origy+h*cellh+1,origx+boardw+1,
                        origy+h*cellh+1);

      graphics.drawLine(origx,origy+h*cellh+2,origx+boardw+1,
                        origy+h*cellh+2);
   }
}

Finally, you render the actual digits. You distinguish the givens from the guesses by painting the former in black and the latter in blue. You also add a quick scan for obvious invalid moves and paint those in red.

// cell labels
graphics.setFont(new Font("Arial", Font.BOLD,
   (Math.min(cellh,cellw)*8)/10));
FontMetrics fm = graphics.getFontMetrics();
int lh = fm.getHeight() - fm.getLeading() - fm.getDescent();

// paint the cells
for (int r = 0; r < 9; r++) {
   for (int c = 0; c < 9; c++) {
      int val = puzzle.answer[r][c];
      if (val > 0) {
         String label = Integer.toString(val);
         int lw = fm.stringWidth(label);
         int lx = origx + c*cellw + cellw/2 - lw/2;
         int ly = origy + r*cellh + cellh/2 + lh/2;
         Color tcolor = puzzle.given[r][c]
            ? Color.BLACK
            : (r == selr && c ==
               selc && !isMoveValid(puzzle.answer, r, c))
            ? Color.RED : Color.BLUE;
         graphics.setColor(tcolor);
         graphics.drawString(label, lx, ly);
      }
   }
}

Create the Game Engine

The Puzzles class is a simple singleton that serves as a Puzzle factory for ten predefined puzzles.

public class Puzzles {

   /**
   * Predefined puzzles. Each string is one puzzle, each full row
     separated by |'s.
   */
   private static final String[] puzzles = {
   "..6...2..|4..197..6|3..6.5..4|..15689..|.........|..94726..
      |8..2.9..7|6..341..9|..5...1..",
   ...

   public static Puzzle newPuzzle(int index) {
      return new Puzzle(puzzles[index % puzzles.length]);
   }
}

I generated and exported the puzzles by using the sudoku solver found at http://sudoku.sourceforge.net/. You can do the same to add new puzzles—simply export the generated puzzles in XML format, and cut the actual puzzle text from the middle and paste it into a string in the Puzzles class, adding '|' separators between rows. An obvious next logical addition to the app would be to read the puzzles directly from an external file in the original XML format.

The Puzzle class handles parsing of the puzzle definition string and holds the current state of the guesses. Any alternate formats would be handled here. Eventually, this class should handle saving and loading of puzzle solution states.

public class Puzzle {

   public final int[][] grid = new int[9][9];
   public final boolean[][] given = new boolean[9][9];
   public final int[][] answer = new int[9][9];

   public Puzzle(String text) {
      Reader reader = new CharArrayReader(text.toCharArray());
      for (int r = 0; r < 9; r++)
         for (int c = 0; c < 9; c++) {
            grid[r][c] = nextDigit(reader);
            given[r][c] = grid[r][c] > 0;
            if (given[r][c])
               answer[r][c] = grid[r][c];
      }
   }
   ...

Ironically, this simple class is where I kept running into the sneaky Java 5 trap. At one point, I introduced a couple of String calls to trim the definition strings. This innocuous method:

test = test.replace("|", "");

was actually added in Java 5, so it and others like it will need to be avoided.

Testing It Out

The quickest way to see the game running is to build and run the sudoku-se under Eclipse. It will run as a default Java Application run configuration. Use the keyboard to navigate and test out the game logic and rendering.

To see the game running on the phone, build the sudoku-xlet project under Eclipse. Then run the two scripts: mkdist to build the jar and dist dir, and tophone to copy the directory tree to the phone's SD. (You will need to adjust the paths in this second script for your machine and SD volume name). Plug the SD into the phone, select My Applications, and play Sudoku!

If your Xlet won't start at all on the phone, it is most like caused by an unresolved symbol (like one in the Java 5 JRE), or that the compiler is set to level 5 Java.

Conclusion

Java ME has come a long way since the early days. The latest CDC specs really cover a lot of ground, and SavaJe provides an incredibly rich platform. It's almost like a desktop in your hand. So many possible applications, so little time.

This is the first real Java ME app that I've written, and see how easy it was? Let me know if you see anything that I really botched, or of course if you have any suggestions. I know that there are quite a few fun things to add to this Sudoku game. You can coordinate any additions at http://code.google.com/p/sudoku-xlet/.

Further Reading/Resources

About the Author

Carl Quinn is a Software Engineer at Google [google.com]. He focuses primarily on Java infrastructure, libraries, and development tools. He can be reached at carl.quinn@gmail.com. He also co-hosts The Java Posse, a podcast devoted to Java news and the Java community, which can be found at http://javaposse.com or via the Google group http://groups.google.com/group/javaposse.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date