Show BTService.java syntax highlighted
/*
* Copyright (c) 2005
* Helsinki Institute of Physics
* see LICENSE file for details
*
* BTService.java
* Created on Mar 7, 2004
*/
package fi.hip.gb.bluetooth;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;
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;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;
/**
* This is the main class for handling bluetooth connectivity and the device/service
* discovery process. This class does many things, including:
* <ul>
* <li>search for bluetooth devices {@link BTService#query()}
* <li>create a local bluetooth server and register it with bluetooth
* {@link BTService#startServer()}
* <li>search for remote services automatically after device discovery
* <li>handle incoming connection request from remote devices
* </ul>
*
* <p>
* The service runs on any JSR-82 Bluetooth API compatible device,
* for example, mobile phones with Bluetooth support. Nokia 6600 has been
* tested. It also works nicely with
* <a href=http://bluecove.sourceforge.net/>Bluecove</a> API for J2SE
* platform.
*/
public class BTService implements Runnable {
public final static int SIGNAL_HANDSHAKE = 0;
public final static int SIGNAL_MESSAGE = 1;
public final static int SIGNAL_TERMINATE = 3;
public final static int SIGNAL_HANDSHAKE_ACK = 4;
public final static int SIGNAL_TERMINATE_ACK = 5;
public final static int SIGNAL_DISPATCH = 6;
// BlueChat specific service UUID
// note: this UUID must be a string of 32 char
// do not use the 0x???? constructor because it won't
// work. not sure if it is a N6600 bug or not
private final static UUID uuid = new UUID(
"102030405060708090A0B0C0D0E0F010", false);
/** reference to bluetooth server */
private static BTService instance;
public final static int SERVICE_OBJECT_TRANSFER = 1048576; //0x100000;
/** reference to local bluetooth device singleton */
private LocalDevice localDevice = null;
/** reference to local discovery agent singleton */
private DiscoveryAgent agent = null;
/** local server object */
private StreamConnectionNotifier server;
/** instances of <code>BTListener</code> implementation listening
* for events */
private Vector listeners = new Vector();
/** set to true when server should be shut down */
boolean done = false;
/** name of the local bluetooth device */
public static String localName = "BTAgent";
/**
* A list of active EndPoints. all messages will be sent to all
* active EndPoints
*/
private Vector endPoints = new Vector();
/**
* A list of pending EndPoints. This is used to keep track of
* discovered devices waiting for service discovery. When all the near-by
* BlueChat service has been discovered, this list will be cleared until the
* next inquiry.
*/
private Vector pendingEndPoints = new Vector();
/** timer to schedule task to do service discovery, see inquiryCompleted */
private Timer timer = new Timer();
/** synchronization lock, see DoServiceDiscovery and serviceSearchCompleted */
private Object lock = new Object();
/** max number of service searches that can occur at any one time */
private int maxServiceSearches = 0;
/** number of service searches that are presently in progress */
private int serviceSearchCount;
/**
* Call {@link BTService#getInstance()} to get the instance.
*/
private BTService() {
}
/**
* Gets the instance of the bluetooth service.
* <p>
* To start up the server use {@link BTService#startServer()} and later
* to shut it down use {@link BTService#stopServer()} method.
*
* @return instance of the BT-service
*/
public static BTService getInstance() {
if (BTService.instance == null) {
BTService.instance = new BTService();
// initialize the JABWT stack
try {
instance.localDevice = LocalDevice.getLocalDevice();
instance.agent = instance.localDevice.getDiscoveryAgent();
// retrieve the name of the local Bluetooth device
BTService.localName = instance.localDevice.getFriendlyName();
/*
* Retrieve the max number of concurrent service searches that can exist
* at any one time. N6600 is capable to only one simultaneous
* connection.
*/
try {
instance.maxServiceSearches = 1;
//Integer.parseInt(LocalDevice.getProperty("bluetooth.sd.trans.max"));
//log("maxServiceSearches = " + maxServiceSearches);
} catch (NumberFormatException e) {
instance.log("NumberFormatException: " + e.getMessage());
}
/*
} catch(UnsatisfiedLinkError ule) {
instance.log("Bluecove works only in Windows XP");
BTService.instance = null;
return null;
} catch(NoClassDefFoundError ncdfe) {
instance.log("Bluecove works only in Windows XP");
BTService.instance = null;
return null;*/
} catch(Exception e) {
instance.log("failed to initialize local BT-device");
return null;
}
}
return instance;
}
/**
* Add a listener class for bluetooth events.
*
* @param callbackListener
* listener to be added
*/
public void addListener(BTListener callbackListener) {
this.listeners.addElement(callbackListener);
}
/**
* Remove a listener class from bluetooth events.
*
* @param callbackListener
* listener to be removed
*/
public void removeListener(BTListener callbackListener) {
this.listeners.removeElement(callbackListener);
}
/**
* Makes the bluetooth device visible and starts a
* server in a new thread.
*
* @throws javax.bluetooth.BluetoothStateException
*/
public void startServer() throws BluetoothStateException {
this.localDevice.setDiscoverable(DiscoveryAgent.GIAC);
new Thread(this).start();
}
/**
* Stops the bluetooth server, sends terminate commands to all connected
* clients and removes them from the list.
*/
public void stopServer() {
log("invoke disconnect()");
// do not accept client connections any more
this.done = true;
try {
// this close will interrupt server.acceptAndOpen()
// wake it up to exit
if (this.server != null)
this.server.close();
} catch (Exception ex) {
}
// clean up connected endpoints
for (int i = 0; i < endPoints.size(); i++) {
EndPoint endPoint = (EndPoint) endPoints.elementAt(i);
cleanupRemoteEndPoint(endPoint);
}
}
/**
* Gets the status of the server.
*
* @return true if bluetooth server is running and waiting new connections,
* false if its stopping or stopped.
*/
public boolean isListening() {
return this.done;
}
/**
* Removes the local device from inquiry mode and cancels all service
* searches that are presently being performed.
* <b>
* NOTE! DiscoveryAgent#cancelServiceSearch(int) is not implemented
* by BlueCove yet and doesn't work.
*/
public void cancel() {
// cancel the device inquiry if in progress
this.agent.cancelInquiry(new Listener());
// cancel all service inquiries
for (int i = 0; i < this.pendingEndPoints.size(); i++) {
EndPoint endpt =
(EndPoint) this.pendingEndPoints.elementAt(i);
if(endpt.transId != -1) {
this.agent.cancelServiceSearch(endpt.transId);
}
}
}
/**
* Searches all available bluetooth devices and adds them to the GUI
* through {@link BTListener} interface.
* <p>
* Before searching new devices, clean up all known connections, list of
* devices and services.
*
* @throws javax.bluetooth.BluetoothStateException
* if cannot inquery the bluetooth network
*/
public void query() throws BluetoothStateException {
// remove known endpoints
for (int i = 0; i < this.endPoints.size(); i++) {
EndPoint endpt = (EndPoint) this.endPoints.elementAt(i);
cleanupRemoteEndPoint(endpt);
}
// Although JSR-82 provides the ability to lookup
// cached and preknown devices, we intentionally by-pass
// them and go to discovery mode directly.
// This allow us to retrieve the latest active parties.
this.agent.startInquiry(DiscoveryAgent.GIAC, new Listener());
}
/**
* Finds endpoint by the name.
* @param endptName name of the device
* @return endpoint object, or null if no match was found.
*/
public EndPoint findEndPointByName(String endptName) {
for (int i = 0; i < this.endPoints.size(); i++) {
EndPoint endpt = (EndPoint) this.endPoints.elementAt(i);
if (endpt.getName().equals(endptName)) {
return endpt;
}
}
return null;
}
/**
* Finds endpoint by the remote device.
* @param rdev remote device object
* @return endpoint object, or null if no match was found.
*/
private EndPoint findEndPointByRemoteDevice(RemoteDevice rdev) {
for (int i = 0; i < this.endPoints.size(); i++) {
EndPoint endpt = (EndPoint) this.endPoints.elementAt(i);
if (endpt.getRemoteDevice().equals(rdev)) {
return endpt;
}
}
return null;
}
/**
* Finds endpoint by the discovery id.
* @param id id of the device
* @return endpoint object, or null if no match was found.
*/
private EndPoint findEndPointByTransId(int id) {
for (int i = 0; i < this.pendingEndPoints.size(); i++) {
EndPoint endpt = (EndPoint) this.pendingEndPoints.elementAt(i);
if (endpt.transId == id) {
return endpt;
}
}
return null;
}
/**
* Sends an event to listening {@link BTListener} implementations.
* @param eventType type of the event
* @param source endpoint where the event originates
* @param payload payload data depending on the eventType
*/
protected void fireEvent(String eventType, EndPoint source, Object payload) {
for(Enumeration e = this.listeners.elements(); e.hasMoreElements();) {
((BTListener)e.nextElement()).handleAction(eventType, source, payload);
}
}
/**
* Closes the connection and removes the endpoint.
* <p>
* Use {@link Service#closeConnection()} to close the connection
* only, this method will also remove the device from the list of known
* devices.
*
* @param endpt endpoint to be removed
*/
public void cleanupRemoteEndPoint(EndPoint endpt) {
log("cleaning " + endpt);
// close all service connections
Service[] services = endpt.getConnections();
for(int i=0; i < services.length; i++) {
services[i].closeConnection();
}
// remove this end point from the active end point list
this.endPoints.removeElement(endpt);
// emit LOST event to BTListener implementation
fireEvent(BTListener.EVENT_LOST, endpt, null);
}
/**
* Bluetooth server run in this loop. The server is first initialised then
* client connections are waited until {@link BTService#stopServer()} method is
* called.
* <br>
* All new connections are passed to new {@link EndPoint} object that
* handles the communication with the remote service.
*/
public void run() {
try {
// Create a server connection object, using a
// Serial Port Profile URL syntax and our specific UUID
// and set the service name to BlueChatApp
server = (StreamConnectionNotifier)
Connector.open("btspp://localhost:" + uuid.toString()
+ ";name=GBAgent");
// Retrieve the service record template
ServiceRecord rec = localDevice.getRecord(server);
// set ServiceRecrod ServiceAvailability (0x0008) attribute to
// indicate our service is available, 0xFF indicate fully available status
rec.setAttributeValue(0x0008, new DataElement(DataElement.U_INT_1,
0xFF));
// print the service record, which contains some default values
// Util.printServiceRecord(rec);
// Set the Major Service Classes flag in Bluetooth stack.
rec.setDeviceServiceClasses(SERVICE_OBJECT_TRANSFER);
} catch (Exception e) {
System.out.println("cannot start the bluetooth server: "
+ e.toString());
this.done = true;
}
while (!this.done) {
// connection to remote device
StreamConnection c = null;
try {
// this message is to inform user that the server is up and ready
log("Ready to accept connection...");
// start accepting client connection.
// This method will block until a client connected
c = server.acceptAndOpen();
log("connection accepted");
// retrieve the remote device object
RemoteDevice rdev = RemoteDevice.getRemoteDevice(c);
// check to see if the EndPoint already exist
EndPoint endpt = findEndPointByRemoteDevice(rdev);
if (endpt != null) {
// this is a safe guard to assure that this client
// has not been connected before
log("client connection end point already exist.. ignore this connection");
} else {
// create a new EndPoint object and open the connection
endpt = new EndPoint(rdev, null);
endpt.addService(c).openConnection();
// add this EndPoint to the active list and inform UI
this.endPoints.addElement(endpt);
fireEvent(BTListener.EVENT_COMPATIBLE, endpt, null);
log("a new active EndPoint is established. name="
+ endpt.getName());
}
} catch (Exception e) {
System.out.println("bluetooth connection failed "
+ e.toString());
// if any exception happen, we assume this connection is
// failed and close it. closing the connection will cause
// the reader and sender thread to exit (because they will got
// exception as well).
if (c != null) {
try {
c.close();
} catch (IOException e2) {
}
}
} finally {
// nothing to be done here
}
}
}
public void log(String s) {
//System.out.println(s);
if(this.listeners.size() > 0)
((BTListener)this.listeners.elementAt(0)).log("N " + s);
}
/**
* Listener class for device and service discovery.
*/
class Listener implements DiscoveryListener {
/**
* A device is discovered. Create a EndPoint for the device discovered
* and put it on the pending list. A service search will happen when all
* the qualifying devices are discovered.
*
* @param remoteDevice
* @param deviceClass
*/
public void deviceDiscovered(RemoteDevice remoteDevice,
DeviceClass deviceClass) {
if (findEndPointByRemoteDevice(remoteDevice) != null) {
log("discovered device "
+ remoteDevice.getBluetoothAddress()
+ " already exists");
return;
}
//log("invoke deviceDiscovered name=" + name);
int SERVICE_ALL = 0x100000;
//if ((deviceClass.getServiceClasses() /*&
// SERVICE_OBJECT_TRANSFER*/) != 0) {
// create a inactive EndPoint and put it on the pending list
EndPoint endpt = new EndPoint(remoteDevice, deviceClass);
BTService.this.fireEvent(BTListener.EVENT_DISCOVERED, endpt, null);
BTService.this.pendingEndPoints.addElement(endpt);
//} else {
// log("found device of wrong type, ignore this device...");
//}
}
/**
* Device discovery completed. After device inquery completed, we start
* to search for GBAgent services. We loop through all the pending
* EndPoints and request agent.searchServices on each of the remote
* device.
*
* @param transId
*/
public void inquiryCompleted(int transId) {
log("invoke inqueryCompleted "
+ BTService.this.pendingEndPoints.size() + " devices");
// start doing service discovery after a while
fireEvent(BTListener.EVENT_SERVICES, null, null);
timer.schedule(new DoServiceDiscovery(), 1000);
}
/**
* A service is discovered from a remote device.
* <p>
* Note: we know the found service is wanted service because we search
* on specific UUID,
*
* @param transId
* @param svcRec
*/
public void servicesDiscovered(int transId, ServiceRecord[] svcRec) {
log("invoke servicesDiscovered:" + transId + "," + svcRec.length);
EndPoint endpt = findEndPointByTransId(transId);
for (int i = 0; i < svcRec.length; i++) {
// Util.printServiceRecord(svcRec[i]);
//EndPoint endpt =
// findEndPointByRemoteDevice(svcRec[i].getHostDevice());
/**
* TODO: There might be multiple instances of services on the
* device, we assume that the last one is the valid one. The
* device is always the same, but the URL might be different, so
* we update only the URL.
*/
/*
* EndPoint existing =
* findEndPointByRemoteDevice(svcRec[i].getHostDevice()); if
* (existing != null) { existing.url =
* svcRec[i].getConnectionURL(ServiceRecord.NOAUTHENTICATE_NOENCRYPT,
* false); log("skipping " + endpt.getName()); } else {
*/
fireEvent(BTListener.EVENT_COMPATIBLE, endpt, null);
endpt.addNMEAService(svcRec[i]);
log("a valid service added name=" + endpt.getName() + " : "
+ endpt.getName());
//}
}
BTService.this.endPoints.addElement(endpt);
}
/**
* Service discovery is completed.
*
* @param transID
* @param respCode
*/
public void serviceSearchCompleted(int transID, int respCode) {
// print response code
if (respCode == SERVICE_SEARCH_COMPLETED)
log("SERVICE_SEARCH_COMPLETED");
else if (respCode == SERVICE_SEARCH_TERMINATED)
log("SERVICE_SEARCH_TERMINATED");
else if (respCode == SERVICE_SEARCH_ERROR)
log("SERVICE_SEARCH_ERROR");
else if (respCode == SERVICE_SEARCH_NO_RECORDS)
log("SERVICE_SEARCH_NO_RECORDS");
else if (respCode == SERVICE_SEARCH_DEVICE_NOT_REACHABLE)
log("SERVICE_SEARCH_DEVICE_NOT_REACHABLE");
else
log("invoke serviceSearchCompleted: " + transID);
// removes the transaction ID
findEndPointByTransId(transID).transId = -1;
serviceSearchCount--;
synchronized (lock) {
// unlock to proceed to service search on next device
// see DoServiceDiscovery.run()
lock.notifyAll();
}
}
}
/**
* Service discovery is initialised here after a small delay.
*/
class DoServiceDiscovery extends TimerTask {
public void run() {
//log("searching services");
// for each EndPoint, we search for services
for (int i = 0; i < BTService.this.pendingEndPoints.size(); i++) {
EndPoint endpt = (EndPoint) BTService.this.pendingEndPoints.elementAt(i);
try {
log("searching service from " + endpt.getName());
// searchServices return a transaction id, which we will used to
// identify which remote device the service is found
endpt.transId = agent.searchServices(null, // retrieve default attributes
new UUID[] { new UUID(0x0100) }, //search criteria, 0x0100 = L2CAP
endpt.getRemoteDevice(), new Listener());
/*
* Determine if another search can be started. If not, wait for
* a service search to end.
*/
synchronized (lock) {
serviceSearchCount++;
if (serviceSearchCount == maxServiceSearches) {
try {
lock.wait();
} catch (InterruptedException ie) {
}
}
}
} catch (BluetoothStateException bse) {
log("failed to discover services "
+ bse.toString());
}
}
// no more service to discovery. so any pending EndPoints
// will be ignored and removed
BTService.this.pendingEndPoints.removeAllElements();
// this message is to inform user that chatting can start
fireEvent(BTListener.EVENT_FINISHED, null, null);
}
}
}
See more files for this project here