Location-based services - Tourist Route

This section provides the source code for the Tourist Route. For the complete Eclipse project ZIP file, see Forum Nokia.

The example includes the following classes:

TouristMIDlet

package com.nokia.example.location.tourist;

import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

import com.nokia.example.location.tourist.model.ConfigurationProvider;
import com.nokia.example.location.tourist.model.ProviderStatusListener;
import com.nokia.example.location.tourist.model.TouristData;
import com.nokia.example.location.tourist.ui.MessageUI;
import com.nokia.example.location.tourist.ui.TouristUI;

/**
 * Tourist Route MIDlet class.
 */
public class TouristMIDlet extends MIDlet implements ProviderStatusListener
{
    /** A static reference to Display object. */
    private static Display display = null;

    /** A Reference to TouristData. */
    private TouristData data = null;

    /** Lock object */
    private Object mutex = new Object();

    public TouristMIDlet()
    {
        super();
    }

    protected void startApp() throws MIDletStateChangeException
    {
        display = Display.getDisplay(this);

        if (ConfigurationProvider.isLocationApiSupported())
        {
            ConfigurationProvider.getInstance().autoSearch(this);
        }
        else
        {
            MessageUI.showApiNotSupported();
        }
    }

    protected void pauseApp()
    {
    }

    protected void destroyApp(boolean unconditional)
            throws MIDletStateChangeException
    {
    }

    /**
     * Getter method for Display reference.
     * 
     * @return reference to Display object.
     */
    public static Display getDisplay()
    {
        return display;
    }

    /**
     * Event indicating location provider is selected. MIDlet use may therefore
     * begin.
     * 
     * @see com.nokia.example.location.tourist.model.ProviderSelectedListener#providerSelectedEvent()
     */
    public void providerSelectedEvent()
    {
        // Attempt to acquire the mutex
        synchronized (mutex)
        {
            // Start scanning location updates. Also set the TouristData
            // reference data.
            MessageUI.showLocationProviderState();

            // Inform the user that MIDlet is looking for location data.
            data = new TouristData((ProviderStatusListener) this);
        }
    }

    /**
     * Event indication about the first location update. This method sets
     * Tourist UI visible. By using mutex object, we ensure TouristaData (data)
     * is created on providerSelectedEvent.
     * 
     * @see com.nokia.example.location.tourist.model.ProviderStatusListener#firstLocationUpdateEvent()
     */
    public void firstLocationUpdateEvent()
    {
        // Attempt to acquire the mutex
        synchronized (mutex)
        {
            TouristUI ui = new TouristUI(data);

            data.setTouristUI(ui);
            display.setCurrent(ui);
        }
    }
}

Utils

package com.nokia.example.location.tourist;

/**
 * A container class for general purpose utility method(s).
 */
public class Utils
{
    /**
     * Creates a String presentation of double value that is formatted to have a
     * certain number of decimals. Formatted output value does not include any
     * rounding rules. Output value is just truncated on the place that is
     * defined by received decimals parameter.
     * 
     * @param value -
     *            double value to be converted.
     * @param decimals -
     *            number of decimals in the String presentation.
     * @return a string representation of the argument.
     */
    public static String formatDouble(double value, int decimals)
    {
        String doubleStr = "" + value;
        int index = doubleStr.indexOf(".") != -1 ? doubleStr.indexOf(".")
                : doubleStr.indexOf(",");
        // Decimal point can not be found...
        if (index == -1) return doubleStr;
        // Truncate all decimals
        if (decimals == 0)
        {
            return doubleStr.substring(0, index);
        }

        int len = index + decimals + 1;
        if (len >= doubleStr.length()) len = doubleStr.length();

        double d = Double.parseDouble(doubleStr.substring(0, len));
        return String.valueOf(d);
    }
}

ConfigurationProvider

package com.nokia.example.location.tourist.model;

import javax.microedition.location.Criteria;
import javax.microedition.location.LocationException;
import javax.microedition.location.LocationProvider;
import javax.microedition.location.Orientation;

import com.nokia.example.location.tourist.ui.ProviderQueryUI;

/**
 * Model class that handles location providers search.
 */
public class ConfigurationProvider
{
    private static ConfigurationProvider INSTANCE = null;

    /** Array of free Criterias. */
    private static Criteria[] freeCriterias = null;

    /** String array of free criteria names. */
    private static String[] freeCriteriaNames = null;

    /** Array of Criterias that may cause costs */
    private static Criteria[] costCriterias = null;

    /** String array of non-free criteria names. */
    private static String[] costCriteriaNames = null;

    /** Reference to ProviderQueryUI viewer class. */
    private ProviderQueryUI queryUI = null;

    /** Selected criteria */
    private Criteria criteria = null;

    /** Selected location provider */
    private LocationProvider provider = null;

    /*
     * Static block that constructs the content of free and non-free Criterias.
     * This code block is performed before the constructor.
     */
    static
    {
        // 1. Create pre-defined free criterias

        freeCriterias = new Criteria[2];
        freeCriteriaNames = new String[2];

        Criteria crit1 = new Criteria();
        crit1.setHorizontalAccuracy(25); // 25m
        crit1.setVerticalAccuracy(25); // 25m
        crit1.setPreferredResponseTime(Criteria.NO_REQUIREMENT);
        crit1.setPreferredPowerConsumption(Criteria.POWER_USAGE_HIGH);
        crit1.setCostAllowed(false);
        crit1.setSpeedAndCourseRequired(true);
        crit1.setAltitudeRequired(true);
        crit1.setAddressInfoRequired(true);

        freeCriterias[0] = crit1;
        freeCriteriaNames[0] = "High details, cost not allowed";

        Criteria crit2 = new Criteria();
        crit2.setHorizontalAccuracy(Criteria.NO_REQUIREMENT);
        crit2.setVerticalAccuracy(Criteria.NO_REQUIREMENT);
        crit2.setPreferredResponseTime(Criteria.NO_REQUIREMENT);
        crit2.setPreferredPowerConsumption(Criteria.POWER_USAGE_HIGH);
        crit2.setCostAllowed(false); // allowed to cost
        crit2.setSpeedAndCourseRequired(false);
        crit2.setAltitudeRequired(false);
        crit2.setAddressInfoRequired(false);

        freeCriterias[1] = crit2;
        freeCriteriaNames[1] = "Low details and power consumption, cost not allowed";

        // 2. Create pre-defined cost criterias

        costCriterias = new Criteria[3];
        costCriteriaNames = new String[3];

        Criteria crit3 = new Criteria();
        crit3.setHorizontalAccuracy(25); // 25m
        crit3.setVerticalAccuracy(25); // 25m
        crit3.setPreferredResponseTime(Criteria.NO_REQUIREMENT);
        crit3.setPreferredPowerConsumption(Criteria.NO_REQUIREMENT);
        crit3.setCostAllowed(true);
        crit3.setSpeedAndCourseRequired(true);
        crit3.setAltitudeRequired(true);
        crit3.setAddressInfoRequired(true);

        costCriterias[0] = crit3;
        costCriteriaNames[0] = "High details, cost allowed";

        Criteria crit4 = new Criteria();
        crit4.setHorizontalAccuracy(500); // 500m
        crit4.setVerticalAccuracy(Criteria.NO_REQUIREMENT);
        crit4.setPreferredResponseTime(Criteria.NO_REQUIREMENT);
        crit4.setPreferredPowerConsumption(Criteria.NO_REQUIREMENT);
        crit4.setCostAllowed(true);
        crit4.setSpeedAndCourseRequired(true);
        crit4.setAltitudeRequired(true);
        crit4.setAddressInfoRequired(false);

        costCriterias[1] = crit4;
        costCriteriaNames[1] = "Medium details, cost allowed";

        // Least restrictive criteria (with default values)
        Criteria crit5 = null;

        costCriterias[2] = crit5;
        costCriteriaNames[2] = "Least restrictive criteria";
    }

    /**
     * Private constructor to force using getInstance() method.
     */
    private ConfigurationProvider()
    {
        queryUI = new ProviderQueryUI();
    }

    /**
     * Provide singleton instance of this class.
     * 
     * @return static instance of this class.
     */
    public static ConfigurationProvider getInstance()
    {
        if (INSTANCE == null)
        {
            // Enable use of this class when Location API is supported.
            if (isLocationApiSupported())
            {
                INSTANCE = new ConfigurationProvider();
            }
            else
            {
                INSTANCE = null;
            }
        }

        return INSTANCE;
    }

    /**
     * Checks whether Location API is supported.
     * 
     * @return a boolean indicating is Location API supported.
     */
    public static boolean isLocationApiSupported()
    {
        String version = System.getProperty("microedition.location.version");
        return (version != null && !version.equals("")) ? true : false;
    }

    public LocationProvider getSelectedProvider()
    {
        return provider;
    }

    /**
     * Search location provider by using pre-defined free and cost criterias.
     * 
     * @param listener - 
     *          a event listener that listens ProviderStatusLisneter events.
     */
    public void autoSearch(ProviderStatusListener listener)
    {
        try
        {
            for (int i = 0; i < freeCriterias.length; i++)
            {
                criteria = freeCriterias[i];

                provider = LocationProvider.getInstance(criteria);
                if (provider != null)
                {
                    // Location provider found, send a selection event.
                    listener.providerSelectedEvent();
                    return;
                }
            }

            if (queryUI.confirmCostProvider())
            {
                for (int i = 0; i < costCriterias.length; i++)
                {
                    criteria = costCriterias[i];

                    provider = LocationProvider.getInstance(criteria);
                    if (provider != null)
                    {
                        // Location provider found, send a selection event.
                        listener.providerSelectedEvent();
                        return;
                    }
                }
            }
            else
            {
                queryUI.showNoFreeServiceFound();
            }
        }
        catch (LocationException le)
        {
            queryUI.showOutOfService();
        }
    }

    public Orientation getOrientation()
    {
        try
        {
            return Orientation.getOrientation();
        }
        catch (LocationException e)
        {
            return null;
        }
    }

    /**
     * Tells whether orientation is supported.
     * 
     * @return a boolean indicating is orientation supported.
     */
    public boolean isOrientationSupported()
    {
        try
        {
            // Test whether Orientation instance can be obtained.
            Orientation.getOrientation();
            return true;
        }
        catch (LocationException e)
        {
            return false;
        }
    }
}

ControlPoints

package com.nokia.example.location.tourist.model;

import java.io.IOException;
import java.util.Enumeration;

import javax.microedition.location.Coordinates;
import javax.microedition.location.Landmark;
import javax.microedition.location.LandmarkException;
import javax.microedition.location.LandmarkStore;

/**
 * Model class that handles landmark store actions.
 */
public class ControlPoints
{
    private LandmarkStore store = null;

    private static final String STORENAME = "TOURIST_DEMO";

    private static final String DEFAULT_CATEGORY = null;

    private static ControlPoints INSTANCE = null;

    /**
     * Constructs instance of this class. Makes sure that landmark store
     * instance exists.
     */
    private ControlPoints()
    {
        String name = null;
        // Try to find landmark store called "TOURIST_DEMO".
        try
        {
            store = LandmarkStore.getInstance(STORENAME);
        }
        catch (NullPointerException npe)
        {
            // This should never occur.
        }

        // Check whether landmark store exists.
        if (store == null)
        {
            // Landmark store does not exist.

            try
            {
                // Try to create landmark store named "TOURIST_DEMO".
                LandmarkStore.createLandmarkStore(STORENAME);
                name = STORENAME;
            }
            catch (IllegalArgumentException iae)
            {
                // Name is too long or landmark store with the specified
                // name already exists. This Exception should not occur,
                // because we have earlier tryed to created instance of
                // this landmark store.
            }
            catch (IOException e)
            {
                // Landmark store couldn't be created due to an I/O error
            }
            catch (LandmarkException e)
            {
                // Implementation does not support creating new landmark
                // stores.
            }

            store = LandmarkStore.getInstance(name);
        }
    }

    /**
     * Singleton patterns getInstance method. Makes sure that only one instance
     * of this class is alive at once.
     * 
     * @return instance of this class.
     */
    public static ControlPoints getInstance()
    {
        if (INSTANCE == null)
        {
            INSTANCE = new ControlPoints();
        }

        return INSTANCE;
    }

    /**
     * Find a Landmark from the landmark store using a index.
     * 
     * @param index -
     *            the landmark in the store.
     * @return Landmark from landmark store.
     */
    public Landmark findLandmark(int index)
    {
        Landmark lm = null;

        Enumeration cps = ControlPoints.getInstance().getLandMarks();
        int counter = 0;

        while (cps.hasMoreElements())
        {
            lm = (Landmark) cps.nextElement();

            if (counter == index)
            {
                break;
            }
            counter++;
        }

        return lm;
    }

    /**
     * Find nearest landmark to coordinate parameter from the landmarkstore.
     */
    public Landmark findNearestLandMark(Coordinates coord)
    {
        Landmark landmark = null;

        double latRadius = 0.1;
        double lonRadius = 0.1;

        float dist = Float.MAX_VALUE;

        try
        {
            // Generate enumeration of Landmarks that are close to coord.
            Enumeration enu = store.getLandmarks(null, coord.getLatitude()
                    - latRadius, coord.getLatitude() + latRadius, coord
                    .getLongitude() - lonRadius, coord.getLongitude() 
                    + lonRadius);
            float tmpDist;
            Landmark tmpLandmark = null;

            while (enu.hasMoreElements())
            {
                tmpLandmark = (Landmark) enu.nextElement();
                tmpDist = tmpLandmark.getQualifiedCoordinates().distance(coord);

                if (tmpDist < dist)
                {
                    landmark = tmpLandmark;
                }
            }
        }
        catch (IOException ioe)
        {
            // I/O error happened when accessing the landmark store.
            return null;
        }

        return landmark;
    }

    public Enumeration getLandMarks()
    {
        Enumeration enu = null;

        try
        {
            enu = store.getLandmarks();
        }
        catch (IOException ioe)
        {
            // I/O error happened when accessing the landmark store.
        }

        return enu;
    }

    public void addLandmark(Landmark landmark) throws IOException
    {
        store.addLandmark(landmark, DEFAULT_CATEGORY);
    }

    public void updateLandmark(Landmark landmark) throws IOException,
            LandmarkException
    {
        store.updateLandmark(landmark);
    }

    public void removeLandmark(Landmark landmark) throws IOException,
            LandmarkException
    {
        store.deleteLandmark(landmark);
    }

}

ProviderStatusListener

package com.nokia.example.location.tourist.model;

/**
 * Listener interface for location providers status information.
 */
public interface ProviderStatusListener
{
    /**
     * A Notification event that location provider has been selected.
     */
    public void providerSelectedEvent();

    /**
     * A Notification event about the first location update.
     */
    public void firstLocationUpdateEvent();
}

TouristData

package com.nokia.example.location.tourist.model;

import java.util.Enumeration;

import javax.microedition.location.AddressInfo;
import javax.microedition.location.Coordinates;
import javax.microedition.location.Landmark;
import javax.microedition.location.Location;
import javax.microedition.location.LocationException;
import javax.microedition.location.LocationListener;
import javax.microedition.location.LocationProvider;
import javax.microedition.location.ProximityListener;
import javax.microedition.location.QualifiedCoordinates;

import com.nokia.example.location.tourist.ui.TouristUI;

/**
 * Model class that handles LocationListeners and ProximityListeners events.
 */
public class TouristData implements LocationListener, ProximityListener
{
    /** A Reference to Tourist UI Canvas */
    private TouristUI touristUI = null;

    /** Coordinate detection threshold radius in meters */
    public static final float PROXIMITY_RADIUS = 100.0f;

    /** Previous coordinates outside the distance threshold area (20m) */
    private Coordinates prevCoordinates = null;

    /** The first location update done. */
    private boolean firstLocationUpdate = false;

    private ProviderStatusListener statusListener = null;

    /**
     * Construct instance of this model class.
     */
    public TouristData(ProviderStatusListener listener)
    {
        statusListener = listener;

        ConfigurationProvider config = ConfigurationProvider.getInstance();

        // 1. Register LocationListener
        LocationProvider provider = config.getSelectedProvider();
        if (provider != null)
        {
            int interval = -1; // default interval of this provider
            int timeout = 0; // parameter has no effect.
            int maxage = 0; // parameter has no effect.

            provider.setLocationListener(this, interval, timeout, maxage);
        }

        // 2. Register ProxymityListener to each Landmark found from the
        // Landmark store.
        ControlPoints cp = ControlPoints.getInstance();

        Enumeration enumeration = cp.getLandMarks();
        if (enumeration != null)
        {
            while (enumeration.hasMoreElements())
            {
                Landmark lm = (Landmark) enumeration.nextElement();
                createProximityListener(lm.getQualifiedCoordinates());
            }
        }
    }

    /**
     * Setter method to TouristUI reference.
     * 
     * @param ui -
     *            Reference to TouristUI.
     */
    public void setTouristUI(TouristUI ui)
    {
        touristUI = ui;
    }

    /**
     * Adds new ProximityListener to the location provider. This method is
     * called when constructing instance of this calss and when a new landmark
     * is saved by using LandmarkEditorUI.
     * 
     * @param coordinates
     */
    public void createProximityListener(Coordinates coordinates)
    {
        try
        {
            LocationProvider.addProximityListener(this, coordinates,
                    PROXIMITY_RADIUS);
        }
        catch (LocationException e)
        {
            // Platform does not have resources to add a new listener
            // and coordinates to be monitored or does not support
            // proximity monitoring at all
        }
    }

    /**
     * locationUpdated event from LocationListener interface. This method starts
     * a new thread to prevent blocking, because listener method MUST return
     * quickly and should not perform any extensive processing.
     * 
     * Location parameter is set final, so that the anonymous Thread class can
     * access the value.
     */
    public void locationUpdated(LocationProvider provider,
            final Location location)
    {
        // First location update arrived, so we may show the UI (TouristUI)
        if (!firstLocationUpdate)
        {
            firstLocationUpdate = true;
            statusListener.firstLocationUpdateEvent();
        }

        if (touristUI != null)
        {
            new Thread()
            {
                public void run()
                {
                    if (location != null && location.isValid())
                    {
                        AddressInfo address = location.getAddressInfo();
                        QualifiedCoordinates coord = location.getQualifiedCoordinates();
                        float speed = location.getSpeed();
                        
                        touristUI.setInfo(address, coord, speed);
                        touristUI.setProviderState("Available");
                        touristUI.repaint();
                    }
                    else
                    {
                        touristUI.setProviderState("Not valid location data");
                        touristUI.repaint();
                    }
                }
            }.start();
        }
    }

    /**
     * providerStateChanged event from LocationListener interface. This method
     * starts a new thread to prevent blocking, because listener method MUST
     * return quickly and should not perform any extensive processing.
     * 
     * newState parameter is set final, so that the anonymous Thread class can
     * access the value.
     */
    public void providerStateChanged(LocationProvider provider,
            final int newState)
    {
        if (touristUI != null)
        {
            new Thread()
            {
                public void run()
                {
                    switch (newState) {
                        case LocationProvider.AVAILABLE:
                            touristUI.setProviderState("Available");
                            break;
                        case LocationProvider.OUT_OF_SERVICE:
                            touristUI.setProviderState("Out of service");
                            break;
                        case LocationProvider.TEMPORARILY_UNAVAILABLE:
                            touristUI
                                    .setProviderState("Temporarily unavailable");
                            break;
                        default:
                            touristUI.setProviderState("Unknown");
                            break;
                    }

                    touristUI.repaint();
                }
            }.start();
        }
    }

    /**
     * proximity event from ProximityListener interface. The listener is called
     * only once when the terminal enters the proximity of the registered
     * coordinates. That's why no this method should not need to start a new
     * thread.
     */
    public void proximityEvent(Coordinates coordinates, Location location)
    {
        if (touristUI != null)
        {
            touristUI.setProviderState("Control point found!");

            Landmark lm = ControlPoints.getInstance().findNearestLandMark(
                    coordinates);

            // landmark found from landmark store
            if (lm != null)
            {
                touristUI.setInfo(lm.getAddressInfo(), lm
                        .getQualifiedCoordinates(), location.getSpeed());
            }
            // landmark not found from the landmark store, this should not never
            // happen!
            else
            {
                touristUI.setInfo(location.getAddressInfo(), location
                        .getQualifiedCoordinates(), location.getSpeed());
            }

            touristUI.repaint();
        }
    }

    /**
     * monitoringStateChanged event from ProximityListener interface. Notifies
     * that the state of the proximity monitoring has changed. That's why this
     * method should not need to start a new thread.
     */
    public void monitoringStateChanged(boolean isMonitoringActive)
    {
        if (touristUI != null)
        {
            if (isMonitoringActive)
            {
                // proximity monitoring is active
                touristUI.setProximityState("Active");
            }
            else
            {
                // proximity monitoring can't be done currently.
                touristUI.setProximityState("Off");
            }

            touristUI.repaint();
        }
    }
}

CompassUI

package com.nokia.example.location.tourist.ui;

import java.io.IOException;

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.location.Orientation;

import com.nokia.example.location.tourist.TouristMIDlet;
import com.nokia.example.location.tourist.model.ConfigurationProvider;

/**
 * Viewer class representing a compass.
 */
public class CompassUI extends Canvas implements CommandListener, Runnable
{
    /** Constant value for X coordinate. Used in arrays. */
    private final int X = 0;

    /** Constant value for Y coordinate. Used in arrays. */
    private final int Y = 1;

    /** Compass center X point */
    private int centerX;

    /** Compass center Y point */
    private int centerY;

    /** Constant value representing one degree. */
    private final float degree = (float) (2 * Math.PI / 360.0);

    /** Current compass azimuth. */
    private float azimuth = 0.0f;

    /**
     * Is orientation relative to the magnetic field of the Earth or true north
     * and gravity.
     */
    private boolean isMagnetic;

    /** Flag telling is the compass update thread active */
    private boolean threadActive = false;

    /** Sleep 1000ms during each compass update */
    private final long SLEEPTIME = 1000;

    /** A reference to Route UI */
    private Displayable route = null;

    /** A reference to Pitch and Roll UI */
    private Displayable pitchrollUI = null;

    /** Command that swithes current displayable to Route UI */
    private Command routeCmd = new Command("Route", Command.BACK, 1);

    /** Command that swithes current displayable to Pitch and Roll UI */
    private Command prCmd = new Command("Pitch and Roll", Command.OK, 1);

    /** Compss background image. */
    private Image compassImage = null;

    /**
     * Construct instance of this displayable.
     * 
     * @param route
     *            is a reference to Route UI.
     */
    public CompassUI(Displayable route)
    {
        this.route = route;
        pitchrollUI = new PitchRollUI(this);

        loadCompassImage();

        centerX = getWidth() / 2;
        centerY = getHeight() / 2;

        addCommand(routeCmd);
        addCommand(prCmd);

        setCommandListener(this);
    }

    /**
     * Load compass backgound image from JAR file.
     */
    private void loadCompassImage()
    {
        String imageName = "/compass_small.gif";

        if (getWidth() > 160)
        {
            imageName = "/compass_large.gif";
        }

        try
        {
            compassImage = Image.createImage(getClass().getResourceAsStream(
                    imageName));
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }

    /**
     * VM calls this method immediately prior to this Canvas being made visible
     * on the display.
     */
    protected void showNotify()
    {
        // Actives compass update thread.
        threadActive = true;
        new Thread(this).start();
    }

    /**
     * VM calls this method shortly after the Canvas has been removed from the
     * display.
     */
    protected void hideNotify()
    {
        // Stops the thread.
        threadActive = false;
    }

    /**
     * Renders the canvas.
     * 
     * @param g -
     *            the Graphics object to be used for rendering the Canvas
     */
    protected void paint(Graphics g)
    {
        // clean up canvas
        g.setColor(255, 255, 255);
        g.fillRect(0, 0, getWidth(), getHeight());
        int spikeLen = 5;
        int len = (compassImage.getWidth() / 2) - spikeLen;

        // draw compass background
        g.drawImage(compassImage, (getWidth() - compassImage.getWidth()) / 2,
                centerY, Graphics.LEFT | Graphics.VCENTER);

        // draw compass arrow
        g.setColor(0, 0, 255);
        drawArrow(g, degree * azimuth, len, spikeLen);

        // draw orientation type
        g.setColor(0, 0, 255);
        String otext = "True North";
        if (!isMagnetic)
        {
            otext = "Magnetic field";
        }
        g.drawString("Orientation: " + otext, 0, getHeight(), Graphics.BOTTOM
                | Graphics.LEFT);
    }

    /**
     * Draw a compass arrow rotated to a certain angle.
     * 
     * @param g
     *            is a reference to Graphics object.
     * @param angle
     *            in degrees [0.0,360.0)
     * @param len
     *            is arrows length.
     * @param spikeLen
     *            is length of arrows spike.
     */
    private void drawArrow(Graphics g, float angle, int len, int spikeLen)
    {
        int a[] = rotate(angle, 0, -(len - spikeLen));
        int b[] = rotate(angle, -spikeLen, -(len - spikeLen));
        int c[] = rotate(angle, 0, -len);
        int d[] = rotate(angle, spikeLen, -(len - spikeLen));
        int e[] = rotate(angle + (degree * 180.0), 0, -len);

        // use red foreground color
        g.setColor(255, 0, 0);
        g.drawLine(centerX, centerY, centerX + a[X], centerY + a[Y]);
        g.drawLine(centerX + b[X], centerY + b[Y], centerX + d[X], centerY
                + d[Y]);
        g.drawLine(centerX + b[X], centerY + b[Y], centerX + c[X], centerY
                + c[Y]);
        g.drawLine(centerX + c[X], centerY + c[Y], centerX + d[X], centerY
                + d[Y]);

        // use black foreground color
        g.setColor(0, 0, 0);
        g.drawLine(centerX, centerY, centerX + e[X], centerY + e[Y]);
    }

    /**
     * Rotate point (x,y) by degrees that angle parameter defines. The new
     * coordinate calculation is performed with a 2x2 rotate matrix.
     * 
     * @param angle
     *            to be rotated
     * @param x
     *            coordinate
     * @param y
     *            coordinate
     * @return new coordinate pair in int array format [x,y]
     */
    private int[] rotate(double angle, int x, int y)
    {
        int rotated[] = new int[2];
        rotated[X] = (int) (Math.cos(angle) * x + Math.sin(angle) * y);
        rotated[Y] = (int) (-Math.sin(angle) * x + Math.cos(angle) * y);
        return rotated;
    }

    /**
     * run method from Runnable interface. Updates azimuth, pitch and roll
     * values and repaints the canvas.
     * 
     * If Orientation is supported on the terminal, compass sensor is either 2D
     * or 3D. If the terminals compass sensor providers only compass azimuth,
     * pitch and roll values are Float.NaN.
     * 
     * @see HideNotify() method.
     */
    public void run()
    {
        // Keep the thread running until another displayable is set visible.
        // See also hideNotify() method.
        while (threadActive)
        {
            Orientation orientation = ConfigurationProvider.getInstance()
                    .getOrientation();

            if (orientation != null)
            {
                isMagnetic = orientation.isOrientationMagnetic();
                azimuth = orientation.getCompassAzimuth();
            }

            repaint();

            try
            {
                // Pause this thread for a secord before next update.
                Thread.sleep(SLEEPTIME);
            }
            catch (InterruptedException e)
            {
            }
        }
    }

    /**
     * Event indicating when a command button is pressed.
     * 
     * @see javax.microedition.lcdui.CommandListener#commandAction(javax.microedition.lcdui.Command,
     *      javax.microedition.lcdui.Displayable)
     */
    public void commandAction(Command command, Displayable d)
    {
        if (command == routeCmd)
        {
            TouristMIDlet.getDisplay().setCurrent(route);
        }
        else if (command == prCmd)
        {
            TouristMIDlet.getDisplay().setCurrent(pitchrollUI);
        }
    }

}

LandmarkEditorUI

package com.nokia.example.location.tourist.ui;

import java.io.IOException;
import java.util.Enumeration;

import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.List;
import javax.microedition.lcdui.StringItem;
import javax.microedition.lcdui.TextField;
import javax.microedition.location.AddressInfo;
import javax.microedition.location.Landmark;
import javax.microedition.location.LandmarkException;
import javax.microedition.location.QualifiedCoordinates;

import com.nokia.example.location.tourist.TouristMIDlet;
import com.nokia.example.location.tourist.Utils;
import com.nokia.example.location.tourist.model.ControlPoints;
import com.nokia.example.location.tourist.model.TouristData;

/**
 * Viewer class enabling landmarks address info monifications.
 */
class LandmarkEditorUI extends Form implements CommandListener
{
    // Landmark fields, (*) = required field
    private TextField nameField = new TextField("Name (*):", "", 20,
            TextField.ANY);

    private TextField descField = new TextField("Description (*):", "", 40,
            TextField.ANY);

    // AddressInfo fields
    private TextField countryField = new TextField("Country:", "", 20,
            TextField.ANY);

    private TextField stateField = new TextField("State:", "", 20,
            TextField.ANY);

    private TextField cityField = new TextField("City:", "", 20, TextField.ANY);

    private TextField streetField = new TextField("Street:", "", 20,
            TextField.ANY);

    private TextField buildingNameField = new TextField("Building name (*):",
            "", 20, TextField.ANY);

    // Coordinate information
    private StringItem lat = new StringItem("Lat:", "");

    private StringItem lon = new StringItem("Lon:", "");

    private StringItem alt = new StringItem("Alt:", "");

    /** Landmark selector list */
    private List list = new List("Landmarks:", List.EXCLUSIVE);

    private Command saveNewCmd = new Command("Save", Command.OK, 1);

    private Command saveUpdatedCmd = new Command("Save", Command.OK, 1);

    private Command updateCmd = new Command("Update", Command.OK, 1);

    private Command removeCmd = new Command("Remove", Command.OK, 1);

    private Command listCmd = new Command("List", Command.OK, 1);

    private Command routeCmd = new Command("Route", Command.BACK, 1);

    public static final int MODE_UPDATE = 0;

    public static final int MODE_ADDNEW = 1;

    private QualifiedCoordinates coord = null;

    private Displayable route = null;

    private TouristData data = null;

    /**
     * Construct Landmark Editor UI Form.
     * 
     * @param route -
     *            a reference of Route UI.
     * @param data -
     *            a reference to TouristData model layer class.
     */
    public LandmarkEditorUI(Displayable route, TouristData data)
    {
        super("Landmark Editor");

        this.route = route;
        this.data = data;

        // route and list commands always available on landmark editor
        addCommand(routeCmd);
        addCommand(listCmd);
        setCommandListener(this);

        // route command always available on landmarks list
        list.addCommand(routeCmd);
        list.setCommandListener(this);
    }

    /**
     * Initialize UI components and show the landmark editor.
     */
    public void showEditor(QualifiedCoordinates newCoord, int mode)
    {
        this.coord = newCoord;

        // initialize coordinate values
        lat.setText(Utils.formatDouble(newCoord.getLatitude(), 3));
        lon.setText(Utils.formatDouble(newCoord.getLongitude(), 3));
        alt.setText(Utils.formatDouble(newCoord.getAltitude(), 1));

        // initialize editable test field values
        nameField.setString("");
        descField.setString("");
        countryField.setString("");
        stateField.setString("");
        cityField.setString("");
        streetField.setString("");
        buildingNameField.setString("");

        deleteAll();
        append(nameField);
        append(descField);
        append(countryField);
        append(stateField);
        append(cityField);
        append(streetField);
        append(buildingNameField);

        append(lat);
        append(lon);
        append(alt);

        // Update existing landmark.
        if (mode == MODE_UPDATE)
        {
            Landmark lm = ControlPoints.getInstance().findNearestLandMark(
                    newCoord);

            if (lm != null)
            {
                nameField.setString(lm.getName());
                descField.setString(lm.getDescription());

                AddressInfo info = lm.getAddressInfo();
                if (info != null)
                {
                    countryField.setString(info.getField(AddressInfo.COUNTRY));
                    stateField.setString(info.getField(AddressInfo.STATE));
                    cityField.setString(info.getField(AddressInfo.CITY));
                    streetField.setString(info.getField(AddressInfo.STREET));
                    buildingNameField.setString(info
                            .getField(AddressInfo.BUILDING_NAME));
                }
            }

            removeCommand(updateCmd);
            removeCommand(saveNewCmd);
            addCommand(saveUpdatedCmd);
            addCommand(listCmd);
        }
        // Add new landmark to the landmark store.
        else if (mode == MODE_ADDNEW)
        {
            removeCommand(updateCmd);
            removeCommand(saveUpdatedCmd);
            addCommand(saveNewCmd);
            addCommand(listCmd);
        }

        TouristMIDlet.getDisplay().setCurrent(this);
    }

    /**
     * Show landmark selector List. Content of the list is refreshed each time
     * this method is called.
     */
    public void showList()
    {
        list.deleteAll();

        Landmark lm = null;
        Enumeration landmarks = ControlPoints.getInstance().getLandMarks();

        // Check whether landmarks can be founds from the Landmark store.
        if (landmarks != null)
        {
            while (landmarks.hasMoreElements())
            {
                lm = ((Landmark) landmarks.nextElement());
                list.append(lm.getName(), null);
            }

            list.addCommand(updateCmd);
            list.addCommand(removeCmd);
        }
        // No landmarks found (list is empty)
        else
        {
            list.removeCommand(updateCmd);
            list.removeCommand(removeCmd);
        }

        TouristMIDlet.getDisplay().setCurrent(list);
    }

    /**
     * Check wheter any required fields are missing. (*) on the TextFields label
     * indicates that field is mandatory.
     * 
     * @return Name of the missing field name or null indicating no required
     *         fields are missing.
     */
    private String checkRequiredFields()
    {
        if (nameField.getString().equals(""))
        {
            return "Name";
        }
        else if (nameField.getString().equals(""))
        {
            return "Description";
        }
        else if (buildingNameField.getString().equals(""))
        {
            return "Building name";
        }

        return null;
    }

    /**
     * Generate landmark from UI component values.
     * 
     * @return Genereated Landmark.
     */
    private Landmark generateLandmark()
    {
        String field = checkRequiredFields();
        if (field != null)
        {
            Alert alert = new Alert("Error", "Value for required field "
                    + field + " is missing.", null, AlertType.ERROR);
            alert.setTimeout(3000); // 3 secs

            TouristMIDlet.getDisplay().setCurrent(alert, this);
            return null;
        }

        AddressInfo info = new AddressInfo();
        info.setField(AddressInfo.COUNTRY, countryField.getString());
        info.setField(AddressInfo.STATE, stateField.getString());
        info.setField(AddressInfo.CITY, cityField.getString());
        info.setField(AddressInfo.STREET, streetField.getString());
        info.setField(AddressInfo.BUILDING_NAME, buildingNameField.getString());

        Landmark lm = new Landmark(nameField.getString(),
                descField.getString(), coord, info);

        return lm;
    }

    /**
     * Event indicating when a command button is pressed.
     * 
     * @see javax.microedition.lcdui.CommandListener#commandAction(javax.microedition.lcdui.Command,
     *      javax.microedition.lcdui.Displayable)
     */
    public void commandAction(Command command, Displayable displayable)
    {
        Landmark landmark = null;

        // Add new Landmark to the LandmarkStore
        if (command == saveNewCmd)
        {
            landmark = generateLandmark();
            if (landmark != null)
            {
                try
                {
                    ControlPoints.getInstance().addLandmark(landmark);
                    data.createProximityListener(coord);
                }
                catch (IOException e)
                {
                    Alert alert = new Alert("Error",
                            "I/O Error during add operation", null,
                            AlertType.ERROR);
                    alert.setTimeout(3000); // 3 secs
                    TouristMIDlet.getDisplay().setCurrent(alert);
                }

                // New landmark is available on the list
                showList();
            }
        }
        // Update existing landmark
        else if (command == saveUpdatedCmd)
        {
            landmark = generateLandmark();
            if (landmark != null)
            {
                try
                {
                    ControlPoints.getInstance().updateLandmark(landmark);
                    data.createProximityListener(coord);
                }
                catch (IOException e)
                {
                    Alert alert = new Alert("Error",
                            "I/O Error during update operation", null,
                            AlertType.ERROR);
                    alert.setTimeout(3000); // 3 secs
                    TouristMIDlet.getDisplay().setCurrent(alert);
                }
                catch (LandmarkException e)
                {
                    // Landmark instance passed as the parameter does not
                    // belong to this LandmarkStore or does not exist in the
                    // store any more.
                    Alert alert = new Alert("Error",
                            "Unexpected error: can not find landmark from "
                            + "the landmark store.", null,
                            AlertType.ERROR);
                    alert.setTimeout(3000); // 3 secs
                    TouristMIDlet.getDisplay().setCurrent(alert);
                }

                // Updated landmark is available on the list
                showList();
            }
        }
        // Go back to the previous screen
        else if (command == routeCmd)
        {
            TouristMIDlet.getDisplay().setCurrent(route);
        }
        // Show landmark editor for the selected landmark.
        else if (command == updateCmd)
        {
            int index = list.getSelectedIndex();
            landmark = ControlPoints.getInstance().findLandmark(index);

            showEditor(landmark.getQualifiedCoordinates(), MODE_UPDATE);
        }
        // Remove selected Landmark from LandmarkStore
        else if (command == removeCmd)
        {
            try
            {
                int index = list.getSelectedIndex();
                landmark = ControlPoints.getInstance().findLandmark(index);
                ControlPoints.getInstance().removeLandmark(landmark);

                Alert alert = new Alert("Information",
                        "Landmark removed successfully.", null, AlertType.INFO);
                alert.setTimeout(3000); // 3 secs
                TouristMIDlet.getDisplay().setCurrent(alert);
            }
            catch (IOException e)
            {
                Alert alert = new Alert("Error", "I/O Error during operation",
                        null, AlertType.ERROR);
                alert.setTimeout(3000); // 3 secs
                TouristMIDlet.getDisplay().setCurrent(alert);
            }
            catch (LandmarkException le)
            {
                Alert alert = new Alert("Error",
                        "landmark can not be found from the landmark store.",
                        null, AlertType.ERROR);
                alert.setTimeout(3000); // 3 secs
                TouristMIDlet.getDisplay().setCurrent(alert);
            }

            showList();
        }
        // Show the list of Landmarks
        else if (command == listCmd)
        {
            showList();
        }
    }
}

MessageUI

package com.nokia.example.location.tourist.ui;

import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Gauge;

import com.nokia.example.location.tourist.TouristMIDlet;

/**
 * Viewer class containing a general suppose Alert notification(s).
 */
public class MessageUI
{
    /**
     * Shows a alert that Location API is not supported.
     */
    public static void showApiNotSupported()
    {
        Alert alert = new Alert("Information",
                "Device do not support Location API", null, AlertType.INFO);
        TouristMIDlet.getDisplay().setCurrent(alert);
    }

    /**
     * Shows a alert that Location data is not available.
     */
    public static void showLocationDataNotAvailable()
    {
        Alert alert = new Alert("Information",
                "Location data is not yet available.", null, AlertType.INFO);
        TouristMIDlet.getDisplay().setCurrent(alert);
    }

    /**
     * Show a status dialog informing about state of location provider.
     */
    public static void showLocationProviderState()
    {
        Gauge indicator = new Gauge(null, false, 50, 1);
        indicator.setValue(Gauge.CONTINUOUS_RUNNING);

        Alert alert = new Alert("Information",
                "Please wait, looking for location data....", null,
                AlertType.INFO);
        alert.setIndicator(indicator);

        TouristMIDlet.getDisplay().setCurrent(alert);
    }
}

PitchRollUI

package com.nokia.example.location.tourist.ui;

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Graphics;
import javax.microedition.location.Orientation;

import com.nokia.example.location.tourist.TouristMIDlet;
import com.nokia.example.location.tourist.model.ConfigurationProvider;

/**
 * Viewer class that renders orientations pitch and roll values to meters.
 */
public class PitchRollUI extends Canvas implements CommandListener, Runnable
{
    private CompassUI compass;

    private float pitch = Float.NaN;

    private float roll = Float.NaN;

    /** Flag telling is the compass update thread active */
    private boolean threadActive = false;

    /** Sleep 1000ms during each compass update */
    private final long SLEEPTIME = 1000;

    private Command compassCmd = new Command("Compass", Command.BACK, 1);

    public PitchRollUI(CompassUI compass)
    {
        this.compass = compass;

        addCommand(compassCmd);
        setCommandListener(this);
    }

    /**
     * VM calls this method immediately prior to this Canvas being made visible
     * on the display.
     */
    protected void showNotify()
    {
        // Actives compass update thread.
        threadActive = true;
        new Thread(this).start();
    }

    /**
     * VM calls this method shortly after the Canvas has been removed from the
     * display.
     */
    protected void hideNotify()
    {
        // Stops the thread.
        threadActive = false;
    }

    /**
     * run method from Runnable interface. Updates azimuth, pitch and roll
     * values and repaints the canvas.
     * 
     * If Orientation is supported on the terminal, support level may be either
     * 2D or 3D depending on the compass sensor. If the sensor providers only
     * compass azimuth, pitch and roll values are Float.NaN.
     */
    public void run()
    {
        // Run this thread until this displayable is not visiable.
        // See also hideNotify() method.
        while (threadActive)
        {
            Orientation orientation = ConfigurationProvider.getInstance()
                    .getOrientation();

            if (orientation != null)
            {
                pitch = orientation.getPitch();
                roll = orientation.getRoll();
            }

            repaint();

            try
            {
                // Pause this thread for a secord before next update.
                Thread.sleep(SLEEPTIME);
            }
            catch (InterruptedException e)
            {
            }
        }
    }

    /**
     * Renders the canvas.
     * 
     * @param g -
     *            the Graphics object to be used for rendering the Canvas
     */
    protected void paint(Graphics g)
    {
        // clean up canvas
        g.setColor(255, 255, 255);
        g.fillRect(0, 0, getWidth(), getHeight());

        paintPitch(g);
        paintRoll(g);
    }

    /**
     * Draws a customized meter with a scale.
     */
    protected void drawMeter(Graphics g, int min, int max, int smallStep,
            int largeStep, int baseline, int margin)
    {
        double position;
        double scale = 100.0; // scale value to minimize rounding error
        int x;

        g.setColor(0x0000ff);
        g.drawLine(margin, baseline, getWidth(), baseline);

        for (int i = min; i <= max; i += smallStep)
        {
            position = (((i + max) * scale * (getWidth() - margin)) / (2 * max))
                    / scale;
            x = margin + (int) position;
            g.drawLine(x, baseline - 3, x, baseline + 3);
        }

        for (int i = min; i <= max; i += largeStep)
        {
            position = (((i + max) * scale * (getWidth() - margin)) / (2 * max))
                    / scale;
            x = margin + (int) position;
            g.drawLine(x, baseline - 5, x, baseline + 5);
        }
    }

    /**
     * Paint pitch meter and place the current pitch value to the meter.
     * 
     * @param g -
     *            the Graphics object to be used for rendering the Canvas
     */
    protected void paintPitch(Graphics g)
    {
        int baseline = 10;
        int textAreaWidth = 50;
        double position;

        g.setColor(0x000000);
        g.drawString("Pitch", 0, baseline - 5, Graphics.TOP | Graphics.LEFT);
        g.drawString("-90", textAreaWidth + 1, baseline, Graphics.TOP
                | Graphics.LEFT);
        g.drawString("0", textAreaWidth + (getWidth() - textAreaWidth) / 2,
                baseline, Graphics.TOP | Graphics.HCENTER);
        g.drawString("+90", getWidth(), baseline, Graphics.TOP
                | Graphics.RIGHT);

        drawMeter(g, -90, 90, 10, 30, baseline, textAreaWidth);

        // Draw the marker only if terminals pitch is available.
        if (pitch != Float.NaN)
        {
            position = (((pitch + 90.0) * 100 * (getWidth() - textAreaWidth)) / (2 * 90)) / 100;
            g.setColor(0x000000);
            g.fillRect(textAreaWidth + (int) position - 3, baseline - 2, 5, 5);
        }
    }

    /**
     * Paint roll meter and place the current pitch value to the meter.
     * 
     * @param g -
     *            the Graphics object to be used for rendering the Canvas
     */
    protected void paintRoll(Graphics g)
    {
        int baseline = 40;
        int textAreaWidth = 50;
        double position;

        g.setColor(0x000000);
        g.drawString("Roll", 0, baseline - 5, Graphics.TOP | Graphics.LEFT);
        g.drawString("-180", textAreaWidth + 1, baseline, Graphics.TOP
                | Graphics.LEFT);
        g.drawString("0", textAreaWidth + (getWidth() - textAreaWidth) / 2,
                baseline, Graphics.TOP | Graphics.HCENTER);
        g.drawString("+180", getWidth(), baseline, Graphics.TOP
                | Graphics.RIGHT);

        drawMeter(g, -180, 180, 15, 60, baseline, textAreaWidth);

        // Draw the marker only if terminals roll is available.
        if (roll != Float.NaN)
        {
            position = (((roll + 180.0) * 100 * (getWidth() - textAreaWidth)) / (2 * 180)) / 100;
            g.setColor(0x000000);
            g.fillRect(textAreaWidth + (int) position - 3, baseline - 2, 5, 5);
        }
    }

    /**
     * Event indicating when a command button is pressed.
     * 
     * @see javax.microedition.lcdui.CommandListener#commandAction(javax.microedition.lcdui.Command,
     *      javax.microedition.lcdui.Displayable)
     */
    public void commandAction(Command command, Displayable d)
    {
        if (command == compassCmd)
        {
            TouristMIDlet.getDisplay().setCurrent(compass);
        }
    }

}

ProvideQueryUI

package com.nokia.example.location.tourist.ui;

import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.StringItem;

import com.nokia.example.location.tourist.TouristMIDlet;

/**
 * Viewer class that is responsible for all the UI actions when the application
 * is seaching for the location provider.
 */
public class ProviderQueryUI
{
    /** Status information Form */
    private Form searchForm = new Form("Seaching location provider...");

    /** StringItem showing the current status. */
    private StringItem infoItem = new StringItem("Status:", "");

    /** Provider cost selection command - Yes. */
    private Command yesCmd = new Command("Yes", Command.OK, 1);

    /** Provider cost selection command - No. */
    private Command noCmd = new Command("No", Command.STOP, 1);

    /** A boolean indicating may user allow location provider cost. */
    private boolean result = false;

    private static final String COST_QUERY_MESSAGE = "Cost free location providers can not be found. Do you with continue "
            + "search with providers that cost?";

    private static final String OUT_OF_SERVICE_MESSAGE = "All Location providers are currently out of service. Please unsure "
            + "that location-providing module is properly connected.";

    private static final String SEACHING_FREE_PROVIDERS = "Seaching for free location providers.";

    private static final String SEACHING_COST_PROVIDERS = "Seaching for providers that may cost.";

    private static final String NOT_FOUND_MESSAGE = "Try again after location-providing module is properly connected.";

    private static final String NO_FREE_SERVICE_SERVICE_MESSAGE = "Cost free location providers can not be found. Please ensure "
            + "that location-providing module is properly connected.";

    /**
     * Construct the UI with default values.
     */
    public ProviderQueryUI()
    {
        infoItem.setText(SEACHING_FREE_PROVIDERS);
        searchForm.append(infoItem);
    }

    /**
     * Show out of service error message.
     */
    public void showOutOfService()
    {
        Alert alert = new Alert("Error", OUT_OF_SERVICE_MESSAGE, null,
                AlertType.ERROR);
        alert.setTimeout(Alert.FOREVER);
        TouristMIDlet.getDisplay().setCurrent(alert, searchForm);
        infoItem.setText(NOT_FOUND_MESSAGE);
    }

    /**
     * Show no cost free location provider found error message.
     */
    public void showNoFreeServiceFound()
    {
        Alert alert = new Alert("Error", NO_FREE_SERVICE_SERVICE_MESSAGE, null,
                AlertType.ERROR);
        alert.setTimeout(Alert.FOREVER);
        TouristMIDlet.getDisplay().setCurrent(alert, searchForm);
        infoItem.setText(NOT_FOUND_MESSAGE);
    }

    /**
     * Query the user whether the use of location provider may cost. The use of
     * this method is locked with synchronized keyword, so only one thread can
     * access this method at once.
     * 
     * @return a boolean indicating may user allow location provider cost.
     */
    public synchronized boolean confirmCostProvider()
    {
        Alert alert = new Alert("Confimnation", COST_QUERY_MESSAGE, null,
                AlertType.CONFIRMATION);
        alert.addCommand(yesCmd);
        alert.addCommand(noCmd);
        alert.setTimeout(Alert.FOREVER);

        // Set the monitoring object to be this instance.
        final ProviderQueryUI hinstance = this;

        // Add a CommandLister as anomynous inner class
        alert.setCommandListener(new CommandListener()
        {
            /*
             * Event indicating when a command button is pressed.
             * 
             * @see javax.microedition.lcdui.CommandListener#commandAction(javax.microedition.lcdui.Command,
             *      javax.microedition.lcdui.Displayable)
             */
            public void commandAction(Command command, Displayable d)
            {
                if (command == yesCmd)
                {
                    infoItem.setText(SEACHING_COST_PROVIDERS);
                    result = true;
                    synchronized (hinstance)
                    {
                        // Wake up the monitoring object
                        hinstance.notifyAll();
                    }
                }
                else if (command == noCmd)
                {
                    result = false;
                    infoItem.setText(NOT_FOUND_MESSAGE);
                    synchronized (hinstance)
                    {
                        // Wake up the monitoring object
                        hinstance.notifyAll();
                    }
                }

            }
        });

        TouristMIDlet.getDisplay().setCurrent(alert, searchForm);

        // Wait indefinitely for notification.
        try
        {
            wait();
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }

        TouristMIDlet.getDisplay().setCurrent(searchForm);

        return result;
    }

}

TouristUI

package com.nokia.example.location.tourist.ui;

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;

import javax.microedition.location.AddressInfo;
import javax.microedition.location.QualifiedCoordinates;

import com.nokia.example.location.tourist.TouristMIDlet;
import com.nokia.example.location.tourist.Utils;
import com.nokia.example.location.tourist.model.ConfigurationProvider;
import com.nokia.example.location.tourist.model.TouristData;

/**
 * Viewer class that renders current location updates.
 */
public class TouristUI extends Canvas implements CommandListener
{
    /** The current state of the location provider as a String */
    private String providerState = "Unknown";

    /** Proximity monitoring state. */
    private String proximityState = "Waiting";

    private AddressInfo info;

    private QualifiedCoordinates coord;

    private float speed;

    /** Command that shows compass canvas */
    private Command compassCmd = new Command("Compass", Command.OK, 1);

    /** Command that shows Landmark editor UI */
    private Command editorCmd = new Command("Editor", Command.STOP, 1);

    /** Rerefence to the Landmark editor UI */
    private LandmarkEditorUI editorUI = null;

    /** Rerefence to the Compass UI */
    private CompassUI compassUI = null;

    public TouristUI(TouristData data)
    {
        editorUI = new LandmarkEditorUI(this, data);
        compassUI = new CompassUI(this);

        checkSupportedFeatures();

        addCommand(editorCmd);
        setCommandListener(this);
    }

    /**
     * Enable supported Location API features on the UI and disable unsupported
     * features.
     */
    protected void checkSupportedFeatures()
    {
        if (ConfigurationProvider.getInstance().isOrientationSupported())
        {
            addCommand(compassCmd);
        }
        else
        {
            removeCommand(compassCmd);
        }
    }

    public void setProviderState(String state)
    {
        providerState = state;
    }

    public void setProximityState(String state)
    {
        proximityState = state;
    }

    public void setInfo(AddressInfo info, QualifiedCoordinates coord,
            float speed)
    {
        this.info = info;
        this.coord = coord;
        this.speed = speed;
    }

    /**
     * Renders the canvas.
     * 
     * @param g -
     *            the Graphics object to be used for rendering the Canvas
     */
    protected void paint(Graphics g)
    {
        Font f = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN,
                Font.SIZE_SMALL);
        g.setFont(f);

        // use font height as a line height
        int lineHeight = f.getHeight();
        // current line counter
        int line = 0;

        // clean the backround
        g.setColor(0xffffff);
        g.fillRect(0, 0, getWidth(), getHeight());

        g.setColor(0x0000ff);
        g.drawString("Provider state: " + providerState, 0, lineHeight
                * (line++), Graphics.LEFT | Graphics.TOP);
        g.drawString("Proximity monitoring: " + proximityState, 0, lineHeight
                * (line++), Graphics.LEFT | Graphics.TOP);

        if (coord != null)
        {
            double lat = coord.getLatitude();
            double lon = coord.getLongitude();

            g.drawString("Lat, Lon (" + Utils.formatDouble(lat, 3) + ", "
                    + Utils.formatDouble(lon, 3) + ")", 0, lineHeight
                    * (line++), Graphics.TOP | Graphics.LEFT);
            g.drawString("Speed: " + Utils.formatDouble(speed, 2) + " m/s", 0,
                    lineHeight * (line++), Graphics.TOP | Graphics.LEFT);
        }

        // Check if AddressInfo is available
        if (info != null)
        {
            String country = info.getField(AddressInfo.COUNTRY);
            String state = info.getField(AddressInfo.STATE);
            String city = info.getField(AddressInfo.CITY);
            String street = info.getField(AddressInfo.STREET);
            String buildingName = info.getField(AddressInfo.BUILDING_NAME);

            g.setColor(0x000000);

            if (country != null)
                g.drawString("Country: " + country, 0, lineHeight * (line++),
                        Graphics.TOP | Graphics.LEFT);
            if (state != null)
                g.drawString("State: " + state, 0, lineHeight * (line++),
                        Graphics.TOP | Graphics.LEFT);
            if (city != null)
                g.drawString("City: " + city, 0, lineHeight * (line++),
                        Graphics.TOP | Graphics.LEFT);
            if (street != null)
                g.drawString("Street: " + street, 0, lineHeight * (line++),
                        Graphics.TOP | Graphics.LEFT);
            if (buildingName != null)
                g.drawString("Building name: " + buildingName, 0, lineHeight
                        * (line++), Graphics.TOP | Graphics.LEFT);
        }
    }

    /**
     * Event indicating when a command button is pressed.
     * 
     * @see javax.microedition.lcdui.CommandListener#commandAction(javax.microedition.lcdui.Command,
     *      javax.microedition.lcdui.Displayable)
     */
    public void commandAction(Command command, Displayable d)
    {
        if (command == editorCmd)
        {
            if (coord != null)
            {
                editorUI.showEditor(coord, LandmarkEditorUI.MODE_ADDNEW);
            }
            else
            {
                MessageUI.showLocationDataNotAvailable();
            }
        }
        else if (command == compassCmd)
        {
            TouristMIDlet.getDisplay().setCurrent(compassUI);
        }
    }

}