
 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 April 16, 2001. This issue covers:

         * Using Timers
         * An Introduction to the High-Level User Interface
           API: Alerts and Tickers
         
The J2ME Tech Tips are written by Eric Giguere
(http://www.ericgiguere.com), an engineer at iAnywhere 
Solutions, inc., and author of the book "Java 2 Micro
Edition: Professional Developer's Guide."

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

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
USING TIMERS

One of the improvements in version 1.3 of the Java 2 Platform,
Standard Edition (J2SE) are classes that make it simpler to
schedule tasks for execution by a background thread. The Mobile 
Information Device Profile (MIDP) also includes these new 
classes, so Java 2 Platform, Micro Edition (J2ME) developers also 
benefit.

Tasks are defined and scheduled using two classes: 
java.util.TimerTask and java.util.Timer. The TimerTask class is 
an abstract class that serves as the base class for all scheduled 
tasks. The Timer class creates and manages threads on which the 
tasks are executed.

To define a task, create a subclass of TimerTask that implements 
the run method, for example:

import java.util.*;

public class MyTask extends TimerTask {
    public void run() {
        System.out.println( "Running the task" );
    }
}

If the run method looks familiar, it's because TimerTask 
implements the java.lang.Runnable interface. The Timer class 
invokes the run method to run the task. The method should perform
its task and exit as soon as possible because only one task for 
each Timer object can execute at any time.

After you define a task, you schedule it by creating an instance 
of Timer and invoking the schedule method, as in the following:

import java.util.*;

Timer     timer = new Timer();
TimerTask task = new MyTask();

// wait ten seconds before executing...
timer.schedule( task, 10000 );

// wait five seconds before executing, then
// execute every ten seconds
timer.schedule( task, 5000, 10000 );

There are four versions of the schedule method; each schedules 
tasks to occur at a specific time (specified using a Date 
object) or after a specific delay (in milliseconds). You can
schedule the tasks to occur once or to repeat indefinitely at 
specified periods. There is also a scheduleAtFixedRate method
that schedules the task for repeated execution in intervals
relative to the scheduled execution time of the first execution.
If an execution is delayed (say for garbage collection), two or
more subsequent executions are scheduled at shorter intervals to 
"catch up."

Each Timer object creates and manages a single background thread.
A single timer is usually all that a single application needs, 
but there is no limit on the number of timers you can create. 
You can stop a timer at any time and terminate its background 
thread by calling the timer's cancel method. Note that once 
stopped, a timer cannot be restarted -- you must create a new 
Timer object and reschedule the tasks you want executed. Timer 
objects are thread-safe; there's no need to perform any explicit 
synchronization if you're calling a Timer object on different 
threads.

After you schedule a task, you can stop its execution by calling 
its cancel method. This is often done within the run method of 
the task. Calling the cancel method within the run method
guarantees that the current execution of the task is the last 
one; it also allows the method to be called at any point, even 
before the task's first scheduled execution.

Here's a simple example of a MIDlet that uses a timer to perform 
a simple simulation of a moving starfield. The stars are drawn 
as points using low-level graphics APIs. For further discussion
about these low-level APIs, see the March 19, 2001 J2ME Tech Tip 
"Using the MIDP Low-level User Interface API" 
(http://java.sun.com/jdc/J2METechTips/2001/tt0319.html#tip2).

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

public class TimerDemo extends MIDlet {

    Display    display;
    StarField  field = new StarField();
    FieldMover mover = new FieldMover();
    Timer      timer = new Timer();

    public TimerDemo() {
        display = Display.getDisplay( this );
    }

    protected void destroyApp( boolean unconditional ) {
    }

    protected void startApp() {
        display.setCurrent( field );
        timer.schedule( mover, 100, 100 );
    }

    protected void pauseApp() {
    }

    public void exit(){
        timer.cancel(); // stop scrolling
        destroyApp( true );
        notifyDestroyed();
    }

    class FieldMover extends TimerTask {
        public void run(){
            field.scroll();
        }
    }

    class StarField extends Canvas {
        int        height;
        int        width;
        int[]      stars;
        Random     generator = new Random();
        boolean    painting = false;

        public StarField(){
            height      = getHeight();
            width       = getWidth();
            stars       = new int[ height ];

            for( int i = 0; i < height; ++i ){
                stars[i] = -1;
            }
        }

        public void scroll() {
            if( painting ) return;

            for( int i = height-1; i > 0; --i ){
                stars[i] = stars[i-1];
            }

            stars[0] = ( generator.nextInt() % ( 3 * width ) ) / 2;
            if( stars[0] >= width ){
                stars[0] = -1;
            }

            repaint();
        }

        protected void paint( Graphics g ){
            painting = true;

            g.setColor( 0, 0, 0 );
            g.fillRect( 0, 0, width, height );

            g.setColor( 255, 255, 255 );

            for( int y = 0; y < height; ++y ){
                int x = stars[y];
                if( x == -1 ) continue;

                g.drawLine( x, y, x, y );
            }

            painting = false;
        }

        protected void keyPressed( int keyCode ){
            exit();
        }
    }
}

The TimerDemo MIDlet uses a Timer object, timer, to schedule
execution of the TimerTask subclass, FieldMover, every 100 
milliseconds. FieldMover updates and repaints the starfield, 
extending it downward in each interval. This gives the 
illusion of a moving starfield.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
AN INTRODUCTION TO THE HIGH-LEVEL USER INTERFACE API: ALERTS AND
TICKERS

The Mobile Information Device Profile (MIDP) includes both 
a low-level user interface (UI) API and a high-level UI API. The 
low-level API gives you complete access to a device's screen and 
to raw key and pointer events. However, no user interface 
controls are available with the low-level API -- your application
must explicitly draw buttons and other familiar controls. This is 
the price you pay for the flexibility of the low-level API. 

The situation is reversed with the high-level API. It provides 
simple user interface controls, but no direct access to the 
screen or to raw input events. The controls are fairly abstract 
to account for the differences in screen size and input methods 
between various MIDP devices. The MIDP implementation decides how 
to draw the control and how to manage user input.

You can use both low-level and high-level APIs in a single 
MIDlet, just not at the same time. For example, games that rely 
on the low-level API to control the screen can also use the 
high-level API for online help or to display high scores. 
Business applications that use the high-level API for UI 
controls can also use the low-level API to draw graphs.

For more information about the low-level UI API, see the J2ME 
Tech Tip for March 19, 2001, "Using the MIDP Low-Level User 
Interface API" 
(http://java.sun.com/jdc/J2METechTips/2001/tt0319.html#tip2).

The following tip introduces the high-level UI API,
and uses two basic controls to do that: alerts and tickers.

An alert is basically a message dialog, that is, a way of 
presenting a single string to the user (along with an optional 
image or sound). Alerts display warnings, errors, alarms, 
notices, or confirmations. Your application's user interface is 
disabled while an alert is displayed. The alert then either 
times out automatically (the timeout value is programmable) or 
remains on screen until the user explicitly dismisses it.

You display an alert by creating an instance of the 
javax.microedition.lcdui.Alert class. You specify a title (which 
can be null) as a parameter to the constructor. A number of 
setter methods are available to define the alert's properties.
For example:

import javax.microedition.lcdui.*;

Image icon = ...; // code omitted

Alert msg = new Alert( "Error!" );
msg.setImage( icon );
msg.setString( "No connection was possible." );
msg.setTimeout( 5000 ); // in milliseconds
msg.setType( AlertType.ERROR );

You don't need to set all properties. For example, the image is
optional, and can be null. (Even if you set the image, the
device might not be able to display it.) However, at a minimum, 
you should always define a title and a message string. As far as 
other properties:

o The type of the alert, an instance of the AlertType class, 
  determines the sound to play when the alert is displayed; it 
  can be null. 
  
o The timeout value defaults to a system-specific value, which 
  you can determine at runtime by calling the 
  Alert.getDefaultTimeout method. If you specify a timeout value, 
  it should be the number of milliseconds to display the alert, 
  or the special value Alert.FOREVER for a modal alert.  
  
You can also specify all properties, except for the timeout 
value, in the constructor. For example:

Alert msg = new Alert( "Error!",
                       "No connection was possible.",
                       icon,
                       AlertType.ERROR );
msg.setTimeout( Alert.FOREVER ); // make it modal

After you've defined the alert, you display it by calling 
Display.setCurrent. You pass the method both a reference to the 
alert and a reference to a displayable element. A displayable 
element is a top-level user interface object that extends the 
Displayable class -- your application will have at least one
of these whether it's using the low-level or high-level UI APIs. 
The system immediately activates the displayable element when
the alert times out or is dismissed. To return to the 
displayable element that was active just before the alert was 
displayed, simply do the following:

Display display = ....; // assign on startup
Alert   msg = ....; // create an alert

display.setCurrent( alert, display.getCurrent() );

Notice that the setCurrent method is invoked on an instance of 
the Display class. You can obtain the class when the MIDlet 
starts by calling the static Display.getDisplay method, passing 
the MIDlet instance as its only parameter. For example:

public MyApp extends MIDlet
{
    private Display display;
      
    public MyApp(){
        display = Display.getDisplay( this );
    }
  
    public Display getDisplay(){
        return display;
    }
   
    // rest of MIDlet goes here
}

It's simplest to save the Display instance as a member of your 
main application class and make it available to the other classes
in your application.

Note that calling Display.setCurrent simply changes what is 
displayed on the screen -- it doesn't halt the current thread of 
execution, even if the alert is modal. You typically call it
in response to some event; you should exit the method you're in 
as soon as possible to let the system's event processing to
continue unhindered.

The sound associated with an alert must be one of five predefined 
instances of the AlertType class: INFO, WARNING, ERROR, ALARM, or 
CONFIRMATION. The device associates different sounds with each 
class, if possible, though some devices may not even support 
sound.  

You can play any sound at any time by calling the playSound 
method, as in:

    AlertType.ERROR.playSound( display );
    
The icon associated with an alert must be an instance of the Image 
class. The icon should be as small as possible and look good when 
mapped to monochrome.

Now what about tickers? A ticker is a user interface control that 
displays a single line of text, scrolling it onscreen at regular 
intervals just like an old-fashioned movie marquee. To use a 
ticker, you create an instance of the Ticker class, passing the 
string you want displayed into the constructor. For example:

import javax.microedition.ldcdui.*;

Ticker ticker = new Ticker( "Hello, world!" );

To display the ticker, you associate it with a top-level window 
created by the high-level UI, that is, any class that extends
the Screen class. You associate the ticker by calling the 
setTicker method, as in:

Form f = new Form( "A Title" );
f.setTicker( ticker );

The ticker is displayed in an appropriate area of the window. 
You can share the same ticker across different windows; the 
system then attempts to keep it in the same position.

The only thing you can do with a ticker is change its text by 
calling the setString method. To stop displaying a ticker, 
remove it from the top-level window by passing null to the 
setTicker method.

Here's a simple stock tracking MIDlet that demonstrates the use 
of alerts and tickers. Note that the stock values are generated 
randomly for example purposes. In the real world, you would use 
the HttpConnection class to obtain stock values from a web site.  
Note particularly how the alert text can be changed while the 
alert is still active.

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

public class StockWatcher extends MIDlet {

    Display      display;
    Ticker       ticker = new Ticker( "" );
    Command      exitCommand = new Command( 
                                     "Exit", Command.EXIT, 1 );
    Timer        timer = new Timer();
    StockChecker checker = new StockChecker();
    TickerForm   form = new TickerForm();
    Alert        alert = new Alert( "Stock Alert!" );

    public StockWatcher() {
        display = Display.getDisplay( this );
        alert.setTimeout( Alert.FOREVER );
    }

    protected void destroyApp( boolean unconditional ) {
    }

    protected void startApp() {
        display.setCurrent( form );
        timer.schedule( checker, 0, 30000 );
    }

    protected void pauseApp() {
    }

    public void exit(){
        timer.cancel();
        destroyApp( true );
        notifyDestroyed();
    }

    // Display a simple form to hold the ticker

    class TickerForm extends Form implements CommandListener {
        public TickerForm(){
            super( "Stock Watch" );
            setTicker( ticker );
            addCommand( exitCommand );
            setCommandListener( this );
        }

        public void commandAction( Command c, Displayable d ){
            exit();
        }
    }
  
    // Check the stock values and put up an alert if
    // they're beyond certain limits....

    class StockChecker extends TimerTask {
        Random       generator = new Random();
        int          sybsValue = 20000;
        int          sunwValue = 30000;
        int          ibmValue = 40000;
        StringBuffer buf = new StringBuffer();

        public void run(){
            String values = getStockValues();

            ticker.setString( values );

            if( sybsValue < 18000 || sybsValue > 22000 ||
                sunwValue < 28000 || sunwValue > 32000 ||
                ibmValue < 38000 || ibmValue > 42000 ){
                alert.setString( values );
            }

            if( !alert.isShown() ){
              display.setCurrent( alert, form );
            }
        }

        private String getStockValues(){
            sybsValue = randomStockValue( sybsValue );
            sunwValue = randomStockValue( sunwValue );
            ibmValue = randomStockValue( ibmValue );

            buf.setLength( 0 );
            appendValue( "SYBS", sybsValue );
            appendValue( "SUNW", sunwValue );
            appendValue( "IBM", ibmValue );

            return buf.toString();
        }

        // Generate a random stock value... in the
        // real world you'd use HTTP to obtain the
        // stock value from a broker's website.

        private int randomStockValue( int oldVal ){
            int incr1 = ( generator.nextInt() % 2 );
            int incr2 = ( generator.nextInt() % 16 );

            if( incr1 < 1 ){
                oldVal -= incr1 * 1000;
            } else {
                oldVal += ( incr1 - 2 ) * 1000;
            }

            if( incr2 < 8 ){
                oldVal -= incr2 * 250;
            } else {
                oldVal += incr2 * 250;
            }

            return oldVal;
        }

        private void appendValue( String stock, int val ){
            buf.append( stock );
            buf.append( ' ' );
            buf.append( Integer.toString( val / 1000 ) );
            buf.append( '.' );
            buf.append( Integer.toString( val % 1000 ) );
            buf.append( ' ' );
        }
    }
}

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

- NOTE

Sun respects your online time and privacy. The Java Developer 
Connection mailing lists are used for internal Sun 
Microsystems(tm) purposes only. You have received this email 
because you elected to subscribe. To unsubscribe, go to the 
Subscriptions page (http://developer.java.sun.com/subscription/), 
uncheck the appropriate checkbox, and click the Update button.


- SUBSCRIBE

To subscribe to a JDC newsletter mailing list, go to the 
Subscriptions page (http://developer.java.sun.com/subscription/), 
choose the newsletters you want to subscribe to, and click Update.


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

jdc-webmaster@sun.com


- 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


- LINKS TO NON-SUN SITES
The J2ME Tech Tips may provide, or third parties may provide, 
links to other Internet sites or resources. Because Sun has no 
control over such sites and resources, You acknowledge and 
agree that Sun is not responsible for the availability of such 
external sites or resources, and does not endorse and is not 
responsible or liable for any Content, advertising, products, 
or other materials on or available from such sites or resources. 
Sun will not be responsible or liable, directly or indirectly, 
for any damage or loss caused or alleged to be caused by or in 
connection with use of or reliance on any such Content, goods or 
services available on or through any such site or resource.

J2ME Tech Tips 
April 16, 2001

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






