之前转了篇蓝牙实战,其中代码含简易蓝牙通信代码,不过功能仅限于传输字符串等简单格式。这两天翻了翻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