JMU
The J2ME Mobile Information Device Profile
Using the State Pattern


Prof. David Bernstein
James Madison University

Computer Science Department
bernstdh@jmu.edu


Motivation
A Generic Abstract State
j2meexamples/src/gui/AbstractScreen.java
        package gui;

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

/**
 * An abstract "screen" in a MIDlet
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public abstract class AbstractScreen
{
    protected  ApplicationContext          context;
    

    public static final String  CANCEL         = new String("Cancel");
    public static final String  EXIT           = new String("Exit");
    public static final String  INFORMATION    = new String("Information");
    public static final String  OK             = new String("OK");
    public static final String  SELECT         = new String("Select");
    
    protected static final Command CANCEL_COMMAND = new Command(CANCEL, Command.CANCEL, 1);
    protected static final Command EXIT_COMMAND   = new Command(EXIT,   Command.EXIT,   1);
    protected static final Command OK_COMMAND     = new Command(OK,     Command.OK,     1);
    protected static final Command SELECT_COMMAND = new Command(SELECT, Command.SCREEN, 1);
    
    /**
     * Explicit Value Constructor
     *
     * @param context   The application context
     */
    public AbstractScreen(ApplicationContext context)
    {
        this.context = context;
    }
    

    /**
     * Enter this instance of an AbstractScreen
     */
    public void enter()
    {
        Display      display;
        Displayable  displayable;
        
        context.setState(this);
        
        displayable = getDisplayable();
        displayable.setCommandListener(context);

        display     = context.getDisplay();
        display.setCurrent(displayable);
    }
        
    
    /**
     * Get the ApplicationContext for this "screen"
     */
    protected ApplicationContext getContext()
    {
        return context;
    }
    
    
    
    /**
     * Get the Displayable associated with this AbstractScreen
     *
     * @returns   The Displayable
     */
    protected abstract Displayable getDisplayable();
    
}
        
The Context/Controller
j2meexamples/src/gui/ApplicationContext.java
        package gui;

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

/**
 * The context (in the sense of the StatePattern) for a MIDlet
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public interface ApplicationContext extends CommandListener
{
    /**
     * Get the Display associated with this ApplicationContext
     *
     * @return  The Display
     */
    public abstract Display getDisplay();
    
    
    /**
     * Set the current state for this ApplicationContext
     *
     * @param state   The new AbstractState
     */
    public abstract void setState(AbstractScreen state);

    
}
        
A Complete Example

An Overview
images/phoneycall.gif

A Complete Example (cont.)

The MIDlet

j2meexamples/src/phoneycall/PhoneyCall.java
        package phoneycall;

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

import gui.*;

/**
 *
 */
public class PhoneyCall extends MIDlet implements ApplicationContext
{
    private AbstractScreen         currentScreen, previousScreen;
    private AbstractState          state;
    private Display                display;
    
    
    /**
     * Handle a Command
     *
     * @param c   The Command
     * @param d   The Displayable that generated the Command
     */
    public void commandAction(Command c, Displayable d)
    {
        String   ac;
        
        ac = c.getLabel();
        
        if (ac.equals(AbstractState.CANCEL))
            state.handleCancel();
        if (ac.equals(AbstractState.OK))
            state.handleOK();
        if (ac.equals(AbstractState.SELECT))
            state.handleSelect();
    }
    
    
    
    /**
     * Destroy this MIDlet
     *
     * @param unconditional   true to destroy under any circumstances
     */
    public void destroyApp(boolean unconditional)
    {
    }
    
    
    /**
     * Get the Display
     *
     * @return  The Display
     */
    public Display getDisplay()
    {
        return display;
    }
    
    
    /**
     * Pause this MIDlet
     */
    public void pauseApp()
    {
    }
    
    
    /**
     * Set the current state for this ApplicationContext
     *
     * @param state   The new AbstractState
     */
    public void setState(AbstractScreen state)
    {
        this.state = (AbstractState)state;
    }
    
    
    
    /**
     * Start this MIDlet
     */
    public void startApp()
    {
        display = Display.getDisplay(this);
        state = CallCountScreen.enter(this);
    }
    
    
}
        
A Complete Example (cont.)

The AbstractState for this Example

j2meexamples/src/phoneycall/AbstractState.java
        package phoneycall;

import javax.microedition.lcdui.*;

import gui.*;

/**
 */
public abstract class AbstractState extends AbstractScreen
{
    
    /**
     * Explicit Value Constructor
     */
    public AbstractState(ApplicationContext context)
    {
        super(context);
    }
    
    
   /**
     * Enter this AbstractState
     *
     * Specifically, construct an instance if necessary, perform any
     * necessary operations, and call the instances enter() method
     *
     * @param context     The ApplicationContext
     * @return            The (singleton) instance of this State
     */
    public static AbstractState enter(ApplicationContext context)
    {
       return null;
    }    
    
    
    /**
     * Handle a CANCEL_COMMAND
     */
    public void handleCancel()
    {}
    
    
    /**
     * Handle an OK_COMMAND
     */
    public void handleOK()
    {}
    
    
    /**
     * Handle a SELECT_COMMAND
     */
    public void handleSelect()
    {}
}
        
A Complete Example (cont.)

The Initial State

j2meexamples/src/phoneycall/CallCountScreen.java
        package phoneycall;

import javax.microedition.lcdui.*;

import gui.*;

/**
 * The CallCountScreen in PhoneyCall
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class CallCountScreen extends AbstractState
{
    private List        list;
    
    private static CallCountScreen   instance;
    
    private static final String MULTIPLE = new String("Multiple Calls");
    private static final String ONE      = new String("One Call");
    
    /**
     * Explicit Value Constructor
     */
    private CallCountScreen(ApplicationContext context)
    {
        super(context);
        
        list = new List("Number of Calls?", List.IMPLICIT);
        list.append(ONE, null);
        list.append(MULTIPLE, null);
        list.setSelectCommand(SELECT_COMMAND);
    }
    
    
    /**
     * Get the Displayable associated with this AbstractState
     *
     * @returns   The Displayable
     */
    protected Displayable getDisplayable()
    {
        return list;
    }
    
    
    /**
     * Enter this AbstractState
     *
     * Specifically, construct an instance if necessary, perform any
     * necessary operations, and call the instances enter() method
     *
     * @param context     The ApplicationContext
     * @return            The (singleton) instance of this State
     */
    public static AbstractState enter(ApplicationContext context)
    {
        if (instance == null) instance = new CallCountScreen(context);
        
        instance.enter();
        
        return instance;
    }
    
    
    /**
     * Handle a SELECT_COMMAND
     */
    public void handleSelect()
    {
        int     index;
        String  ac;
        
        index = list.getSelectedIndex();
        ac    = list.getString(index);
        
        if (ac.equals(ONE))
            SingleCallScreen.enter(context);
        else if  (ac.equals(MULTIPLE))
            MultipleCallScreen.enter(context);
    }
}
        
A Complete Example (cont.)

The Next Two States

j2meexamples/src/phoneycall/SingleCallScreen.java
        package phoneycall;

import javax.microedition.lcdui.*;

import gui.*;


/**
 * The SingleCallScreen in PhoneyCall
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class SingleCallScreen extends AbstractState
{
    private   Form          form;
    private   TextField     delay;
    
    private static SingleCallScreen    instance;
    
    
    
    /**
     * Explicit Value Constructor
     *
     * @param context  The ApplicationContext
     */
    private SingleCallScreen(ApplicationContext context)
    {
        super(context);
        
        form = new Form("Single Call Details");
        
        delay = new TextField("Delay (sec.):", "5", 6, TextField.NUMERIC);
        delay.setLayout(Item.LAYOUT_EXPAND|Item.LAYOUT_NEWLINE_AFTER);
        
        form.append(delay);
        
        form.addCommand(CANCEL_COMMAND);
        form.addCommand(OK_COMMAND);
    }
    
    
    /**
     * Enter this AbstractState
     *
     * Specifically, construct an instance if necessary, perform any
     * necessary operations, and call the instances enter() method
     *
     * @param context     The ApplicationContext
     * @return            The (singleton) instance of this State
     */
    public static AbstractState enter(ApplicationContext context)
    {
        if (instance == null) instance = new SingleCallScreen(context);
        
        instance.enter();
        
        return instance;
    }
    
    
    /**
     * Get the Displayable associated with this AbstractState
     *
     * @returns   The Displayable
     */
    protected Displayable getDisplayable()
    {
        return form;
    }
    
    
    
    /**
     * Handle a CANCEL_COMMAND
     */
    public void handleCancel()
    {
        CallCountScreen.enter(context);
    }
    
    
    /**
     * Handle an OK_COMMAND
     */
    public void handleOK()
    {
        CallersScreen   next;
        int             n;
        WaitingScreen   waiting;
        
        // Set the attributes of the WaitingScreen
        waiting = WaitingScreen.createInstance(context);
        try
        {
            n = Integer.parseInt(delay.getString());
        }
        catch (NumberFormatException nfe)
        {
            n = 5;
        }
        waiting.setDelay(n);
        waiting.setNumber(1);
        waiting.setInterval(n);
        
        // Set the attributes of the CallersScreen
        next = CallersScreen.createInstance(context);
        next.setPreviousState(this);
        
        // Enter the CallersScreen
        next.enter();
    }
    
}
        
j2meexamples/src/phoneycall/MultipleCallScreen.java
        package phoneycall;

import javax.microedition.lcdui.*;

import gui.*;

/**
 * The MultipleCallScreen in PhoneyCall
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class MultipleCallScreen extends AbstractState
{
    private Form         form;
    private TextField    delay, interval, number;
    
    private static MultipleCallScreen    instance;
    
    
    /**
     * Explicit Value Constructor
     *
     * @param context  The ApplicationContext
     */
    private MultipleCallScreen(ApplicationContext context)
    {
        super(context);
        form = new Form("Multiple Call Details");
        
        number = new TextField("No. of Calls:", "6", 6, TextField.NUMERIC);
        number.setLayout(Item.LAYOUT_EXPAND|Item.LAYOUT_NEWLINE_AFTER);
        form.append(number);
        
        interval = new TextField("In the next (sec.):", "120", 6, TextField.NUMERIC);
        interval.setLayout(Item.LAYOUT_EXPAND|Item.LAYOUT_NEWLINE_AFTER);
        form.append(interval);
        
        delay = new TextField("First call in (sec.):", "5", 3, TextField.NUMERIC);
        delay.setLayout(Item.LAYOUT_EXPAND|Item.LAYOUT_NEWLINE_AFTER);
        form.append(delay);
   
        form.addCommand(CANCEL_COMMAND);
        form.addCommand(OK_COMMAND);
    }
    
    
    
    /**
     * Enter this AbstractState
     *
     * Specifically, construct an instance if necessary, perform any
     * necessary operations, and call the instances enter() method
     *
     * @param context     The ApplicationContext
     * @return            The (singleton) instance of this State
     */
    public static AbstractState enter(ApplicationContext context)
    {
        if (instance == null) instance = new MultipleCallScreen(context);
        
        instance.enter();
        
        return instance;
    }
    
    
    
    
    /**
     * Get the Displayable associated with this AbstractState
     *
     * @returns   The Displayable
     */
    protected Displayable getDisplayable()
    {
        return form;
    }
    
    
    
    
    /**
     * Handle a CANCEL_COMMAND
     */
    public void handleCancel()
    {
        CallCountScreen.enter(context);
    }
    
    
    /**
     * Handle an OK_COMMAND
     */
    public void handleOK()
    {
        CallersScreen   next;
        int             n;
        WaitingScreen   waiting;
        
        // Set the attributes of the WaitingScreen
        waiting = WaitingScreen.createInstance(context);
        try
        {
            n = Integer.parseInt(number.getString());
        }
        catch (NumberFormatException nfe)
        {
            n = 5;
        }
        waiting.setNumber(n);
        try
        {
            n = Integer.parseInt(interval.getString());
        }
        catch (NumberFormatException nfe)
        {
            n = 5;
        }
        waiting.setInterval(n);
        try
        {
            n = Integer.parseInt(delay.getString());
        }
        catch (NumberFormatException nfe)
        {
            n = 5;
        }
        waiting.setDelay(n);
        
        // Set the attributes of the CallersScreen
        next = CallersScreen.createInstance(context);
        next.setPreviousState(this);
        
        // Enter the CallersScreen
        next.enter();
    }
    
}
        
A Complete Example (cont.)

The Next State

j2meexamples/src/phoneycall/CallersScreen.java
        package phoneycall;

import javax.microedition.lcdui.*;

import gui.*;

/**
 * The CallersScreen in PhoneyCall
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class CallersScreen extends AbstractState
{
    private AbstractState     previousState;
    private List              list;
    
    
    private static CallersScreen     instance;
    
    /**
     * Explicit Value Constructor
     *
     * @param context  The ApplicationContext
     */
    public CallersScreen(ApplicationContext context)
    {
        super(context);
        list = new List("Select Caller(s)", List.MULTIPLE);
        list.append("Aboutabl, Mohamed", null);
        list.append("Abzug, Charles", null);
        list.append("Adams, Elizabeth", null);
        list.append("Bernstein, David", null);
        list.append("Buchholz, Florian", null);
        list.append("Cushman, Pauline", null);
        list.append("Daughtrey, Taz", null);
        list.append("Fox, Christopher", null);
        list.append("Grove, Ralph", null);
        list.append("Harris, J. Archer", null);
        list.append("Harris, Nancy", null);
        list.append("Heydari, M. Hossain", null);
        list.append("Lane, Malcolm", null);
        list.append("Marchal, Joseph", null);
        list.append("Mata-Toledo, Ramon", null);
        list.append("Norton, Michael", null);
        list.append("Prieto-Díaz, Rubén", null);
        list.append("Redwine, Samuel", null);
        list.append("Tjaden, Brett", null);
        list.append("Wang, Xunhua (Steve)", null);
        
        list.addCommand(CANCEL_COMMAND);
        list.addCommand(OK_COMMAND);
    }
    
    
    /**
     * Construct an instance of this class
     *
     * @param context     The ApplicationContext
     * @return            The (singleton) instance of this State
     */
    public static CallersScreen createInstance(ApplicationContext context)
    {
        if (instance == null) instance = new CallersScreen(context);
        
        return instance;
    }
    
    
    /**
     * Enter this AbstractState
     *
     * Specifically, construct an instance if necessary, perform any
     * necessary operations, and call the instances enter() method
     *
     * @param context     The ApplicationContext
     * @return            The (singleton) instance of this State
     */
    public static AbstractState enter(ApplicationContext context)
    {
        instance = createInstance(context);
        instance.enter();
        
        return instance;
    }
    
    
    
    
    /**
     * Get the Displayable associated with this AbstractState
     *
     * @returns   The Displayable
     */
    protected Displayable getDisplayable()
    {
        return list;
    }
    
    
    
    
    /**
     * Handle a CANCEL_COMMAND
     */
    public void handleCancel()
    {
        previousState.enter();
    }
    
    
    /**
     * Handle an OK_COMMAND
     */
    public void handleOK()
    {
        boolean[]       selected;
        WaitingScreen   next;
        int             index, n;
        String[]        callers;
        
        selected = new boolean[list.size()];
        n = list.getSelectedFlags(selected);
        
        if (n == 0)
        {
            callers = new String[1];
            callers[0] = list.getString(0);
        }
        else
        {
            callers = new String[n];
            index   = 0;
            for (int i=0; i<selected.length; i++)
            {
                if (selected[i])
                {
                    callers[index] = list.getString(i);
                    index++;
                }
            }
        }
        
        next = WaitingScreen.createInstance(context);
        next.setCallers(callers);
        
        next.enter();
    }
    
    
    /**
     * Set the previous state
     *
     * @param previous   The previous AbstractState
     */
    public void setPreviousState(AbstractState previous)
    {
        previousState = previous;
    }
}
        
A Complete Example (cont.)

The Last State

j2meexamples/src/phoneycall/WaitingScreen.java
        package phoneycall;

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

import auditory.*;
import gui.*;

/**
 * The CallersScreen in PhoneyCall
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class WaitingScreen extends AbstractState
{
    private AudioPlayer      player;
    private Form             form;
    private int              delay, interval, number;
    private Ringer           ringer;
    private String[]         callers;
    private StringItem       text;
    private Timer            timer;
    
    private static WaitingScreen     instance;
    
    
    /**
     * Explicit Value Constructor
     *
     * @param context  The ApplicationContext
     */
    private WaitingScreen(ApplicationContext context)
    {
        super(context);
        
        player = new AudioPlayer();
        
        form = new Form("");
        text = new StringItem("","Waiting for a call...");
        form.append(text);
        
        form.addCommand(CANCEL_COMMAND);
        form.addCommand(OK_COMMAND);
        
        timer = new Timer();
    }
    
    
    
    /**
     * Construct an instance of this class
     *
     * @param context     The ApplicationContext
     * @return            The (singleton) instance of this State
     */
    public static WaitingScreen createInstance(ApplicationContext context)
    {
        if (instance == null) instance = new WaitingScreen(context);
        
        return instance;
    }
    
    
    /**
     * Enter this instance of an AbstractScreen
     */
    public void enter()
    {
        super.enter();
        ringer = new Ringer();
        timer.schedule(ringer, delay*1000, (interval*1000)/number);
    }
    
    
    /**
     * Enter this AbstractState
     *
     * Specifically, construct an instance if necessary, perform any
     * necessary operations, and call the instances enter() method
     *
     * @param context     The ApplicationContext
     * @return            The (singleton) instance of this State
     */
    public static AbstractState enter(ApplicationContext context)
    {
        instance = createInstance(context);
        instance.enter();
        
        return instance;
    }
    
    
    
    
    
    /**
     * Get the Displayable associated with this AbstractState
     *
     * @returns   The Displayable
     */
    protected Displayable getDisplayable()
    {
        return form;
    }
    
    
    
    /**
     * Handle a CANCEL_COMMAND
     */
    public void handleCancel()
    {
        // Stop the audio
        player.stop();
        
        // Reset the message
        text.setText("Waiting for a call...");
        
        // Stop the TimerTask
        ringer.cancel();
        
        // Return to the CallCountScreen
        CallCountScreen.enter(context);
    }
    
    
    /**
     * Handle an OK_COMMAND
     */
    public void handleOK()
    {
        // Stop the audio
        player.stop();
        
        // Reset the message
        text.setText("Waiting for a call...");
        
        if (number == 0) ringer.cancel();
    }
    
    
    
    /**
     * Set the collection of callers
     *
     * @param callers  The collection of callers
     */
    public void setCallers(String[] callers)
    {
        this.callers = callers;
    }
    
    
    /**
     * Set the delay
     *
     * @param delay   The delay in minutes
     */
    public void setDelay(int delay)
    {
        this.delay = delay;
    }
    
    
    /**
     * Set the interval
     *
     * @param interval   The interval in minutes
     */
    public void setInterval(int interval)
    {
        this.interval = interval;
    }
    
    
    /**
     * Set the number of calls
     *
     * @param number   The number of calls
     */
    public void setNumber(int number)
    {
        this.number = number;
    }
    
    
    /**
     * The TimerTask used by the WaitingScreen
     *
     * Note: This is nested class (specifically, a named inner class)
     */
    private class Ringer extends TimerTask
    {
        public void run()
        {
            number--;
            if (number < 0) number = 0;
            
            text.setText("Incoming call from "+callers[number]);
            player.play("ringtone.wav");
        }
    }
}