Sun Java Solaris Communities My SDN Account Join SDN
 
Wireless Tech Tips

J2ME Tech Tips: October 15, 2001

 

Tech Tips archive

October 15, 2001

WELCOME to the Java Developer Connection (JDC) Java 2 Platform, Micro Edition (J2ME) Tech Tips, for April 16, 2001. This issue covers:

The J2ME Tech Tips are written by Eric Giguere (http://www.ericgiguere.com), an engineer at iAnywhere Solutions, inc. Eric is the author of the book "Java 2 Micro Edition: Professional Developer's Guide" and co-author of the upcoming "Mobile Information Device Profile for Java 2 Micro Edition," both books in John Wiley & Sons' Professional Developer's Guide series.

Pixel

Using Passwords to Protect Your MIDlets

Security is always a concern when you are writing an application that deals with sensitive data. It's especially so on handheld devices, which are more apt to be lost or stolen than a desktop computer. A handheld device is likely to hold truly personal information that you don't want strangers to know, things such as important phone numbers and addresses. Safeguarding that information should always be a priority.

MIDP security prevents MIDlets in different MIDlet suites from interacting with each other or with their data. The MIDP runtime environment also disallows the dynamic loading of new classes over the network. Apart from system classes, a MIDlet can only use classes found in its suite's JAR file. If you want any further security, for example, protecting sensitive information from disclosure, you need to program it into the MIDlet itself.

The simplest way for a MIDlet to safeguard sensitive information, is to prompt the user for a password. You can program this into the MIDlet so that the MIDlet does this when it starts or the first time it accesses sensitive data. You can also have the MIDlet ask the user to confirm certain sensitive operations by reentering the password.

Prompting the user for a password is easily done using either the TextBox or the TextField class, both part of the javax.microedition.lcdui package. The TextBox class displays a top-level window consisting of a title, an optional label, and a text entry field. No other user interface components can be added to a TextBox. So for more flexibility, you can use a TextField component. A TextField is a text entry field that can be placed on a Form, that is, a top-level window that can display multiple user interface components. Both components have an identical interface, so what's being discussed here applies to either component.

Before looking at some code, however, let's answer an important question: what kind of password should you prompt for -- alphanumeric or just numeric? Your first thought might be to allow for alphanumeric passwords, because that's what is typically used with desktop computers. The reality, however, is that numeric-only passwords are the only ones that make sense in a MIDP context. Remember that a MIDP device might have a numeric keypad instead of a full keyboard -- think MIDP-enabled cellular telephone, for example. Entering text on a keypad is done by assigning multiple characters to individual keys and letting the user press the same key multiple times to cycle through its set of characters. This is a tedious and error-prone process that can frustrate the user. It is also more of a security risk, because password masking, that is, displaying a masking character instead of the actual character, must be disabled to allow users to view which characters they are actually entering. But anyone looking over the user's shoulder also has a clear view of the password! Using a numeric password makes for quicker and easier password input. Think of it more like the personal identification number you enter at an automated banking machine.

Here's the code for a simple password prompter using the TextBox class:

// Defines a class that prompts the user
// for a numeric password.

import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;

public class PasswordPrompter
          extends TextBox
          implements CommandListener {

    private Display     display;
    private Displayable next;
    private int         pin;

    // Constructor with a default title

    public PasswordPrompter( int pin,
                             int maxchars,
                             Display display,
                             Displayable next ){
        this( defaultTitle, pin, maxchars,
              display, next );
    }

    // Constructor with a specific title

    public PasswordPrompter( String title,
                             int pin,
                             int maxchars,
                             Display display,
                             Displayable next ){
        super( title, "", maxchars,
          TextField.NUMERIC | 
            TextField.PASSWORD );

        addCommand( okCommand );
        setCommandListener( this );

        this.display = display;
        this.next    = next;
        this.pin     = pin;

        display.setCurrent( this );
    }

    // Responds to the one-and-only command event
    // by checking the entered password against
    // the pin. If it's OK, the user is 
    // "forwarded" to the next displayable, 
    // otherwise an alert is displayed.

    public void commandAction( Command c,
      Displayable d ){
        String pinStr = getString();

        try {
            if( Integer.parseInt( pinStr ) 
               == pin ){
                display.setCurrent( next );
                return;
            }
        }
        catch( NumberFormatException e ){
        }

        Alert alert = new Alert( "Error!",
          "Invalid password", null,
            AlertType.ERROR );

        setString( "" );
        display.setCurrent( alert, this );
    }

    private static final Command okCommand =
      new Command( "OK", Command.OK, 1 );

    private static final String defaultTitle =
      "Enter Password";
}


To invoke the prompter, just create an instance of the PasswordPrompter class, passing it the password to check against (as an integer), the maximum number of characters to allow for the password, the Display instance for the MIDlet, and the screen to display if the correct password is entered. Here is a simple MIDlet that demonstrates the use of the prompter:

// A MIDlet that demonstrates the use of the
// password prompter.

import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;

public class PasswordTester extends MIDlet
  implements CommandListener {

    private Display display;
    private Command exitCommand
      = new Command( "Exit", Command.EXIT, 1 );

    public PasswordTester(){
    }

    protected void destroyApp( boolean unconditional )
      throws MIDletStateChangeException {
        exitMIDlet();
    }

    protected void pauseApp(){
    }

    protected void startApp()
      throws MIDletStateChangeException {
        if( display == null ){ // 
          first time called... initMIDlet();
        }
    }

    private void initMIDlet(){
        display = Display.getDisplay( this );

        // First time through, ask for 
        // the password.

        new PasswordPrompter( 1234, 4, display,
          new TrivialForm() );
    }

    public void exitMIDlet(){
      notifyDestroyed();
    }

    public void commandAction( 
                   Command c, Displayable d ){
        exitMIDlet();
    }

    // A trivial UI screen

    class TrivialForm extends Form {
        TrivialForm(){
            super( "MainApp" );
            addCommand( exitCommand );
            setCommandListener( 
              PasswordTester.this );
        }
    }
}

In the example above, the password is hardcoded to be "1234." Of course, you wouldn't do this in a real application. What you would do is get the password either from a record store maintained by the application or else from an application property. An application property is a value stored in either the MIDlet suite manifest or the suite's application descriptor. To retrieve an application property just call the MIDlet's getAppProperty method. For example:

    MIDlet midlet = ....;
    int    password;
    
    try {
        String p = midlet.getAppProperty( 
          "Password" );
        password = Integer.parseInt( p );
    }
    catch( NumberFormatException e ){
    }

Using an application property allows you to customize a MIDlet suite for a particular customer. For example, as part of a servlet-controlled download process, the customer could be asked to enter an email address. The process could then generate a random password and a custom version of the MIDlet suite, and send the password to the customer by email. The customer would have to enter the password to activate the application, once it's on the device.

A word of caution in all of this: no security measure is foolproof, and password protection might not be enough security to protect every application. Data stored in the application manifest or descriptor is easily read by anyone with access to those files. Even data stored in a record store isn't secure. On a Palm device, for example, it's trivial for an experienced developer to examine the native Palm database that underlies a record store. If data is truly important, then it should be stored in an encrypted format.

Pixel

ADAPTING IMAGES FOR MIDP DEVICES

The Mobile Information Device Profile (MIDP) supports the display of bitmapped images, but not with the flexibility of Java 2 Standard Edition (J2SE). The MIDP specification only requires support for a single format, the Portable Network Graphics (PNG) format. PNG was developed primarily as a replacement for the older Graphics Interchange Format (GIF), and has the support of the World Wide Web consortium. Modern web browsers and image editors can load and save images in PNG format.

Still, many images on the World Wide Web are in GIF or JPEG (a "lossy" image format better suited for photographs) format, not PNG. This is a problem for MIDP applications that need to fetch and display web images. While it is certainly possible to add the necessary Java code to a MIDlet to perform GIF-to-PNG and JPEG-to-PNG conversions, it makes more sense to do the conversion on a server instead of the client. Not only does this make your application smaller, it is likely to be much faster. Because you're already fetching images across the network, fetching the image through a web server extension you've written is not going to be much slower.

Even images already in PNG format can benefit from being funneled through a server, because the server can adapt the image to fit the device's characteristics. For example, the width and height of the display varies from device to device, so the server could scale the image appropriately. The server can also change the bit-depth of the image based on the number of colors the device supports. One approach, then, is to not package an application's images with the application but to download them dynamically the first time the application is run, storing the customized images locally using the MIDP's Record Management System (RMS).

In effect, what you need is a "web service" (in a generic sense) that can dynamically fetch, convert and adapt an image for a particular device. An obvious way to do this is to write the service as a Java servlet, which the MIDP application can then access using HTTP. The basics of how to do this were covered in a previous J2ME Tech Tip, "Client-Server Communication Over HTTP Using MIDlets and Servlets". This Tech Tips builds on those concepts.

The first thing you need to do is locate some image conversion code. There are many free and commercial packages available. But for our purposes let's use Sun's Jimi (Java Image Management Interface) software development kit, available for download. The Jimi SDK enhances the basic image support found in J2SE by providing encoders and decoders for various image formats. As a bonus, it runs under Java 1.1.x as well as the Java 2 Platform. Note that a new set of imaging I/O extensions for the Java 2 Platform, developed under the Java Community Process, is available starting with J2SE 1.4, but given that J2SE 1.4 is currently in Beta, and that many servlet containers are still running with J2SE 1.2 or even Java 1.1.8, Jimi is a reasonable choice to start with. Download the Jimi SDK and place the JimiProClasses.zip file in your servlet container's classpath so that the servlet can use the Jimi classes.

With the Jimi classes, the servlet is almost trivial to write, but it's too long to include here. You can download the source for the servlet from the following URL: http://www.ericgiguere.com/techtips/ImageAdapter.zip.

Compile the code and install it on your web server. As a test, you should be able to use a standard web browser to invoke the servlet. The web browser will display an image of Duke, the official Java mascot, fetched from the Sun Web site.

Note that the servlet can handle any image supported by Jimi, including the JPEG, GIF and PNG formats.

Once the servlet is running, it's time to write a MIDlet that uses it to fetch images. Here is the code for the MIDlet:

import java.io.*;
import javax.microedition.io.*;
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;

/**
 * A MIDP application that fetches and displays  
 * an arbitrary image from the Web using the  
 * ImageAdapter servlet. This class depends on  
 * HttpConnectionHelper, described in an earlier 
 * J2ME Tech Tip.
 */

public class ImageClient extends MIDlet
              implements CommandListener {

    // Adjust this URL appropriately for 
    // the adapter

    private static String adapterURL =
      "http://localhost:8080/servlet/ImageAdapter";

    // A default image to display...

    private static String defaultURL =
   "http://java.sun.com/products/images/duke_j2ee.gif";

    private Display display;
    private Command exitCommand = new 
      Command( "Exit", Command.EXIT, 1 );
    private Command okCommand   = new 
      Command( "OK", Command.OK, 1 );
    private Command sendCommand = new 
      Command( "Get", Command.OK, 1 );
    private TextBox entryForm;
    private int     screenHeight;
    private int     screenWidth;
    private int     numColors;

    public ImageClient(){
    }

    protected void destroyApp( boolean 
      unconditional )
        throws MIDletStateChangeException {
        exitMIDlet();
    }

    protected void pauseApp(){
    }

    protected void startApp()
           throws MIDletStateChangeException {
        if( display == null ){ // first time 
          called... initMIDlet();
        }
    }

    // First we display the dummy canvas so 
    // we can get information about the display

    private void initMIDlet(){
        display = Display.getDisplay( this );
        entryForm = new EntryForm();
        display.setCurrent( new DummyCanvas() );
    }

    public void exitMIDlet(){
        notifyDestroyed();
    }

    public void commandAction( 
                   Command c, Displayable d ){
        if( c == sendCommand ){
            StatusForm f =
               new StatusForm( 
                 entryForm.getString() );
            display.setCurrent( f );
            f.start();
        } else if( c == okCommand ){
            display.setCurrent( entryForm );
        } else {
            exitMIDlet();
        }
    }

    // A dummy canvas to get the size of 
    // the screen

    class DummyCanvas extends Canvas {
        protected void paint( Graphics g ){
            screenHeight = getHeight();
            screenWidth  = getWidth();
            numColors    = display.numColors();

            // Go directly to the main screen

            display.setCurrent( entryForm );
        }
    }

    // The text entry form...

    class EntryForm extends TextBox {
        EntryForm(){
            super( "Enter a URL", defaultURL, 
              80, 0 );
            addCommand( exitCommand );
            addCommand( sendCommand );
            setCommandListener( 
              ImageClient.this );
        }
    }

    // Show the image...

    class ShowImage extends Canvas {
      ShowImage( byte[] imageData ){
        image = Image.createImage( imageData,
          imageData.length );
            display.setCurrent( this );
            addCommand( okCommand );
            setCommandListener( ImageClient.this );
        }

        protected void paint( Graphics g ){
            g.drawImage( image, 0, 0,
                         g.TOP | g.LEFT );
        }

        private Image image;
    }

    // A status for for displaying messages  
    // as the data is sent...

    class StatusForm extends Form
      implements Runnable,
        HttpConnectionHelper.Callback {
        StatusForm( String url ){
            super( "Status" );

            try {
                ByteArrayOutputStream bout =
                  new ByteArrayOutputStream();
                DataOutputStream      dout =
                  new DataOutputStream( bout );

                // Write info about the image and the
                // display settings for this device

                dout.writeUTF( "url" );
                dout.writeUTF( url );

                writeInt( 
                       dout, "minwidth", 
                         screenWidth );
                writeInt( 
                       dout, "maxwidth", 
                         screenWidth );
                writeInt( 
                     dout, "minheight", 
                       screenHeight );
                writeInt( 
                     dout, "maxheight", 
                       screenHeight );
                writeInt( 
                           dout, "colors", 
                             numColors );
                writeInt( dout, "reduce", 1 );
                writeInt( dout, "dither", 1 );

                dout.writeUTF( "" );

                data = bout.toByteArray();
                dout.close();
            }
            catch( IOException e ){
                // should handle this....
            }
        }

        void writeInt( DataOutputStream dout, 
          String key, int value ) 
            throws IOException {
            dout.writeUTF( key );
            dout.writeUTF( Integer.toString( 
              value ) );
        }

        // Updates the display.

        void display( String text ){
            if( message == null ){
                message = new StringItem( 
                  null, text );
                append( message );
            } else {
                message.setText( text );
            }
        }

        // We're done.

        void done( String msg ){
            display( msg != null ? msg : 
              "Done." );
            addCommand( okCommand );
            setCommandListener( 
              ImageClient.this );
        }

        // Callback for making the HTTP connection.

        public void prepareRequest( String 
          originalURL, HttpConnection conn ) 
            throws IOException
        {
          conn.setRequestMethod( HttpConnection.POST );
            conn.setRequestProperty( "User-Agent",
           "Profile/MIDP-1.0 
             Configuration/CLDC-1.0" );
           conn.setRequestProperty( 
             "Content-Language",
               "en-US" );
           conn.setRequestProperty( 
             "Accept", "image/png" );
            conn.setRequestProperty( 
              "Connection", "close" );
            conn.setRequestProperty( 
              "Content-Length", Integer.toString( 
                data.length ) );

            OutputStream os = 
              conn.openOutputStream();
            os.write( data );
            os.close();
        }

        // Do the connection on a separate  
        // thread to keep the UI responsive...

        public void run(){
            HttpConnection conn = null;

            display( 
              "Obtaining HttpConnection 
                object..." );

            try {
                conn =
                  HttpConnectionHelper.connect( 
                    adapterURL,
                      this );

                display( 
                  "Connecting to the server..." );
                int rc = conn.getResponseCode();

                if( rc == HttpConnection.HTTP_OK ){
                  try {
                    DataInputStream din = 
                      new DataInputStream(
                        conn.openInputStream() );
             data = new byte[ (int) 
               conn.getLength() ];
                        din.readFully( data );
                        din.close();
                    }
                    catch( IOException e ){
                    }

                    done( "Image received" );
                    new ShowImage( data );
                } else {
                    done( "Unexpected return 
                      code: " + rc );
                }

                conn.close();
            }
            catch( IOException e ){
                done( "Exception " + e +
                      " trying to connect." );
            }
        }

        // Starts the upload in the background...

        void start(){
            display( "Starting..." );

            Thread t = new Thread( this );
            try {
                t.start();
            }
            catch( Exception e ){
                done( "Exception " + e +
                  " trying to start thread." );
            }
        }

        private StringItem message;
        private byte[]     data;
    }
}

This MIDlet follows the same basic outline described in the August 20, 2001 J2ME Tech Tip, "Client-Server Communication over HTTP using MIDP and Servlets," but it changes a few things. For one, it sends different data: the body of the POST request consists of string pairs describing the URL to fetch and the display characteristics of the device. (By sending the data in the request body you avoid having to URL-encode it.) The data the MIDlet receives from the servlet is assumed to be the bytestream of an image in PNG format, which it collects and then displays using the javax.microedition.lcdui.Image class.

Notice how you can create an image directly from a byte array:

  Image img = Image.createImage( imageData, 0,
    imageData.length );

The image is displayed using a simple subclass of Canvas that does nothing but paint the image on the display.

To compile and run this MIDlet you'll also need the HttpConnectionHelper class that was shown in the December 18, 2000 J2ME Tech Tip, "Making HTTP Connections with MIDP".

You can download the complete code for the client from http://www.ericgiguere.com/techtips/ImageClient.zip. If you are using the J2ME Wireless Toolkit, you can simply unzip this file into the Toolkit's apps folder, and load and run the project directly using the KToolbar application.

Pixel

IMPORTANT: Please read our Terms of Use and Privacy policies:
http://www.sun.com/share/text/termsofuse.html
http://www.sun.com/privacy/

- FEEDBACK

Comments? Send your feedback on the J2ME Tech Tips to:

jdc-webmaster@sun.com

- SUBSCRIBE/UNSUBSCRIBE

- To subscribe, go to the subscriptions page, choose the newsletters you want to subscribe to and click "Update".
- To unsubscribe, go to the subscriptions page, uncheck the appropriate checkbox, and click "Update".
- To use our one-click unsubscribe facility, see the link at the end of this email:

- ARCHIVES

You'll find the J2ME Tech Tips archives at: http://java.sun.com/jdc/J2METechTips/index.html

- COPYRIGHT

Copyright 2001 Sun Microsystems, Inc. All rights reserved. 901 San Antonio Road, Palo Alto, California 94303 USA.

This document is protected by copyright. For more information, see:

http://java.sun.com/jdc/copyright.html

J2ME Tech Tips October 15, 2001

Sun, Sun Microsystems, Java, Java Developer Connection, J2ME, and J2SE are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.