package se.nicklasgavelin.bluetooth;
import java.util.ArrayList;
import java.util.Collection;
import javax.bluetooth.*;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnectionNotifier;
import se.nicklasgavelin.log.Logging;
/**
* Gives the possibility to find and connect to remote Bluetooth
* devices. Functionality for creating a server socket will be added
* in the future.
*
* To listen for events on this class implement the "BluetoothDiscoveryListener"
* class
* for the classes. The BluetoothDiscoveryListener will receive events happening
* in this class.
*
* @author Nicklas Gavelin, nicklas.gavelin@gmail.com, LuleƄ University of Technology
* @version 0.1alpha
*/
public class Bluetooth implements DiscoveryListener, Runnable
{
/**
* Value for setting communication mode to serial communication
*/
public static final int SERIAL_COM = 0x1101;
// Bluetooth
private LocalDevice local;
private DiscoveryAgent dAgent;
// Listeners
private Collection<BluetoothDiscoveryListener> listeners;
// Thread
private Thread deviceDiscoveryThread = null;
// UUID
private UUID uuid;
// private String UUID_DEFAULT = "102030405060708090A0B0C0D0E0F010";
// Temporary stuff
private Collection<BluetoothDevice> devices;
private StreamConnectionNotifier server;
/**
* Describes an error for the Bluetooth
*
* @author Nicklas Gavelin
*/
public static class EVENT
{
private String message;
/**
* The code for each of the possible events
*/
public enum EVENT_CODE
{
/**
* Event code for when discovery is canceled
*/
ERROR_DISCOVERY_CANCELED,
/**
* Event code for when Bluetooth exception occurs
*/
ERROR_BLUETOOTH_EXCEPTION;
}
// The error code for the message
private EVENT_CODE errorCode;
/**
* Create an error message
*
* @param message The message for the error
* @param errorCode The error code of the message
*/
protected EVENT( String message, EVENT_CODE errorCode )
{
this.message = message;
this.errorCode = errorCode;
}
/**
* Returns the error code
*
* @return The error code
*/
public EVENT_CODE getErrorCode()
{
return this.errorCode;
}
/**
* Returns the error message
*
* @return The error message
*/
public String getErrorMessage()
{
return this.message;
}
}
/**
* Create a Bluetooth instance that uses the build in Bluetooth
* device.
*
* @param listener The one that will listen for events on this class
* instance
* @param uuid The UUID for the Bluetooth connections
*/
public Bluetooth( BluetoothDiscoveryListener listener, long uuid )
{
this( listener, Long.toString( uuid ) );
}
/**
* Create a Bluetooth instance that uses the build in Bluetooth
* device.
*
* @param listener The one that will listen for events on this class
* instance
* @param uuid The UUID for the Bluetooth connections
*/
public Bluetooth( BluetoothDiscoveryListener listener, int uuid )
{
this( listener );
this.uuid = new UUID( uuid );
}
/**
* Create a Bluetooth instance that uses the build in Bluetooth
* device.
*
* @param listener The one that will listen for events on this class
* instance
* @param uuid The UUID for the Bluetooth connections
*/
public Bluetooth( BluetoothDiscoveryListener listener, String uuid )
{
this( listener );
this.uuid = new UUID( uuid, false );
}
/**
* Create a Bluetooth instance that uses the build in Bluetooth
* device.
*
* @param uuid The UUID for the Bluetooth connections
*/
public Bluetooth( int uuid )
{
this( null, uuid );
}
/**
* Create a Bluetooth instance that uses the build in Bluetooth
* device.
*
* @param uuid The UUID for the Bluetooth connections
*/
public Bluetooth( long uuid )
{
this( null, uuid );
}
/**
* Create a Bluetooth instance that uses the build in Bluetooth
* device.
*
* @param uuid The UUID for the Bluetooth connections
*/
public Bluetooth( String uuid )
{
this( null, uuid );
}
/**
* Create a Bluetooth instance that uses the build in Bluetooth
* device. Do NOT use this directly as the UUID has to be set by the
* other constructors!
*
* @param listener The one that will listen for events on this class
* instance
*/
private Bluetooth( BluetoothDiscoveryListener listener )
{
this.listeners = new ArrayList<BluetoothDiscoveryListener>();
// Add the listener
if( listener != null )
this.listeners.add( listener );
try
{
// Try to get everything that we need regarding the local
// bluetooth device
this.local = LocalDevice.getLocalDevice();
this.dAgent = this.local.getDiscoveryAgent();
}
catch( BluetoothStateException e )
{
// throw new RuntimeException( e.getMessage() );
this.notifyListeners( new EVENT( e.getMessage(), EVENT.EVENT_CODE.ERROR_BLUETOOTH_EXCEPTION ) );
}
}
/*
* *********************************************************************************************************
*
* BLUETOOTH LISTENERS
*
* ************************************************************************************
* *******************
*/
/**
* Add listener to the Bluetooth instance
*
* @param listener The listener to add
*/
public void addListener( BluetoothDiscoveryListener listener )
{
if( !this.listeners.contains( listener ) )
this.listeners.add( listener );
}
/**
* Remove an active listener from the class
*
* @param listener The listener to remove
*/
public void removeListener( BluetoothDiscoveryListener listener )
{
if( this.listeners.contains( listener ) )
this.listeners.remove( listener );
}
/**
* Notify all listeners with the error event
*
* @param ERROR_CODE The error code
*/
private void notifyListeners( EVENT error )
{
for( BluetoothDiscoveryListener l : this.listeners )
l.deviceSearchFailed( error );
}
/**
* Notify listeners about new available devices (after a device search)
*
* @param devices The available devices
*/
private void notifyListeners( Collection<BluetoothDevice> devices )
{
for( BluetoothDiscoveryListener l : this.listeners )
l.deviceSearchCompleted( devices );
}
/**
* Notify listeners about a new available device
*
* @param device The available device
*/
private void notifyListeners( BluetoothDevice device )
{
for( BluetoothDiscoveryListener l : this.listeners )
l.deviceDiscovered( device );
}
/**
* Notify listeners that a search has been initialized
*/
private void notifyListenersDiscoveryStarted()
{
for( BluetoothDiscoveryListener l : this.listeners )
l.deviceSearchStarted();
}
/*
* *********************************************************************************************************
*
* DEVICE DISCOVERY
*
* ************************************************************************************
* *******************
*/
/**
* Searches for devices in the vicinity that we may connect to.
* Results are returned via the BluetoothListener methods.
*/
public void discover()
{
log( "Creating discovery thread" );
this.deviceDiscoveryThread = new Thread( this );
this.deviceDiscoveryThread.start();
}
/**
* Cancel an ongoing discovery event
*/
public void cancelDiscovery()
{
// TODO: Should this really be an error?
dAgent.cancelInquiry( this );
log( "Device discovery canceled" );
this.notifyListeners( new EVENT( "Device discovery canceled by user", EVENT.EVENT_CODE.ERROR_DISCOVERY_CANCELED ) );
}
/**
* Searches for devices in the vicinity that we may connect to.
* Results are returned via the BluetoothListener methods.
*/
private void performDiscovery()
{
log( "Starting discovery" );
this.notifyListenersDiscoveryStarted();
// Clear the previous device list if there is one
if( this.devices == null )
this.devices = new ArrayList<BluetoothDevice>();
else
this.devices.clear();
// Start searching for devices
synchronized( this.local )
{
try
{
log( "Starting inquiry" );
this.dAgent.startInquiry( DiscoveryAgent.GIAC, this );
local.wait();
}
catch( BluetoothStateException e )
{
error( "Failed to perform discovery, maybe interrupted" );
this.notifyListeners( new EVENT( "Failed to perform discovery due to exception", EVENT.EVENT_CODE.ERROR_BLUETOOTH_EXCEPTION ) );
// throw new RuntimeException( e.getMessage() );
}
catch( InterruptedException e )
{
// Just ignore, we got nothing else to do
}
}
// Go through all devices and set their names if they have one available
/*
* for( BluetoothDevice d : this.devices )
* {
* // Set the name
* try
* {
* log( "Setting device name for " + d );
* d.setName( d.getRemoteDevice().getFriendlyName( false ) );
* log( "Name for " + d + " is now " + d.getName() );
* }
* catch (IOException e) {}
* }
*/
// Notify observers
this.notifyListeners( devices );
}
/**
* Called when a device is discovered by the current discovery search
*
* @param device Discovered device
* @param deviceClass Device class
*/
@Override
public void deviceDiscovered( RemoteDevice device, DeviceClass deviceClass )
{
log( "Discovered device " + device );
BluetoothDevice btd = new BluetoothDevice( this, device );
this.devices.add( btd );
// Notify listeners
this.notifyListeners( btd );
}
/**
* Called when the discovery is completed. Will unlock any active mutex
* locks.
*/
@Override
public void inquiryCompleted( int arg0 )
{
log( "Discovery completed, notifying synchronized lock" );
synchronized( this.local )
{
this.local.notifyAll();
}
}
@Override
public void serviceSearchCompleted( int transId, int respCode )
{
/*
* log( "Service search completed." );
*
* switch ( respCode )
* {
* case DiscoveryListener.SERVICE_SEARCH_COMPLETED:
* log("The service search completed normally");
* break;
* case DiscoveryListener.SERVICE_SEARCH_TERMINATED:
* log("The service search request was cancelled by a call to
* DiscoveryAgent.cancelServiceSearch(int)");
* break;
* case DiscoveryListener.SERVICE_SEARCH_ERROR:
* log("An error occurred while processing the request");
* break;
* case DiscoveryListener.SERVICE_SEARCH_NO_RECORDS:
* log("No records were found during the service search");
* break;
* case DiscoveryListener.SERVICE_SEARCH_DEVICE_NOT_REACHABLE:
* log("The device specified in the search request could not be reached
* or the local device could not establish a connection to the remote
* device");
* break;
* default:
* log("Unknown Response Code - " + respCode);
* break;
* }
*/
}
@Override
public void servicesDiscovered( int arg0, ServiceRecord[] arg1 )
{
/*
* if( arg1.length == 0 ) System.out.println("No service record found");
* for( ServiceRecord r : arg1 )
* System.out.println("Service: " +
* r.getHostDevice().getBluetoothAddress() +
* r.getConnectionURL(ServiceRecord.AUTHENTICATE_NOENCRYPT, false));
*/
}
/*
* *********************************************************************************************************
*
* THREAD
*
* ************************************************************************************
* *******************
*/
/**
* Handles device discovery
*/
public void run()
{
this.performDiscovery();
}
/**
* Starts a local Bluetooth server that listens for connections
* on the Bluetooth address.
*
* @param name The name for the server
*/
public void startServer( String name )
{
try
{
log( "Setting up connection listener" );
local.setDiscoverable( DiscoveryAgent.GIAC );
String url = "btspp://localhost:" + uuid.toString() + ";name=" + name;
server = (StreamConnectionNotifier) Connector.open( url );
ServiceRecord record = local.getRecord( server );
// // set availability to fully available
record.setAttributeValue( 0x0008, new DataElement( DataElement.U_INT_1, 0xFF ) );
// // set device class to telephony
record.setDeviceServiceClasses( 0x400000 );
// // set up a service for this record and set it up as the thread
BluetoothService s = new BluetoothService( null, record );//, this );
new Thread( s ).start();
}
catch( Exception e )
{
this.notifyListeners( new EVENT( "Failed to setup bluetooth server socket: " + e.getMessage(), EVENT.EVENT_CODE.ERROR_BLUETOOTH_EXCEPTION ) );
// throw new RuntimeException( e.getMessage() );
}
}
/*
* *********************************************************************************************************
*
* GETTERS
*
* ************************************************************************************
* *******************
*/
/**
* Returns the discovery agent
*
* @return The discovery agent
*/
protected DiscoveryAgent getDiscoveryAgent()
{
return this.dAgent;
}
/**
* Returns the pre-set UUID
*
* @return The UUID
*/
public UUID getUUID()
{
return this.uuid;
}
/*
* *********************************************************************************************************
*
* DEBUG
*
* ************************************************************************************
* *******************
*/
/**
* Log a message
*
* @param msg The message to log
*/
private void log( String msg )
{
Logging.debug( msg );
}
/**
* Log an error
*
* @param msg The error to log
*/
private void error( String msg )
{
Logging.error( msg );
}
}