bluetooth传图片,WTK 2.5 EG

        之前转了篇蓝牙实战,其中代码含简易蓝牙通信代码,不过功能仅限于传输字符串等简单格式。这两天翻了翻WTK 的EG,其中的代码含传送图片功能,比前面提高了一个档次,但仍不能传送本地任意文件 ,sun的tutorial建议学习JSR75(好像是,也可能是72,呵呵,有兴趣的自己查一下),此JSR提供对本地文件系统的访问。

        图片传送主要是把图片在server端转化成流,在client则根据相应的转化规则逆转化即可。同理,可以传送音频、视频等。

代码含5文件,6个图片,路径可根据code自己创建:

DemoMIDlet.java:

/*
 *
 * Copyright 漏 2007 Sun Microsystems, Inc. All rights reserved.
 * Use is subject to license terms.
 */
package src;

import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.List;
import javax.microedition.midlet.MIDlet;


/**
 * Contains the Bluetooth API demo, that allows to download
 * the specific images from the other devices.
 *
 * @version ,
 */
public final class DemoMIDlet extends MIDlet implements CommandListener
{
    /** The messages are shown in this demo this amount of time. */
    static final int ALERT_TIMEOUT = 2000;

    /** A list of menu items */
    private static final String[] elements = { "Server", "Client" };

    /** Soft button for exiting the demo. */
    private final Command EXIT_CMD = new Command("Exit", Command.EXIT, 2);

    /** Soft button for launching a client or sever. */
    private final Command OK_CMD = new Command("Ok", Command.SCREEN, 1);

    /** A menu list instance */
    private final List menu = new List("Bluetooth Demo", List.IMPLICIT, elements, null);

    /** A GUI part of server that publishes images. */
    private GUIImageServer imageServer;

    /** A GUI part of client that receives image from client */
    private GUIImageClient imageClient;

    /** value is true after creating the server/client */
    private boolean isInit = false;

    /**
     * Constructs main screen of the MIDlet.
     */
    public DemoMIDlet()
    {
        menu.addCommand(EXIT_CMD);
        menu.addCommand(OK_CMD);
        menu.setCommandListener(this);
    }

    /**
     * Creates the demo view and action buttons.
     */
    public void startApp()
    {
        if (!isInit)
        {
            show();
        }
    }

    /**
     * Destroys the application.
     */
    protected void destroyApp(boolean unconditional)
    {
        if (imageServer != null)
        {
            imageServer.destroy();
        }

        if (imageClient != null)
        {
            imageClient.destroy();
        }
    }

    /**
     * Does nothing. Redefinition is required by MIDlet class.
     */
    protected void pauseApp()
    {
    }

    /**
     * Responds to commands issued on "client or server" form.
     *
     * @param c command object source of action
     * @param d screen object containing the item action was performed on
     */
    public void commandAction(Command c, Displayable d)
    {
        if (c == EXIT_CMD)
        {
            destroyApp(true);
            notifyDestroyed();

            return;
        }

        switch (menu.getSelectedIndex())
        {
        case 0:
            imageServer = new GUIImageServer(this);

            break;

        case 1:
            imageClient = new GUIImageClient(this);

            break;

        default:
            System.err.println("Unexpected choice...");

            break;
        }

        isInit = true;
    }

    /** Shows main menu of MIDlet on the screen. */
    void show()
    {
        Display.getDisplay(this).setCurrent(menu);
    }

    /**
     * Returns the displayable object of this screen -
     * it is required for Alert construction for the error
     * cases.
     */
    Displayable getDisplayable()
    {
        return menu;
    }
} // end of class 'DemoMIDlet' definition

 

 

BTImageServer.java:

/*
 *
 * Copyright 漏 2007 Sun Microsystems, Inc. All rights reserved.
 * Use is subject to license terms.
 */
package src;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import java.util.Hashtable;
import java.util.Vector;

// jsr082 API
import javax.bluetooth.DataElement;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.ServiceRecord;
import javax.bluetooth.ServiceRegistrationException;
import javax.bluetooth.UUID;

// midp/cldc API
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;


/**
 * Established the BT service, accepts connections
 * and send the requested image silently.
 *
 * @version ,
 */
final class BTImageServer implements Runnable
{
    /** Describes this server */
    private static final UUID PICTURES_SERVER_UUID =
        new UUID("F0E0D0C0B0A000908070605040302010", false);

    /** The attribute id of the record item with images names. */
    private static final int IMAGES_NAMES_ATTRIBUTE_ID = 0x4321;

    /** Keeps the local device reference. */
    private LocalDevice localDevice;

    /** Accepts new connections. */
    private StreamConnectionNotifier notifier;

    /** Keeps the information about this server. */
    private ServiceRecord record;

    /** Keeps the parent reference to process specific actions. */
    private GUIImageServer parent;

    /** Becomes 'true' when this component is finalized. */
    private boolean isClosed;

    /** Creates notifier and accepts clients to be processed. */
    private Thread accepterThread;

    /** Process the particular client from queue. */
    private ClientProcessor processor;

    /** Optimization: keeps the table of data elements to be published. */
    private final Hashtable dataElements = new Hashtable();

    /**
     * Constructs the bluetooth server, but it is initialized
     * in the different thread to "avoid dead lock".
     */
    BTImageServer(GUIImageServer parent)
    {
        this.parent = parent;

        // we have to initialize a system in different thread...
        accepterThread = new Thread(this);
        accepterThread.start();
    }

    /**
     * Accepts a new client and send him/her a requested image.
     */
    public void run()
    {
        boolean isBTReady = false;

        try
        {
            // create/get a local device
            localDevice = LocalDevice.getLocalDevice();

            // set we are discoverable
            if (!localDevice.setDiscoverable(DiscoveryAgent.GIAC))
            {
                // Some implementations always return false, even if
                // setDiscoverable successful
                // throw new IOException("Can't set discoverable mode...");
            }

            // prepare a URL to create a notifier
            StringBuffer url = new StringBuffer("btspp://");

            // indicate this is a server
            url.append("localhost").append(':');

            // add the UUID to identify this service
            url.append(PICTURES_SERVER_UUID.toString());

            // add the name for our service
            url.append(";name=Picture Server");

            // request all of the client not to be authorized
            // some devices fail on authorize=true
            url.append(";authorize=false");

            // create notifier now
            notifier = (StreamConnectionNotifier)Connector.open(url.toString());

            // and remember the service record for the later updates
            record = localDevice.getRecord(notifier);

            // create a special attribute with images names
            DataElement base = new DataElement(DataElement.DATSEQ);
            record.setAttributeValue(IMAGES_NAMES_ATTRIBUTE_ID, base);

            // remember we've reached this point.
            isBTReady = true;
        }
        catch (Exception e)
        {
            System.err.println("Can't initialize bluetooth: " + e);
        }

        parent.completeInitialization(isBTReady);

        // nothing to do if no bluetooth available
        if (!isBTReady)
        {
            return;
        }

        // ok, start processor now
        processor = new ClientProcessor();

        // ok, start accepting connections then
        while (!isClosed)
        {
            StreamConnection conn = null;

            try
            {
                conn = notifier.acceptAndOpen();
            }
            catch (IOException e)
            {
                // wrong client or interrupted - continue anyway
                continue;
            }

            processor.addConnection(conn);
        }
    }

    /**
     * Updates the service record with the information
     * about the published images availability.
     * <p>
     * This method is invoked after the caller has checked
     * already that the real action should be done.
     *
     * @return true if record was updated successfully, false otherwise.
     */
    boolean changeImageInfo(String name, boolean isPublished) {
        // ok, get the record from service
        DataElement base = record.getAttributeValue(IMAGES_NAMES_ATTRIBUTE_ID);

        // check the corresponding DataElement object is created already
        DataElement de = (DataElement)dataElements.get(name);

        // if no, then create a new DataElement that describes this image
        if (de == null) {
            de = new DataElement(DataElement.STRING, name);
            dataElements.put(name, de);
        }

        // we know this data element has DATSEQ type
        if (isPublished) {
            base.addElement(de);
        } else {
            if (!base.removeElement(de)) {
                System.err.println("Error: item was not removed for: " + name);

                return false;
            }
        }

        record.setAttributeValue(IMAGES_NAMES_ATTRIBUTE_ID, base);

        try {
            localDevice.updateRecord(record);
        } catch (ServiceRegistrationException e) {
            System.err.println("Can't update record now for: " + name);

            return false;
        }

        return true;
    }

    /**
     * Destroy a work with bluetooth - exits the accepting
     * thread and close notifier.
     */
    void destroy()
    {
        isClosed = true;

        // finalize notifier work
        if (notifier != null)
        {
            try
            {
                notifier.close();
            }
            catch (IOException e)
            {
            } // ignore
        }

        // wait for acceptor thread is done
        try
        {
            accepterThread.join();
        }
        catch (InterruptedException e)
        {
        } // ignore

        // finalize processor
        if (processor != null)
        {
            processor.destroy(true);
        }
        processor = null;
    }

    /**
     * Reads the image name from the specified connection
     * and sends this image through this connection, then
     * close it after all.
     */
    private void processConnection(StreamConnection conn)
    {
        // read the image name first
        String imgName = readImageName(conn);

        // check this image is published and get the image file name
        imgName = parent.getImageFileName(imgName);

        // load image data into buffer to be send
        byte[] imgData = getImageData(imgName);

        // send image data now
        sendImageData(imgData, conn);

        // close connection and good-bye
        try
        {
            conn.close();
        }
        catch (IOException e)
        {
        } // ignore
    }

    /** Send image data. */
    private void sendImageData(byte[] imgData, StreamConnection conn)
    {
        if (imgData == null)
        {
            return;
        }

        OutputStream out = null;

        try
        {
            out = conn.openOutputStream();
            out.write(imgData.length >> 8);
            out.write(imgData.length & 0xff);
            out.write(imgData);
            out.flush();
        }
        catch (IOException e)
        {
            System.err.println("Can't send image data: " + e);
        }

        // close output stream anyway
        if (out != null)
        {
            try
            {
                out.close();
            }
            catch (IOException e)
            {
            } // ignore
        }
    }

    /** Reads image name from specified connection. */
    private String readImageName(StreamConnection conn)
    {
        String imgName = null;
        InputStream in = null;

        try
        {
            in = conn.openInputStream();

            int length = in.read(); // 'name' length is 1 byte

            if (length <= 0)
            {
                throw new IOException("Can't read name length");
            }

            byte[] nameData = new byte[length];
            length = 0;

            while (length != nameData.length)
            {
                int n = in.read(nameData, length, nameData.length - length);

                if (n == -1)
                {
                    throw new IOException("Can't read name data");
                }

                length += n;
            }

            imgName = new String(nameData);
        }
        catch (IOException e)
        {
            System.err.println(e);
        }

        // close input stream anyway
        if (in != null)
        {
            try
            {
                in.close();
            }
            catch (IOException e)
            {
            } // ignore
        }

        return imgName;
    }

    /** Reads images data from MIDlet archive to array. */
    private byte[] getImageData(String imgName)
    {
        if (imgName == null)
        {
            return null;
        }

        InputStream in = getClass().getResourceAsStream(imgName);

        // read image data and create a byte array
        byte[] buff = new byte[1024];
        ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);

        try
        {
            while (true)
            {
                int length = in.read(buff);

                if (length == -1)
                {
                    break;
                }

                baos.write(buff, 0, length);
            }
        }
        catch (IOException e)
        {
            System.err.println("Can't get image data: imgName=" + imgName + " :" + e);
            return null;
        }

        return baos.toByteArray();
    }

    /**
     * Organizes the queue of clients to be processed,
     * processes the clients one by one until destroyed.
     */
    private class ClientProcessor implements Runnable
    {
        private Thread processorThread;
        private Vector queue = new Vector();
        private boolean isOk = true;

        ClientProcessor()
        {
            processorThread = new Thread(this);
            processorThread.start();
        }

        public void run()
        {
            while (!isClosed)
            {
                // wait for new task to be processed
                synchronized (this)
                {
                    if (queue.size() == 0)
                    {
                        try
                        {
                            wait();
                        }
                        catch (InterruptedException e)
                        {
                            System.err.println("Unexpected exception: " + e);
                            destroy(false);

                            return;
                        }
                    }
                }

                // send the image to specified connection
                StreamConnection conn;

                synchronized (this)
                {
                    // may be awaked by "destroy" method.
                    if (isClosed)
                    {
                        return;
                    }

                    conn = (StreamConnection)queue.firstElement();
                    queue.removeElementAt(0);
                    processConnection(conn);
                }
            }
        }

        /** Adds the connection to queue and notifies the thread. */
        void addConnection(StreamConnection conn)
        {
            synchronized (this)
            {
                queue.addElement(conn);
                notify();
            }
        }

        /** Closes the connections and . */
        void destroy(boolean needJoin) {
            StreamConnection conn;

            synchronized (this) {
                notify();

                while (queue.size() != 0) {
                    conn = (StreamConnection)queue.firstElement();
                    queue.removeElementAt(0);

                    try {
                        conn.close();
                    } catch (IOException e) {
                    } // ignore
                }
            }

            // wait until dispatching thread is done
            try {
                processorThread.join();
            } catch (InterruptedException e) {
            } // ignore
        }
    }
} // end of class 'BTImageServer' definition

 

 

GUIImageServer/java:

/*
 *
 * Copyright 漏 2007 Sun Microsystems, Inc. All rights reserved.
 * Use is subject to license terms.
 */
package src;

import java.io.IOException;

import java.util.Vector;

import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.List;
import javax.microedition.lcdui.Ticker;


/**
 * Allows to customize the images list to be published,
 * creates the corresponding service record to describe this list
 * and send the images to clients by request.
 *
 * @version ,
 */
final class GUIImageServer implements CommandListener
{
    /** Keeps the help message of this demo. */
    private final String helpText =
        "The server is started by default./n/n" +
        "No images are published initially. Change this by corresponding" +
        " commands - the changes have an effect immediately./n/n" +
        "If image is removed from the published list, it can't " + "be downloaded.";

    /** This command goes to demo main screen. */
    private final Command backCommand = new Command("Back", Command.BACK, 2);

    /** Adds the selected image to the published list. */
    private final Command addCommand = new Command("Publish image", Command.SCREEN, 1);

    /** Removes the selected image from the published list. */
    private final Command removeCommand = new Command("Remove image", Command.SCREEN, 1);

    /** Shows the help message. */
    private final Command helpCommand = new Command("Help", Command.HELP, 1);

    /** The list control to configure images. */
    private final List imagesList = new List("Configure Server", List.IMPLICIT);

    /** The help screen for the server. */
    private final Alert helpScreen = new Alert("Help");

    /** Keeps the parent MIDlet reference to process specific actions. */
    private DemoMIDlet parent;

    /** The list of images file names. */
    private Vector imagesNames;

    /** These images are used to indicate the picture is published. */
    private Image onImage;

    /** These images are used to indicate the picture is published. */
    private Image offImage;

    /** Keeps an information about what images are published. */
    private boolean[] published;

    /** This object handles the real transmission. */
    private BTImageServer bt_server;

    /** Constructs images server GUI. */
    GUIImageServer(DemoMIDlet parent)
    {
        this.parent = parent;
        bt_server = new BTImageServer(this);
        setupIdicatorImage();
        setupImageList();
        published = new boolean[imagesList.size()];

        // prepare main screen
        imagesList.addCommand(backCommand);
        imagesList.addCommand(addCommand);
        imagesList.addCommand(removeCommand);
        imagesList.addCommand(helpCommand);
        imagesList.setCommandListener(this);

        // prepare help screen
        helpScreen.addCommand(backCommand);
        helpScreen.setTimeout(Alert.FOREVER);
        helpScreen.setString(helpText);
        helpScreen.setCommandListener(this);
    }

    /**
     * Process the command event.
     *
     * @param c - the issued command.
     * @param d - the screen object the command was issued for.
     */
    public void commandAction(Command c, Displayable d)
    {
        if ((c == backCommand) && (d == imagesList))
        {
            destroy();
            parent.show();

            return;
        }

        if ((c == backCommand) && (d == helpScreen))
        {
            Display.getDisplay(parent).setCurrent(imagesList);

            return;
        }

        if (c == helpCommand)
        {
            Display.getDisplay(parent).setCurrent(helpScreen);

            return;
        }

        /*
         * Changing the state of base of published images
         */
        int index = imagesList.getSelectedIndex();

        // nothing to do
        if ((c == addCommand) == published[index]) {
            return;
        }

        // update information and view
        published[index] = c == addCommand;

        Image stateImg = (c == addCommand) ? onImage : offImage;
        imagesList.set(index, imagesList.getString(index), stateImg);

        // update bluetooth service information
        if (!bt_server.changeImageInfo(imagesList.getString(index), published[index])) {
            // either a bad record or SDDB is busy
            Alert al = new Alert("Error", "Can't update base", null, AlertType.ERROR);
            al.setTimeout(DemoMIDlet.ALERT_TIMEOUT);
            Display.getDisplay(parent).setCurrent(al, imagesList);

            // restore internal information
            published[index] = !published[index];
            stateImg = published[index] ? onImage : offImage;
            imagesList.set(index, imagesList.getString(index), stateImg);
        }
    }

    /**
     * We have to provide this method due to "do not do network
     * operation in command listener method" restriction, which
     * is caused by crooked midp design.
     *
     * This method is called by BTImageServer after it is done
     * with bluetooth initialization and next screen is ready
     * to appear.
     */
    void completeInitialization(boolean isBTReady)
    {
        // bluetooth was initialized successfully.
        if (isBTReady)
        {
            Ticker t = new Ticker("Choose images you want to publish...");
            imagesList.setTicker(t);
            Display.getDisplay(parent).setCurrent(imagesList);

            return;
        }

        // something wrong
        Alert al = new Alert("Error", "Can't initialize bluetooth", null, AlertType.ERROR);
        al.setTimeout(DemoMIDlet.ALERT_TIMEOUT);
        Display.getDisplay(parent).setCurrent(al, parent.getDisplayable());
    }

    /** Destroys this component. */
    void destroy() {
        // finalize the image server work
        bt_server.destroy();
    }

    /** Gets the image file name from its title (label). */
    String getImageFileName(String imgName)
    {
        if (imgName == null)
        {
            return null;
        }

        // no interface in List to get the index - should find
        int index = -1;

        for (int i = 0; i < imagesList.size(); i++)
        {
            if (imagesList.getString(i).equals(imgName))
            {
                index = i;
                break;
            }
        }

        // not found or not published
        if ((index == -1) || !published[index])
        {
            return null;
        }

        return (String)imagesNames.elementAt(index);
    }

    /**
     * Creates the image to indicate the base state.
     */
    private void setupIdicatorImage()
    {
        // create "on" image
        try
        {
            onImage = Image.createImage("/images/st-on.png");
        }
        catch (IOException e)
        {
            // provide off-screen image then
            onImage = createIndicatorImage(12, 12, 0, 255, 0);
        }

        // create "off" image
        try
        {
            offImage = Image.createImage("/images/st-off.png");
        }
        catch (IOException e)
        {
            // provide off-screen image then
            offImage = createIndicatorImage(12, 12, 255, 0, 0);
        }
    }

    /**
     * Gets the description of images from manifest and
     * prepares the list to control the configuration.
     * <p>
     * The attributes are named "ImageTitle-n" and "ImageImage-n".
     * The value "n" must start at "1" and be incremented by 1.
     */
    private void setupImageList()
    {
        imagesNames = new Vector();
        imagesList.setCommandListener(this);

        for (int n = 1; n < 100; n++)
        {
            String name = parent.getAppProperty("ImageName-" + n);

            // no more images available
            if ((name == null) || (name.length() == 0))
            {
                break;
            }

            String label = parent.getAppProperty("ImageTitle-" + n);

            // no label available - use picture name instead
            if ((label == null) || (label.length() == 0))
            {
                label = name;
            }

            imagesNames.addElement(name);
            imagesList.append(label, offImage);
        }
    }

    /**
     * Creates the off-screen image with specified size an color.
     */
    private Image createIndicatorImage(int w, int h, int r, int g, int b) {
        Image res = Image.createImage(w, h);
        Graphics gc = res.getGraphics();
        gc.setColor(r, g, b);
        gc.fillRect(0, 0, w, h);

        return res;
    }
} // end of class 'GUIImageServer' definition

 

GUIImageClient.java:

/*
 *
 * Copyright 漏 2007 Sun Microsystems, Inc. All rights reserved.
 * Use is subject to license terms.
 */
package src;


// midp/cldc classes
import java.io.IOException;

import java.util.Enumeration;
import java.util.Hashtable;

// midp GUI classes
import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.Gauge;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.ImageItem;
import javax.microedition.lcdui.List;
import javax.microedition.lcdui.StringItem;


/**
 * Provides a GUI to present the download options
 * to used, gives a chance to make a choice,
 * finally shows the downloaded image.
 *
 * @version ,
 */
final class GUIImageClient implements CommandListener {
    /** This command goes to demo main screen. */
    private final Command SCR_MAIN_BACK_CMD = new Command("Back", Command.BACK, 2);

    /** Starts the proper services search. */
    private final Command SCR_MAIN_SEARCH_CMD = new Command("Find", Command.OK, 1);

    /** Cancels the device/services discovering. */
    private final Command SCR_SEARCH_CANCEL_CMD = new Command("Cancel", Command.BACK, 2);

    /** This command goes to client main screen. */
    private final Command SCR_IMAGES_BACK_CMD = new Command("Back", Command.BACK, 2);

    /** Start the chosen image download. */
    private final Command SCR_IMAGES_LOAD_CMD = new Command("Load", Command.OK, 1);

    /** Cancels the image download. */
    private final Command SCR_LOAD_CANCEL_CMD = new Command("Cancel", Command.BACK, 2);

    /** This command goes from image screen to images list one. */
    private final Command SCR_SHOW_BACK_CMD = new Command("Back", Command.BACK, 2);

    /** The main screen of the client part. */
    private final Form mainScreen = new Form("Image Viewer");

    /** The screen with found images names. */
    private final List listScreen = new List("Image Viewer", List.IMPLICIT);

    /** The screen with download image. */
    private final Form imageScreen = new Form("Image Viewer");

    /** Keeps the parent MIDlet reference to process specific actions. */
    private DemoMIDlet parent;

    /** This object handles the real transmission. */
    private BTImageClient bt_client;

    /** Constructs client GUI. */
    GUIImageClient(DemoMIDlet parent) {
        this.parent = parent;
        bt_client = new BTImageClient(this);
        mainScreen.addCommand(SCR_MAIN_BACK_CMD);
        mainScreen.addCommand(SCR_MAIN_SEARCH_CMD);
        mainScreen.setCommandListener(this);
        listScreen.addCommand(SCR_IMAGES_BACK_CMD);
        listScreen.addCommand(SCR_IMAGES_LOAD_CMD);
        listScreen.setCommandListener(this);
        imageScreen.addCommand(SCR_SHOW_BACK_CMD);
        imageScreen.setCommandListener(this);
    }

    /**
     * Process the command events.
     *
     * @param c - the issued command.
     * @param d - the screen object the command was issued for.
     */
    public void commandAction(Command c, Displayable d) {
        // back to demo main screen
        if (c == SCR_MAIN_BACK_CMD) {
            destroy();
            parent.show();

            return;
        }

        // starts images (device/services) search
        if (c == SCR_MAIN_SEARCH_CMD) {
            Form f = new Form("Searching...");
            f.addCommand(SCR_SEARCH_CANCEL_CMD);
            f.setCommandListener(this);
            f.append(new Gauge("Searching images...", false, Gauge.INDEFINITE,
                    Gauge.CONTINUOUS_RUNNING));
            Display.getDisplay(parent).setCurrent(f);
            bt_client.requestSearch();

            return;
        }

        // cancels device/services search
        if (c == SCR_SEARCH_CANCEL_CMD) {
            bt_client.cancelSearch();
            Display.getDisplay(parent).setCurrent(mainScreen);

            return;
        }

        // back to client main screen
        if (c == SCR_IMAGES_BACK_CMD) {
            bt_client.requestLoad(null);
            Display.getDisplay(parent).setCurrent(mainScreen);

            return;
        }

        // starts image download
        if (c == SCR_IMAGES_LOAD_CMD) {
            Form f = new Form("Loading...");
            f.addCommand(SCR_LOAD_CANCEL_CMD);
            f.setCommandListener(this);
            f.append(new Gauge("Loading image...", false, Gauge.INDEFINITE, Gauge.CONTINUOUS_RUNNING));
            Display.getDisplay(parent).setCurrent(f);

            List l = (List)d;
            bt_client.requestLoad(l.getString(l.getSelectedIndex()));

            return;
        }

        // cancels image load
        if (c == SCR_LOAD_CANCEL_CMD) {
            bt_client.cancelLoad();
            Display.getDisplay(parent).setCurrent(listScreen);

            return;
        }

        // back to client main screen
        if (c == SCR_SHOW_BACK_CMD) {
            Display.getDisplay(parent).setCurrent(listScreen);

            return;
        }
    }

    /**
     * We have to provide this method due to "do not do network
     * operation in command listener method" restriction, which
     * is caused by crooked midp design.
     *
     * This method is called by BTImageClient after it is done
     * with bluetooth initialization and next screen is ready
     * to appear.
     */
    void completeInitialization(boolean isBTReady) {
        // bluetooth was initialized successfully.
        if (isBTReady) {
            StringItem si = new StringItem("Ready for images search!", null);
            si.setLayout(StringItem.LAYOUT_CENTER | StringItem.LAYOUT_VCENTER);
            mainScreen.append(si);
            Display.getDisplay(parent).setCurrent(mainScreen);

            return;
        }

        // something wrong
        Alert al = new Alert("Error", "Can't initialize bluetooth", null, AlertType.ERROR);
        al.setTimeout(DemoMIDlet.ALERT_TIMEOUT);
        Display.getDisplay(parent).setCurrent(al, parent.getDisplayable());
    }

    /** Destroys this component. */
    void destroy() {
        // finalize the image client work
        bt_client.destroy();
    }

    /**
     * Informs the error during the images search.
     */
    void informSearchError(String resMsg) {
        Alert al = new Alert("Error", resMsg, null, AlertType.ERROR);
        al.setTimeout(DemoMIDlet.ALERT_TIMEOUT);
        Display.getDisplay(parent).setCurrent(al, mainScreen);
    }

    /**
     * Informs the error during the selected image load.
     */
    void informLoadError(String resMsg) {
        Alert al = new Alert("Error", resMsg, null, AlertType.ERROR);
        al.setTimeout(DemoMIDlet.ALERT_TIMEOUT);
        Display.getDisplay(parent).setCurrent(al, listScreen);
    }

    /**
     * Shows the downloaded image.
     */
    void showImage(Image img, String imgName) {
        imageScreen.deleteAll();
        imageScreen.append(new ImageItem(imgName, img,
                ImageItem.LAYOUT_CENTER | ImageItem.LAYOUT_VCENTER, "Downloaded image: " + imgName));
        Display.getDisplay(parent).setCurrent(imageScreen);
    }

    /**
     * Shows the available images names.
     *
     * @returns false if no images names were found actually
     */
    boolean showImagesNames(Hashtable base) {
        Enumeration keys = base.keys();

        // no images actually
        if (!keys.hasMoreElements()) {
            informSearchError("No images names in found services");

            return false;
        }

        // prepare the list to be shown
        while (listScreen.size() != 0) {
            listScreen.delete(0);
        }

        while (keys.hasMoreElements()) {
            listScreen.append((String)keys.nextElement(), null);
        }

        Display.getDisplay(parent).setCurrent(listScreen);

        return true;
    }
} // end of class 'GUIImageClient' definition

 

BTImageClient.java:

/*
 *
 * Copyright 漏 2007 Sun Microsystems, Inc. All rights reserved.
 * Use is subject to license terms.
 */
package src;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

// jsr082 API
import javax.bluetooth.BluetoothStateException;
import javax.bluetooth.DataElement;
import javax.bluetooth.DeviceClass;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.DiscoveryListener;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.RemoteDevice;
import javax.bluetooth.ServiceRecord;
import javax.bluetooth.UUID;

// midp/cldc API
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import javax.microedition.lcdui.Image;


/**
 * Initialize BT device, search for BT services,
 * presents them to user and picks his/her choice,
 * finally download the choosen image and present
 * it to user.
 *
 * @version ,
 */
final class BTImageClient implements Runnable, DiscoveryListener {
    /** Describes this server */
    private static final UUID PICTURES_SERVER_UUID =
        new UUID("F0E0D0C0B0A000908070605040302010", false);

    /** The attribute id of the record item with images names. */
    private static final int IMAGES_NAMES_ATTRIBUTE_ID = 0x4321;

    /** Shows the engine is ready to work. */
    private static final int READY = 0;

    /** Shows the engine is searching bluetooth devices. */
    private static final int DEVICE_SEARCH = 1;

    /** Shows the engine is searching bluetooth services. */
    private static final int SERVICE_SEARCH = 2;

    /** Keeps the current state of engine. */
    private int state = READY;

    /** Keeps the discovery agent reference. */
    private DiscoveryAgent discoveryAgent;

    /** Keeps the parent reference to process specific actions. */
    private GUIImageClient parent;

    /** Becomes 'true' when this component is finalized. */
    private boolean isClosed;

    /** Process the search/download requests. */
    private Thread processorThread;

    /** Collects the remote devices found during a search. */
    private Vector /* RemoteDevice */ devices = new Vector();

    /** Collects the services found during a search. */
    private Vector /* ServiceRecord */ records = new Vector();

    /** Keeps the device discovery return code. */
    private int discType;

    /** Keeps the services search IDs (just to be able to cancel them). */
    private int[] searchIDs;

    /** Keeps the image name to be load. */
    private String imageNameToLoad;

    /** Keeps the table of {name, Service} to process the user choice. */
    private Hashtable base = new Hashtable();

    /** Informs the thread the download should be canceled. */
    private boolean isDownloadCanceled;

    /** Optimization: keeps service search pattern. */
    private UUID[] uuidSet;

    /** Optimization: keeps attributes list to be retrieved. */
    private int[] attrSet;

    /**
     * Constructs the bluetooth server, but it is initialized
     * in the different thread to "avoid dead lock".
     */
    BTImageClient(GUIImageClient parent) {
        this.parent = parent;

        // we have to initialize a system in different thread...
        processorThread = new Thread(this);
        processorThread.start();
    }

    /**
     * Process the search/download requests.
     */
    public void run() {
        // initialize bluetooth first
        boolean isBTReady = false;

        try {
            // create/get a local device and discovery agent
            LocalDevice localDevice = LocalDevice.getLocalDevice();
            discoveryAgent = localDevice.getDiscoveryAgent();

            // remember we've reached this point.
            isBTReady = true;
        } catch (Exception e) {
            System.err.println("Can't initialize bluetooth: " + e);
        }

        parent.completeInitialization(isBTReady);

        // nothing to do if no bluetooth available
        if (!isBTReady) {
            return;
        }

        // initialize some optimization variables
        uuidSet = new UUID[2];

        // ok, we are interesting in btspp services only
        uuidSet[0] = new UUID(0x1101);

        // and only known ones, that allows pictures
        uuidSet[1] = PICTURES_SERVER_UUID;

        // we need an only service attribute actually
        attrSet = new int[1];

        // it's "images names" one
        attrSet[0] = IMAGES_NAMES_ATTRIBUTE_ID;

        // start processing the images search/download
        processImagesSearchDownload();
    }

    /**
     * Invoked by system when a new remote device is found -
     * remember the found device.
     */
    public void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) {
        // same device may found several times during single search
        if (devices.indexOf(btDevice) == -1) {
            devices.addElement(btDevice);
        }
    }

    /**
     * Invoked by system when device discovery is done.
     * <p>
     * Use a trick here - just remember the discType
     * and process its evaluation in another thread.
     */
    public void inquiryCompleted(int discType) {
        this.discType = discType;

        synchronized (this) {
            notify();
        }
    }

    public void servicesDiscovered(int transID, ServiceRecord[] servRecord) {
        for (int i = 0; i < servRecord.length; i++) {
            records.addElement(servRecord[i]);
        }
    }

    public void serviceSearchCompleted(int transID, int respCode) {
        // first, find the service search transaction index
        int index = -1;

        for (int i = 0; i < searchIDs.length; i++) {
            if (searchIDs[i] == transID) {
                index = i;

                break;
            }
        }

        // error - unexpected transaction index
        if (index == -1) {
            System.err.println("Unexpected transaction index: " + transID);

            // FIXME: process the error case
        } else {
            searchIDs[index] = -1;
        }

        /*
         * Actually, we do not care about the response code -
         * if device is not reachable or no records, etc.
         */

        // make sure it was the last transaction
        for (int i = 0; i < searchIDs.length; i++) {
            if (searchIDs[i] != -1) {
                return;
            }
        }

        // ok, all of the transactions are completed
        synchronized (this) {
            notify();
        }
    }

    /** Sets the request to search the devices/services. */
    void requestSearch() {
        synchronized (this) {
            notify();
        }
    }

    /** Cancel's the devices/services search. */
    void cancelSearch() {
        synchronized (this) {
            if (state == DEVICE_SEARCH) {
                discoveryAgent.cancelInquiry(this);
            } else if (state == SERVICE_SEARCH) {
                for (int i = 0; i < searchIDs.length; i++) {
                    discoveryAgent.cancelServiceSearch(searchIDs[i]);
                }
            }
        }
    }

    /** Sets the request to load the specified image. */
    void requestLoad(String name) {
        synchronized (this) {
            imageNameToLoad = name;
            notify();
        }
    }

    /** Cancel's the image download. */
    void cancelLoad() {
        /*
         * The image download process is done by
         * this class's thread (not by a system one),
         * so no need to wake up the current thread -
         * it's running already.
         */
        isDownloadCanceled = true;
    }

    /**
     * Destroy a work with bluetooth - exits the accepting
     * thread and close notifier.
     */
    void destroy() {
        synchronized (this) {
            isClosed = true;
            isDownloadCanceled = true;
            notify();

            // FIXME: implement me
        }

        // wait for acceptor thread is done
        try {
            processorThread.join();
        } catch (InterruptedException e) {
        } // ignore
    }

    /**
     * Processes images search/download until component is closed
     * or system error has happen.
     */
    private synchronized void processImagesSearchDownload() {
        while (!isClosed) {
            // wait for new search request from user
            state = READY;

            try {
                wait();
            } catch (InterruptedException e) {
                System.err.println("Unexpected interruption: " + e);

                return;
            }

            // check the component is destroyed
            if (isClosed) {
                return;
            }

            // search for devices
            if (!searchDevices()) {
                return;
            } else if (devices.size() == 0) {
                continue;
            }

            // search for services now
            if (!searchServices()) {
                return;
            } else if (records.size() == 0) {
                continue;
            }

            // ok, something was found - present the result to user now
            if (!presentUserSearchResults()) {
                // services are found, but no names there
                continue;
            }

            // the several download requests may be processed
            while (true) {
                // this download is not canceled, right?
                isDownloadCanceled = false;

                // ok, wait for download or need to wait for next search
                try {
                    wait();
                } catch (InterruptedException e) {
                    System.err.println("Unexpected interruption: " + e);

                    return;
                }

                // check the component is destroyed
                if (isClosed) {
                    return;
                }

                // this means "go to the beginning"
                if (imageNameToLoad == null) {
                    break;
                }

                // load selected image data
                Image img = loadImage();

                // FIXME: this never happen - monitor is taken...
                if (isClosed) {
                    return;
                }

                if (isDownloadCanceled) {
                    continue; // may be next image to be download
                }

                if (img == null) {
                    parent.informLoadError("Can't load image: " + imageNameToLoad);

                    continue; // may be next image to be download
                }

                // ok, show image to user
                parent.showImage(img, imageNameToLoad);

                // may be next image to be download
                continue;
            }
        }
    }

    /**
     * Search for bluetooth devices.
     *
     * @return false if should end the component work.
     */
    private boolean searchDevices() {
        // ok, start a new search then
        state = DEVICE_SEARCH;
        devices.removeAllElements();

        try {
            discoveryAgent.startInquiry(DiscoveryAgent.GIAC, this);
        } catch (BluetoothStateException e) {
            System.err.println("Can't start inquiry now: " + e);
            parent.informSearchError("Can't start device search");

            return true;
        }

        try {
            wait(); // until devices are found
        } catch (InterruptedException e) {
            System.err.println("Unexpected interruption: " + e);

            return false;
        }

        // this "wake up" may be caused by 'destroy' call
        if (isClosed) {
            return false;
        }

        // no?, ok, let's check the return code then
        switch (discType) {
        case INQUIRY_ERROR:
            parent.informSearchError("Device discovering error...");

        // fall through
        case INQUIRY_TERMINATED:
            // make sure no garbage in found devices list
            devices.removeAllElements();

            // nothing to report - go to next request
            break;

        case INQUIRY_COMPLETED:

            if (devices.size() == 0) {
                parent.informSearchError("No devices in range");
            }

            // go to service search now
            break;

        default:
            // what kind of system you are?... :(
            System.err.println("system error:" + " unexpected device discovery code: " + discType);
            destroy();

            return false;
        }

        return true;
    }

    /**
     * Search for proper service.
     *
     * @return false if should end the component work.
     */
    private boolean searchServices() {
        state = SERVICE_SEARCH;
        records.removeAllElements();
        searchIDs = new int[devices.size()];

        boolean isSearchStarted = false;

        for (int i = 0; i < devices.size(); i++) {
            RemoteDevice rd = (RemoteDevice)devices.elementAt(i);

            try {
                searchIDs[i] = discoveryAgent.searchServices(attrSet, uuidSet, rd, this);
            } catch (BluetoothStateException e) {
                System.err.println("Can't search services for: " + rd.getBluetoothAddress() +
                    " due to " + e);
                searchIDs[i] = -1;

                continue;
            }

            isSearchStarted = true;
        }

        // at least one of the services search should be found
        if (!isSearchStarted) {
            parent.informSearchError("Can't search services.");

            return true;
        }

        try {
            wait(); // until services are found
        } catch (InterruptedException e) {
            System.err.println("Unexpected interruption: " + e);

            return false;
        }

        // this "wake up" may be caused by 'destroy' call
        if (isClosed) {
            return false;
        }

        // actually, no services were found
        if (records.size() == 0) {
            parent.informSearchError("No proper services were found");
        }

        return true;
    }

    /**
     * Gets the collection of the images titles (names)
     * from the services, prepares a hashtable to match
     * the image name to a services list, presents the images names
     * to user finally.
     *
     * @return false if no names in found services.
     */
    private boolean presentUserSearchResults() {
        base.clear();

        for (int i = 0; i < records.size(); i++) {
            ServiceRecord sr = (ServiceRecord)records.elementAt(i);

            // get the attribute with images names
            DataElement de = sr.getAttributeValue(IMAGES_NAMES_ATTRIBUTE_ID);

            if (de == null) {
                System.err.println("Unexpected service - missed attribute");

                continue;
            }

            // get the images names from this attribute
            Enumeration deEnum = (Enumeration)de.getValue();

            while (deEnum.hasMoreElements()) {
                de = (DataElement)deEnum.nextElement();

                String name = (String)de.getValue();

                // name may be stored already
                Object obj = base.get(name);

                // that's either the ServiceRecord or Vector
                if (obj != null) {
                    Vector v;

                    if (obj instanceof ServiceRecord) {
                        v = new Vector();
                        v.addElement(obj);
                    } else {
                        v = (Vector)obj;
                    }

                    v.addElement(sr);
                    obj = v;
                } else {
                    obj = sr;
                }

                base.put(name, obj);
            }
        }

        return parent.showImagesNames(base);
    }

    /**
     * Loads selected image data.
     */
    private Image loadImage() {
        if (imageNameToLoad == null) {
            System.err.println("Error: imageNameToLoad=null");

            return null;
        }

        // ok, get the list of service records
        ServiceRecord[] sr = null;
        Object obj = base.get(imageNameToLoad);

        if (obj == null) {
            System.err.println("Error: no record for: " + imageNameToLoad);

            return null;
        } else if (obj instanceof ServiceRecord) {
            sr = new ServiceRecord[] { (ServiceRecord)obj };
        } else {
            Vector v = (Vector)obj;
            sr = new ServiceRecord[v.size()];

            for (int i = 0; i < v.size(); i++) {
                sr[i] = (ServiceRecord)v.elementAt(i);
            }
        }

        // now try to load the image from each services one by one
        for (int i = 0; i < sr.length; i++) {
            StreamConnection conn = null;
            String url = null;

            // the process may be canceled
            if (isDownloadCanceled) {
                return null;
            }

            // first - connect
            try {
                url = sr[i].getConnectionURL(ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
                conn = (StreamConnection)Connector.open(url);
            } catch (IOException e) {
                System.err.println("Note: can't connect to: " + url);

                // ignore
                continue;
            }

            // then open a steam and write a name
            try {
                OutputStream out = conn.openOutputStream();
                out.write(imageNameToLoad.length()); // length is 1 byte
                out.write(imageNameToLoad.getBytes());
                out.flush();
                out.close();
            } catch (IOException e) {
                System.err.println("Can't write to server for: " + url);

                // close stream connection
                try {
                    conn.close();
                } catch (IOException ee) {
                } // ignore

                continue;
            }

            // then open a steam and read an image
            byte[] imgData = null;

            try {
                InputStream in = conn.openInputStream();

                // read a length first
                int length = in.read() << 8;
                length |= in.read();

                if (length <= 0) {
                    throw new IOException("Can't read a length");
                }

                // read the image now
                imgData = new byte[length];
                length = 0;

                while (length != imgData.length) {
                    int n = in.read(imgData, length, imgData.length - length);

                    if (n == -1) {
                        throw new IOException("Can't read a image data");
                    }

                    length += n;
                }

                in.close();
            } catch (IOException e) {
                System.err.println("Can't read from server for: " + url);

                continue;
            } finally {
                // close stream connection anyway
                try {
                    conn.close();
                } catch (IOException e) {
                } // ignore
            }

            // ok, may it's a chance
            Image img = null;

            try {
                img = Image.createImage(imgData, 0, imgData.length);
            } catch (Exception e) {
                // may be next time
                System.err.println("Error: wrong image data from: " + url);

                continue;
            }

            return img;
        }

        return null;
    }
} // end of class 'BTImageClient' definition

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值