
 J 2 M E    T  E  C  H    T  I  P  S

                      TIPS, TECHNIQUES, AND SAMPLE CODE


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

     * Using Passwords to Protect Your MIDlets
     * Adapting Images for MIDP Devices
         
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.

You can view this issue of the J2ME Tech Tips on the Web at
http://java.sun.com/jdc/J2METechTips/2001/tt1015.html

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
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.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ADAPTING IMAGES FOR MIDP DEVICES

The Mobile Information Device Profile (MIDP) supports the display 
of bitmapped images, but not with the flexibility of Java(tm) 2 
Standard Edition (J2SE(tm)). 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. (See 
http://www.w3.org/Graphics/PNG/ for information about PNG.) 
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", which you can read at
http://java.sun.com/jdc/J2METechTips/2001/tt0820.html#tip2.
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 from
http://java.sun.com/products/jimi. 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, 0,
                                    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"
(http://java.sun.com/jdc/J2METechTips/2000/tt1218.html#tip2).

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.

.  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .

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,
    (http://developer.java.sun.com/subscription/), choose
    the newsletters you want to subscribe to and click "Update".
  - To unsubscribe, go to the subscriptions page,
    (http://developer.java.sun.com/subscription/), 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.




