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:

Click to enlarge

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:

Click to enlarge

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 an L2CAPConnection.
  • btspp is the URL scheme for an RFCOMM StreamConnection.
  • 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 parameters authenticate, authorize, and encrypt.

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:

  1. Creating a service record for the service you want to make available
  2. Adding the new service record to the Service Discovery Database
  3. Registering the service
  4. 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 devices
  • PREKNOWN 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 is DiscoveryAgent.LIAC or DiscoveryAgent.GIAC, as you saw earlier
  • listener 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 indicates authenticate=true; encrypt=false.
  • ServiceRecord.AUTHENTICATE_ENCRYPT indicates authenticate=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.