Listing 4: The ImageViewer spotlet

/* @author Peter Meehan  Copyright(c) 2000 */

import com.sun.kjava.*;
import javax.microedition.io.*;
import java.io.*;

/* Spotlet that requests a camera image from a dedicated server,
 * retrieves that image from the server and displays it to the 
 * screen. */
public class ImageViewer extends Spotlet implements Runnable {

    /* Image server protocol */
    static final String 
    STATUS_CONNECTING = "Connecting to server...";

    ...

    static final String HOSTNAME = "localhost";
    static final int IMAGE_SERVER_PORT = 4443;

    // for max image width 2048 pixels
    static final int BUFFER_SIZE = 256; 
    
    /* Display constants */
    public static final int SCREEN_WIDTH = 160;
    public static final int SCREEN_HEIGHT = 160;
    Graphics g = Graphics.getGraphics();

    /* On request the spotlet returns control to the
     * cameraclient passed to it at creation time. */
    CameraClient master = null; 

    /* Image data */
    String imageDesc = null;
    String imageURL = null;
    int imageWidth = 0;
    int imageHeight = 0;
    byte imageWidthBytes = 0;
    byte imageBuffer[] = null;
    int imageX,imageY;
    Bitmap imageBitmap = null;

    /* Store the master spotlet 
     * @param master Spotlet to which control is returned on exit 
     */
    ImageViewer(CameraClient master) { this.master = master; }

    /* Start a thread to communicate with the server, retrieve
     * the image data and display it
     * @param desc descriptive label for the image
     * @param url URL for the image */
    public void retrieveImage(String desc,String url) {
        imageDesc = desc;
        imageURL = url;
        register(Spotlet.WANT_SYSTEM_KEYS);
        Thread imageThread = new Thread(this);
        imageThread.start();
    }

    /* Display a status message centered on the screen
     * @param msg the message */
    public void displayStatus(String msg) {
        int w = g.getWidth(msg);
        int h = g.getHeight(msg);
        g.clearScreen();
        g.drawString(msg,(SCREEN_WIDTH-w)/2,(SCREEN_HEIGHT-h)/2);
    }

    /* Display a camera image title centered over the image
     * @param title the title
     * @param imageX the x coordinate of the image
     * @param imageY the y coordinate of the image */
    public void 
    displayTitle(String title,int imageX, int imageY) {
        int w = g.getWidth(title);
        int h = g.getHeight(title);
        g.drawString(title.toUpperCase(),
                     (SCREEN_WIDTH-w)/2,(imageY-h)/2,
                     Graphics.INVERT);
    }
    
    /* Retrieve the image in a separate thread.
     *  - create socket and connect to server
     *  - send get image request
     *  - wait for result containing image size to be returned
     *  - read image data from socket 
     *  - close socket connection
     *  - draw image centered on screen */
    public void run() {

        StreamConnection connection = null;
        InputStream is = null;
        OutputStream os = null;
        int nRead = 0;
        byte buffer[] = new byte[BUFFER_SIZE];
        /* connect to server */
        displayStatus(STATUS_CONNECTING);
        try {
            connection = 
               (StreamConnection)
               Connector.open("socket://localhost:4443");
        } catch (IllegalArgumentException iae) {
            displayStatus(STATUS_COMMS_FAILURE);
            return;
        } catch (ConnectionNotFoundException cnfe) {
            displayStatus(STATUS_COMMS_FAILURE);
            return;
        } catch (IOException ioe) {
            displayStatus(STATUS_COMMS_FAILURE);
            return;
        }
        
        /* Request image */
        displayStatus(STATUS_REQUESTING_IMAGE);
        try {
            os = connection.openOutputStream();
            os.write((CMD_GET + imageURL).getBytes());
            os.flush();
        } catch (IOException ioe) {
            displayStatus(STATUS_COMMS_FAILURE);
            return;
        }
        
        /* Await response indicating image size 
         * zero image size indicates a problem */
        try {
            is = connection.openInputStream();
            displayStatus("Reading...");
            nRead = is.read(buffer,0,6);
            displayStatus("Read " + nRead + " bytes");
            if (nRead <6) 
                throw new IOException();
            else {
                // test for 6 byte reply
                // bytes 0-3 RSP_IMAGE ("Imag")
                // byte 4 image width
                // byte 5 image height
                if (buffer[0] == 'I' &&
                    buffer[1] == 'm' &&
                    buffer[2] == 'a' &&
                    buffer[3] == 'g') {
                    // extract image width & height
                    imageWidth = (buffer[4]&0x00ff);
                    imageHeight = (buffer[5]&0x00ff);
                    if (imageHeight <= 0 || imageWidth <= 0 ||
                        imageWidth >256 || imageHeight >256) {
                        displayStatus(STATUS_BAD_IMAGE + " " + 
                                      imageWidth +"x"
                                      + imageHeight);
                        os.write(CMD_NAK.getBytes());
                        connection.close();
                        return;
                    }
                    else {
                        // allocate space for image
                        imageWidthBytes = (byte)(imageWidth/8);
                        if (imageWidth % 8  0)
                            imageWidthBytes++;
                        imageBuffer = 
                           new byte[imageHeight*imageWidthBytes];
                    }
                }
            }
        } catch (IOException ioe) {
            displayStatus(STATUS_COMMS_FAILURE);
            return;
        }
            
        /* Get image data, one row at a time */
        try {
            os.write(CMD_ACK.getBytes());
            int imageBytesRxd = 0;
            while (imageBytesRxd <imageBuffer.length) {
                nRead = is.read(imageBuffer, imageBytesRxd,
                                imageWidthBytes);
                imageBytesRxd += nRead;
                displayStatus(STATUS_XFER_IMAGE + 
                   (imageBytesRxd*100)/imageBuffer.length + "%");
            }
            connection.close(); // done
        } catch (IOException ioe) {
            displayStatus(STATUS_COMMS_FAILURE);
            return;
        }

        /* Display image */
        displayStatus("Creating bitmap");
        imageBitmap = new Bitmap(imageWidthBytes,imageBuffer);
        imageX = (SCREEN_WIDTH-imageWidth)/2;
        imageY = (SCREEN_HEIGHT-imageHeight)/2;
        displayStatus("Drawing bitmap");
        displayTitle(imageDesc,imageX,imageY);
        g.drawBitmap(imageX,imageY,imageBitmap);
    }

    /* Handle key and character entry events. On HARD_KEY1 presses 
     * return control to master if non-null else exit.
     * @param keycode the key that was pressed or character 
     * that was entered */
    public void keyDown(int keycode) {
        if (keycode == Spotlet.KEY_HARD1) {
            if (master != null)
                master.handleViewerDone();
            else
                System.exit(0);
        }
    }
}