Technical Article
Using the Java APIs for Bluetooth, Part 2 - Putting the Core APIs to Work
By C. Enrique Ortiz, February, 2005
Bluetooth is a low-cost, short-range wireless technology that has become popular among those who want to create personal area networks (PANs). Each PAN is a dynamically created network built around an individual, that enables devices such as cellular phones and personal digital assistants (PDAs) to connect automatically and share data immediately. To support development of Bluetooth-enabled software on the Java platform, the Java Community Process (JCP) has defined JSR 82, the Java APIs for Bluetooth Wireless Technology (JABWT).
Part 1 of this article presented an overview of Bluetooth technology and JABWT, along with use cases, activities, and elements of a typical Bluetooth application. It also introduced the core JABWT APIs defined in the javax.bluetooth
package. This second part of the article will focus on the how-to aspects of JABWT. Code examples will show you how to use the core Bluetooth APIs to initialize a Bluetooth application, deal with connections, set up a service, discover nearby devices and services, connect to a service, and make a connection secure.
Introduction
Let's begin by reviewing briefly the typical use cases and activities of a Bluetooth-enabled application. A Bluetooth application can be either a server or a client - a producer of services or a consumer - or it can behave as a true peer-to-peer endpoint by exposing both server and client behavior. Typical Bluetooth applications have three categories of use cases:
Figure 1: Bluetooth Use Cases
- Initialization - Any Bluetooth-enabled application, server or client, must first initialize the Bluetooth stack.
- Client - A client consumes remote services. It first discovers any nearby devices, then for each discovered device it searches for services of interest.
- Server - A server makes services available to clients. It registers them in the Service Discovery Database (SDDB), in effect advertising them. It then waits for incoming connections, accepts them as they come in, and serves the clients that make them. Finally, when the service is no longer needed the application removes it from the SDDB.
Figure 2 diagrams the typical activities of Bluetooth client and server applications:
Figure 2: Server and Client Activities
You see in this figure that both client and server perform initialization, that the server application prepares a service and waits for connections, and that the client discovers devices and services, then connects to specific device to consume a particular service.
Now let's drill down into some sample code that shows how you implement these activities using the Java APIs for Bluetooth.
Initializing the Bluetooth Application
As you can see in Figure 3, initialization is a very simple activity:
Figure 3: Initializing the Bluetooth Application
First the application retrieves a reference to the Bluetooth Manager from the LocalDevice
. Client applications retrieve a reference to the DiscoveryAgent
, which provides all the discovery-related services. Server applications make the device discoverable. In the following code snippet, the initialization method btInit()
performs both client and server initialization:
...
private LocalDevice localDevice; // local Bluetooth Manager
private DiscoveryAgent discoveryAgent; // discovery agent
...
/**
* Initialize
*/
public void btInit() throws BluetoothStateException {
localDevice = null;
discoveryAgent = null;
// Retrieve the local device to get to the Bluetooth Manager
localDevice =
LocalDevice.getLocalDevice();
// Servers set the discoverable mode to GIAC
localDevice.setDiscoverable(DiscoveryAgent.GIAC);
// Clients retrieve the discovery agent
discoveryAgent =
localDevice.getDiscoveryAgent();
}
..
Not all applications serve as both server and client at the same time; the roles they play depend on your application requirements. Server applications set themselves to be discoverable, while client applications get a reference to the discovery agent for service discovery. When you set the device's discoverable mode by calling LocalDevice.setDiscoverable()
you must specify the inquiry access code (IAC). JABWT supports two access modes:
DiscoveryAgent.LIAC
specifies Limited Inquiry Access Code. The device will be discoverable for only a limited period of time, typically one minute. After the limited period, the device automatically reverts to undiscoverable mode.DiscoveryAgent.GIAC
specifies General Inquiry Access Code. No limit is set on how long the device remains in the discoverable mode.
To force a device back to undiscoverable mode, simply call LocalDevice.setDiscoverable()
with DiscoveryAgent.NOT_DISCOVERABLE
as the argument.
Note: Behind the scenes, the Bluetooth Control Center (BCC) serves as the central authority for all local Bluetooth settings. The BCC enables the user to define and override discoverable modes and other settings. The BCC also exists to keep any application from adversely affecting others. For more information, see the specification.
Whenever a device is discoverable it's visible to other Bluetooth devices, and thus open to attack. Even though GIAC is the most commonly used inquiry access code, to reduce risk you should consider making the device discoverable only when necessary, and perhaps only for short periods of time, by using LIAC. The BCC allows you to disable the discovery mode.
Dealing with Connections
In Part 1 of this article you learned that JABWT connections are based on the Logical Link Control and Adaptation Layer Protocol (L2CAP), a low-level data-packet protocol, and that a serial emulation protocol over L2CAP is supported by the Serial Port Profile (SPP) RFCOMM. JABWT connections are based on the Generic Connection Framework (GCF) and are represented by the L2CAPConnection
and StreamConnection
types respectively. Part 1 also noted that, while L2CAPConnection
was introduced with JSR 82, StreamConnection
was defined as part of the original javax.microedition.io
GCF, in the Connected Limited Device Configuration (CLDC).
As with all GCF connection types, you create a Bluetooth connection using the GCF connection factory javax.microedition.io.Connector
, passing to its open()
method a connection URL argument describing the connection endpoint to create. The connection URL scheme determines the connection type to create:
- The URL format for an
L2CAPConnection
:btl2cap:
//hostname:[
PSM|
UUID]
; parameters - The URL format for an RFCOMM
StreamConnection
:btspp:
//hostname:[
CN|
UUID]
; parameters
Where:
btl2cap
is the URL scheme for anL2CAPConnection
.btspp
is the URL scheme for an RFCOMMStreamConnection
.- hostname is either
localhost
to set up a server connection, or the Bluetooth address to create a client connection. - PSM is the Protocol/Service Multiplexer value, used by a client connecting to a server. This is similar in concept to a TCP/IP port.
- CN is the Channel Number value, used by a client connecting to a server - also similar in concept to a TCP/IP port.
- UUID is the Universally Unique Identifier used when setting up a service on a server. Each UUID is guaranteed to be unique across all time and space.
- parameters include
name
to describe the service name, and the security parametersauthenticate
,authorize
, andencrypt
.
For example:
- A server RFCOMM URL:
btspp://localhost:2D26618601FB47C28D9F10B8EC891363;authenticate=false; encrypt=false;name=MyBtService
- A client RFCOMM URL:
btspp://0123456789AF:1;master=false;encrypt=false;authenticate=false
Using localhost
as a hostname indicates you want a server connection. To create a client connection to a known device and service, use the service's connection URL, found in its ServiceRecord
.
Handling L2CAP connections is more involved than handling stream connections; developers must deal with maximum message sizes ( maximum transmission unit, or MTU), and with breaking up and reassembling long messages. These complexities are hidden from developers who use stream connections, making them preferable for Bluetooth connectivity. Code samples in this article cover only stream connections.
The following code snippet shows how to create an RFCOMM server connection:
...
// Bluetooth Service name
private static final String myServiceName = "MyBtService";
// Bluetooth Service UUID of interest
private static final String myServiceUUID = "2d26618601fb47c28d9f10b8ec891363";
private UUID MYSERVICEUUID_UUID = new UUID(myServiceUUID, false);
...
// Define the server connection URL
String connURL = "btspp://
localhost: "+MYSERVICEUUID_UUID.toString()+";"+name="+myServiceName;
...
// Create a server connection (a notifier)
StreamConnectionNotifier scn = (StreamConnectionNotifier)
Connector.open(connURL);
...
// Accept a new client connection
StreamConnection sc = scn
.acceptAndOpen();
...
The following code snippet shows how to create a client connection to a given service of interest, using its service record:
...
// Given a service of interest, get its service record
ServiceRecord sr = (ServiceRecord)discoveredServices.elementAt(i);
// Then get the service's connection URL
String connURL = sr
.getConnectionURL (ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
// Open connection
StreamConnection sc = (StreamConnection)
Connector.open (connURL);
...
If you're deploying your JABWT application on MIDP 2.0 handsets, it must request the appropriate permissions before attempting to connect, otherwise Connector.open()
will throw a SecurityException
. I'll tell you how to request permissions later, in the " JABWT and MIDP 2.0 Security" section of this article.
Setting up a Bluetooth Server
Figure 4: Setting up a Bluetooth Server
You set up a Bluetooth server to make a service available for consumption. There are four main steps:
- Creating a service record for the service you want to make available
- Adding the new service record to the Service Discovery Database
- Registering the service
- Waiting for incoming client connections
Two related operations are significant:
- Modifying the service record, if the service attributes that are visible to clients need to change
- When all done, removing the service record from the SDDB.
Let's look at each of these operations closely.
Creating a Service Record
The Bluetooth implementation automatically creates a service record when your application creates a connection notifier, either a StreamConnectionNotifier
or an L2CAPConnectionNotifier
.
Every Bluetooth service and service attribute has its own Universally Unique Identifier. You can't create a service without first assigning it a UUID. Easy enough: The UUID
class represents short (16- or 32-bit) and long (128-bit) UUIDs. To generate a UUID, use the command uuidgen -t
if you're running Linux, or uuidgen
if you're running Windows. The uuidgen
utility includes hyphens in the generated UUID; remove these hyphens when you copy the output into your source code.
To get a UUID to use in the sample code for this article I ran uuidgen -t
and it generated 2d266186-01fb-47c2-8d9f-10b8ec891363
. The following code snippet defines some private members for the service, such as the service name and service UUID members:
...
// Bluetooth service name
private static final String myServiceName = "MyBtService";
// Bluetooth service UUID
private static final String myServiceUUID = "2d26618601fb47c28d9f10b8ec891363";
private UUID MYSERVICEUUID_UUID = new UUID(myServiceUUID, false);
private UUID[] uuids = {MYSERVICEUUID_UUID};
...
The next snippet defines and instantiates an RFCOMM connection notifier, resulting in the creation of the service record:
...
StreamConnectionNotifier streamConnectionNotifier;
// Create notifier (and service record)
streamConnectionNotifier = (StreamConnectionNotifier)
Connector.open (connectionURL);
...
Registering the Service and Waiting for Incoming Connections
Once you've created the connection notifier and the service record, the server is ready to register the service and wait for clients. Invoking the notifier's acceptAndOpen()
method causes the Bluetooth implementation to insert the service record for the associated connection into the SDDB, making the service visible to clients. The acceptAndOpen()
method then blocks, waiting for incoming connections, which are accepted as they come in:
....
// Insert service record into SDDB and wait for an incoming client
StreamConnection conn = streamConnectionNotifier
.acceptAndOpen ();
...
When a client connects, acceptAndOpen()
returns a connection, in our example a StreamConnection
, that represents the client endpoint that the server will read data from. The following code snippet waits for and accepts an incoming client connection, then reads from it:
...
// Wait for client connection
StreamConnection conn = streamConnectionNotifier
.acceptAndOpen ();
// New client connection accepted; get a handle on it
RemoteDevice rd = RemoteDevice
.getRemoteDevice (conn);
System.out.println("New client connection... " +
rd.getFriendlyName(false));
// Read input message, in this example a String
DataInputStream dataIn =
conn.openDataInputStream ();
String s = dataIn.readUTF();
// Pass received message to incoming message listener
...
This snippet reads a simple String
from the connection. Different applications have different data-encoding needs, of course. While a String
might suffice for a chat application, a multimedia application would probably use a combination of character and binary data.
Note that this code blocks while waiting for incoming client connections, and must be dispatched in its own thread of execution; if it's called from the system thread, the user interface will freeze, and your application may deadlock.
Updating the Service Record
There are occasions when the attributes for a registered service must be changed. You can update records in the SDDB using the local Bluetooth manager. As the next snippet shows, you retrieve the record from the SDDB by calling LocalDevice.getRecord()
, add or change attributes of interest by calling ServiceRecord.setAttributeValue()
, and write the service record back to the SDDB with a call to LocalDevice.updateRecord()
:
...
try {
// Retrieve service record and set/update optional attributes,
// for example, ServiceAvailability, indicating service is available
sr = localDevice
.getRecord (streamConnectionNotifier);
sr
.setAttributeValue (SDP_SERVICEAVAILABILITY,
new DataElement(DataElement.U_INT_1, 0xFF));
localDevice
.updateRecord (sr);
} catch (IOException ioe) {
// Catch exception, display error
}
...
Closing the Connection and Removing the Service Record
When the service is no longer needed, remove it from the SDDB by closing the connection notifier:
...
streamConnectionNotifier.close();
...
Discovering Nearby Devices and Services
Figure 5: Discovering Devices and Services
A client can't consume services until it finds them. The search cycle consists of discovering nearby devices, then for each discovered device searching for services of interest. Discovering devices - inquiry - is expensive and time-consuming. The client can often avoid this overhead by finding out whether known or cached devices already provide the services of interest, and initiating a new inquiry only when they don't.
Discovery is the responsibility of the DiscoveryAgent
. This class allows the client to initiate and cancel device and service discovery. The DiscoveryAgent
notifies the client application of any discovered devices and services through the DiscoveryListener
interface. Figure 6 illustrates the relationships between the Bluetooth client, the DiscoveryAgent
, and the DiscoveryListener
.
Figure 6: The DiscoveryAgent and DiscoveryListener
To retrieve already known or cached devices the client calls the DiscoveryAgent
method retrieveDevices()
:
RemoteDevice[]
retrieveDevices (int option);
...Where option is one of the following:
CACHED
if the method should return previously found devicesPREKNOWN
if it should return devices already known
The client initiates a device discovery cycle by calling startInquiry()
:
boolean
startInquiry (int accessCode, DiscoveryListener listener);
...Where
accessCode
isDiscoveryAgent.LIAC
orDiscoveryAgent.GIAC
, as you saw earlierlistener
is the discovery listener.
To receive discovery notifications from the DiscoveryAgent
the client application must implement the DiscoveryListener
interface and its four discovery callbacks deviceDiscovered()
, inquiryCompleted()
, servicesDiscovered(), and serviceSearchCompleted()
, as illustrated in this sample class:
public class BtClient
implements DiscoveryListener {
...
Vector discoveredDevices = new Vector();
...
// DiscoveryListener Callbacks ///////////////////////
/**
* deviceDiscovered() is called by the DiscoveryAgent when
* it discovers a device during an inquiry.
*/
public void
deviceDiscovered (
javax.bluetooth.RemoteDevice remoteDevice,
javax.bluetooth.DeviceClass deviceClass) {
// Keep track of discovered remote devices by inserting
// them into a Vector
...
}
/**
* inquiryCompleted() is called by the DiscoveryAgent when
* a device discovery cycle finishes.
*/
public void
inquiryCompleted (int param) {
// Now that the inquiry has been completed, if any
// devices were discovered trigger the search for services
...
}
/**
* servicesDiscovered() is called by the DiscoveryAgent when
* a service search finds services.
* transID identifies the service search that returned results.
* serviceRecord holds references to the services found.
*/
public void
servicesDiscovered (int transID,
javax.bluetooth.ServiceRecord[] serviceRecord) {
// Keep track of discovered services, adding them
// to a Vector
...
}
/**
* serviceSearchCompleted() is called by the DiscoveryAgent
* implementation when a service search finishes.
* transID identifies a particular service search.
* responseCode indicates why the service search ended.
*/
public void
serviceSearchCompleted
(int transID, int responseCode) {
// Now that the service discovery has been completed,
// dispatch thread to handle the discovered services
...
}
...
}
The helper method btInitiateDeviceSearch()
, which can be called in response to a user request, retrieves any cached or previously found devices, then kicks off an inquiry:
/**
* btInitiateDeviceSearch() kicks off the device discovery
*/
public void btInitiateDeviceSearch() {
System.out.println("BTMIDlet.btInitiateDeviceSearch");
int i, s;
...
remoteDevices.clear();
discoveredDevices.clear();
...
RemoteDevice[] cachedDevices =
discoveryAgent
.retrieveDevices(DiscoveryAgent.CACHED) ;
if (cachedDevices != null) {
s = cachedDevices.length;
for (i=0; i<s; i++) {
remoteDevices.put(
cachedDevices[i].getBluetoothAddress(),
cachedDevices[i]);
}
}
...
RemoteDevice[] preknownDevices =
discoveryAgent
.retrieveDevices(DiscoveryAgent.PREKNOWN) ;
if (preknownDevices != null) {
s = preknownDevices.length;
for (i=0; i<s; i++) {
remoteDevices.put(
preknownDevices[i].getBluetoothAddress(),
preknownDevices[i]);
}
}
...
try {
inquiryStarted =discoveryAgent.startInquiry(DiscoveryAgent.GIAC, this);
} catch(BluetoothStateException bse) {
// Display error message
System.out.println("Inquiry Failed");
return;
}
if (inquiryStarted == true) {
// Display progress message
System.out.println("Inquiry in Progress");
} else {
// Display error message
System.out.println("Inquiry Failed");
}
}
Let's take a more detailed look at two callbacks the DiscoveryAgent
invokes, and see how the client should process discovered devices:
/**
* deviceDiscovered() is called by the DiscoveryAgent when
* it discovers a device during an inquiry.
*/
public void
deviceDiscovered (
javax.bluetooth.RemoteDevice remoteDevice,
javax.bluetooth.DeviceClass deviceClass) {
System.out.println("BTMIDlet.deviceDiscovered");
// Keep track of discovered remote devices
discoveredDevices.put(
remoteDevice.getBluetoothAddress(), remoteDevice);
}
/**
* inquiryCompleted() is called by the DiscoveryAgent when
* device discovery finishes.
* @param type is the type of request that was completed:
* INQUIRY_COMPLETED, INQUIRY_TERMINATED, or INQUIRY_ERROR
*/
public void
inquiryCompleted (int type) {
System.out.println("BTMIDlet.inquiryCompleted");
int i, s;
// After each inquiry is completed, update the screen
// Now that the inquiry has been completed, move newly
// discovered devices into the remoteDevices Vector
s = discoveredDevices.size();
if (s > 0) {
System.out.println(s + " device(s) found");
for (i=0; i<s; i++) {
RemoteDevice rd = (RemoteDevice)
discoveredDevices.elementAt(i);
// Add device only if not already known or cached
RemoteDevice rd2 = (RemoteDevice)
remoteDevices.get(rd.getBluetoothAddress());
if (rd2 == null) {
remoteDevices.put(rd.getBluetoothAddress(), rd);
}
}
} else {
System.out.println("No devices found");
}
// Show remote devices
String friendlyName;
s = remoteDevices.size();
if (s > 0) {
System.out.println(s + "device(s) found");
for (i=0; i<s; i++) {
RemoteDevice rd = (RemoteDevice)
remoteDevices.elementAt(i);
try {
friendlyName = rd.getFriendlyName(false);
} catch (IOException ioe) {
friendlyName = null;
}
if (friendlyName == null) {
System.out.println(rd.getBluetoothAddress());
} else {
System.out.println(friendlyName);
}
}
}
}
Once nearby devices have been discovered, the client can search for services of interest. The following code snippet shows the helper method btInitiateServiceSearch()
that is responsible for kicking off the service search:
/**
* btInitiateServiceSearch() kicks off the service discovery
*/
public void btInitiateServiceSearch() {
System.out.println("BTMIDlet.btInitiateServiceSearch");
int s, i;
...
discoveredServices.clear();
// Initiate the service search on the remote device
s = remoteDevices.size();
if (s==0) {
System.out.println("No devices to search...");
} else {
for (i=0; i<s; i++) {
RemoteDevice rd = (RemoteDevice)
remoteDevices.elementAt(i);
try {
transID = discoveryAgent
.searchServices (
attrs, uuids, rd, this);
// Display progress message
System.out.println(
"Service Search in Progress ("+transID+")");
synchronized (serviceSearchSemaphore) {
serviceSearchSemaphore.wait();
}
} catch (InterruptedException ie) {
// Ignore
} catch (BluetoothStateException bse) {
// ...
System.out.println("Service Search Failed");
return;
}
}
}
}
Connecting to a Service
Once a service of interest has been found, the client application can connect to it. As you learned earlier, the client can retrieve the service's connection URL from its service record. The next method shows how to connect to a service:
/**
* Connect to service represented by service record
* @param sr is the service record for the service of interest
*/
public void btConnect(final
ServiceRecord sr ) {
Thread th = new Thread() {
public void run() {
System.out.println("BTMIDlet.btConnect.run()");
RemoteDevice rd =
sr.getHostDevice ();
String connectionURL =
sr.getConnectionURL (
ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
try {
System.out.println(
"Connecting to " + rd.getFriendlyName(false));
System.out.println(
"Connecting to " + rd.getFriendlyName(false) +
", " + connectionURL);
StreamConnection streamConnection =
(StreamConnection)
Connector.open (connectionURL);
DataOutputStream dataout =
streamConnection.openDataOutputStream();
dataout.writeUTF(messageOut);
System.out.println("Closing");
streamConnection.close();
} catch (IOException ioe) {
System.out.println(
"BTMIDlet.btConnect, exception & + ioe);
}
}
};
th.start();
}
There's another way to connect to a service. If you don't care which device offers the desired service, you can use DiscoveryAgent.selectService()
. This method searches all nearby Bluetooth devices for the service indicated by a UUID, and if successful returns the service's connection URL. Here's a helper method that uses this approach:
/**
* Connect to service represented by UUID
* @param uuid is the UUID for the service of interest
*/
public void btConnect2(final UUID uuid) {
Thread th = new Thread() {
public void run() {
System.out.println("BTMIDlet.btConnect2.run()");
try {
// Select the service. Indicate no
// authentication or encryption is required.
String connectionURL = discoveryAgent.selectService(uuid,ServiceRecord.NOAUTHENTICATE_NOENCRYPT,false);
mainScreen.setTitle(
"Connecting to " + uuid.toString());
StreamConnection streamConnection =
(StreamConnection)
Connector.open(
connectionURL );
System.out.println("Sending message out");
DataOutputStream dataout =
streamConnection.openDataOutputStream();
dataout.writeUTF(messageOut);
System.out.println("Closing");
streamConnection.close();
} catch (BluetoothStateException bse) {
System.out.println("BTMIDlet.btConnect2,
exception " + bse);
} catch (IOException ioe) {
System.out.println("BTMIDlet.btConnect2,
exception " + ioe);
}
}
};
th.start();
}
Note that, because connecting to a server is a long operation, the method dispatches this task in its own thread of execution.
Securing a Bluetooth Connection
A secure Bluetooth connection is one that is authenticated, and optionally authorized, and encrypted. A Bluetooth connection can be secured when it's established, or later.
Note: Not all Bluetooth implementations provide secure connections.
To make a Bluetooth connection secure when you establish it you must ensure that the javax.microedition.io.Connector
connection URL string has the appropriate security parameters:
btspp://hostname:[CN | UUID];
authenticate
=true;
authorize
=true;
encrypt
=true
...Where:
authenticate
verifies the identity of a connecting device.authorize
verifies its access to a given service. Authorize is not allowed on client URL connection strings.encrypt
specifies that the connection must be encrypted.
For example:
btspp://localhost:2D26618601FB47C28D9F10B8EC891363; authenticate=true;encrypt=true;name=MyBtService
A client can retrieve a service's connection URL by calling ServiceRecord.getConnectionURL()
. One of this method's arguments, requiredSecurity
, determines whether the returned connection URL should include the optional authenticate
and encrypt
security parameters. The valid values for requiredSecurity
are:
ServiceRecord.AUTHENTICATE_NOENCRYPT
indicatesauthenticate=true; encrypt=false.
ServiceRecord.AUTHENTICATE_ENCRYPT
indicatesauthenticate=true; encrypt=true.
For example:
...
ServiceRecord sr = ...;
...
String connURL =
sr.getConnectionURL(
ServiceRecord.AUTHENTICATE_ENCRYPT, false);
// Open connection
StreamConnection sc = (StreamConnection)
Connector.open (connURL);
...
You can secure a connection after you establish it by invoking the RemoteDevice
security methods authenticate()
, authorize()
, and encrypt()
; for example:
To authenticate a remote device:
StreamConnection sc = ...;
// Authenticate the remote device for the indicated connection
RemoteDevice rd = RemoteDevice.getRemoteDevice(sc);
rd.authenticate();
To authorize a remote device's access to a given service:
// Authorize client access to service indicated by connection
StreamConnection sc = ...;
RemoteDevice rd = RemoteDevice.getRemoteDevice(sc);
rd.authorize(sc);
To encrypt a connection:
// Encrypt connection
StreamConnection sc = ...;
RemoteDevice rd = RemoteDevice.getRemoteDevice(sc);
rd.encrypt(sc, true);
Note that authentication must be performed before authorization and encryption. Turning encryption on will force authentication, if it hasn't already been done.
JABWT and MIDP 2.0 Security
MIDP 2.0 introduces a robust model for access to restricted resources and APIs that are based on permissions and policies. A Bluetooth application using JABWT and running under MIDP 2.0 may need to request permission to use network resources. Failing to request permission may cause Connection.open()
to throw a SecurityException
. You request permissions by creating MIDlet-Permissions
property entries in the JAD file or the JAR manifest:
- To request permission to open a Bluetooth server connection:
Connector.open("btspp://localhost:..."): MIDlet-Permissions:javax.microedition.io.Connector.bluetooth.server
- To request permission to open a Bluetooth client connection:
Connector.open("btspp://... "): MIDlet-Permissions:javax.microedition.io.Connector.bluetooth.client
- To request permission to open both:
MIDlet-Permissions:javax.microedition.io.Connector.bluetooth.client, javax.microedition.io.Connector.bluetooth.server
JABWT and MIDP 2.0 PushRegistry
MIDP 2.0's push registry manages network- and timer-initiated MIDlet activation; that is, it enables an inbound network connection or a timer-based alarm to wake a MIDlet up. MIDlets can thus use the PushRegistry
class to set themselves up to be launched automatically, without user initiation, if the underlying implementation supports push operations.
MIDlets can be activated by incoming Bluetooth connections, if they first register with the push registry, statically or dynamically.
For static registration, place MIDlet-Push-1
entries in the JAD or manifest:
...
MIDlet-Push-1: btspp://localhost:2D26618601FB47C28D9F10B8EC891363;
name=MyBtService,com.j2medeveloper.MyMIDlet, *
...
Dynamic registration is done at runtime, using the PushRegistry
's registerConnection()
API:
...
// MIDlet class name
String midletClassName = this.getClass().getName();
// Register a static connection.
String url = "btspp://localhost:2D26618601FB47C28D9F10B8EC891363;name=MyBtService"
// Use an unrestricted filter.
String filter = "*";
...
PushRegistry.registerConnection(url, midletClassName, filter);
...
The MIDP 2.0 permission model may require applications to request permissions. Creating the following MIDlet-Permissions
property entries in the JAD or the manifest requests permission to use PushRegistry
activation by a Bluetooth server connection:
MIDlet-Permissions: javax.microedition.io.PushRegistry,
javax.microedition.io.PushRegistry.bluetooth.server
Summary
You now should have a good understanding of Bluetooth networking and JSR 82, and be able to use the core Java APIs for Bluetooth Wireless Technology effectively. Bluetooth is an exciting wireless technology for personal networks that allows personal devices to share data and services. This article has covered a lot of ground, including some background information on Bluetooth, an overview of the typical elements of a Bluetooth-enabled MIDlet application, an introduction to the core JABWT interfaces in the javax.bluetooth
package, and some code that showed you how to use the core Java Bluetooth APIs.
Acknowledgements
Thanks to Jim Trudeau and Sony Ericsson for loaning me the devices that I used to run and test the code samples for this article. Thanks to Roger Riggs and Kirill Kounik and Brian Christeson for their feedback and helping improve this article.
Resources
About the Author
C. Enrique Ortiz is a software architect and developer, and a mobility technologist and writer. He is author or co-author of many publications, a co-designer of Sun Microsystems' the Mobile Java Developer Certification Exam, and has been an active participant in the wireless Java community and in various J2ME expert groups. Enrique holds a B.S. in Computer Science from the University of Puerto Rico and has more than 15 years of software engineering, product development, and management experience.