In this article, I'm going to demonstrate how to create a simple OBEX client application that can transfer a file to the server application. You'll also learn how to modify your OBEX client application into a Bluetooth Music Store. OBEX is a preferred method of sending and receiving files between two Bluetooth devices. (In the first article in this two-part series, I introduced the semantics of OBEX and explained how to create an OBEX server application.)
I'll start by creating the OBEX client application. Before you examine the code necessary to build the OBEX client application, take a brief look at the OBEX server application. Figure 1 shows the FileServer.java application at startup.
Figure 1. FileServer.java at startup
As you can see from Figure 1, the server application is ready and waiting for clients to connect, so let's find out how to build the OBEX client application.
Creating an OBEX client application
As you can see in Figure 2, FileClient.java looks a lot like FileServer.java, so I'll skip the details of FileClient.java for now.
Figure 2. FileClient.java at startup
Now, as I stated in Part 1 of this series, Bluetooth clients have a lot more work to do in comparison with Bluetooth servers -- and if you think about it for a second, you'll understand why. First of all, how does the client know where to find the server? (Don't worry, this problem exists for every Bluetooth client/server application; it's not unique to your situation). In order for any Bluetooth client to find a Bluetooth server, the client must first discover it.
DeviceDiscoverer.java is a helper application used by FileClient.java to find any remote Bluetooth devices in the vicinity. Listing 1 provides the import statements, class declarations, and the constructor of DeviceDiscoverer.java.
Listing 1. Import statement and constructor for DeviceDiscoverer.java
import javax.bluetooth.*;
import java.util.*;
public class DeviceDiscoverer implements DiscoveryListener {
FileClient client;
Vector remoteDevices = new Vector();
DiscoveryAgent discoveryAgent;
public DeviceDiscoverer(FileClient client) {
this.client = client;
try {
LocalDevice localDevice = LocalDevice.getLocalDevice();
discoveryAgent = localDevice.getDiscoveryAgent();
client.updateStatus("[client:] LocalDevice properties: " +
localDevice.getFriendlyName() +
" (" + localDevice.getBluetoothAddress() + ")");
client.updateStatus("[client:]
Searching for Bluetooth devices in the vicinity...");
discoveryAgent.startInquiry(DiscoveryAgent.GIAC, this);
} catch(Exception e) {
e.printStackTrace();
}
}
|
As you can see in Listing 1, DeviceDiscoverer.java implements javax.bluetooth.DiscoveryListener. This way, the helper class gets notified when a remote Bluetooth device is discovered. To start the device discovery process, you need to first get an instance of javax.bluetooth.LocalDevice, which allows you to get an instance of javax.bluetooth.DiscoveryAgent. After you have instantiated a DiscoveryAgent, you're free to call discoveryAgent.startInquiry(), which starts the device discovery process.
You may also notice in Listing 1 that LocalDevice has some pertinent information about your own Bluetooth device, such as its friendly name (like "Bruce's laptop" or "Joe's PDA"). LocalDevice also knows your 6-byte Bluetooth address; for instance, 00:0A:3E:56:57:B5. For informational purposes, DeviceDiscoverer.java displays that information before it enters the discovery process.
If you refer back to Figure 2, you'll see that FileClient.java has three buttons, of which one of them is aptly named Discover Devices. When you click on the Discover Devices button, you'll instantiate the helper class, DeviceDiscoverer.java. If you're new to Bluetooth, you may assume that the device discovery process is instantaneous. Unfortunately, it isn't.
However, the good news is that because the helper class, DeviceDiscoverer.java, is a DiscoveryListener, it is notified asynchronously when Bluetooth devices are found. For each remote Bluetooth device found in the vicinity, the Java Virtual Machine (JVM) calls the deviceDiscovered() method. When the device discovery process has ended, the JVM also calls the inquiryCompleted() method. Listing 2 and Listing 3 demonstrate the code for deviceDiscovered() and inquiryCompleted().
Listing 2. DeviceDiscoverer.deviceDiscovered()
public void deviceDiscovered(RemoteDevice remoteDevice, DeviceClass cod) {
try{
remoteDevices.addElement(remoteDevice);
client.updateStatus("[client:] New device discovered : " +
remoteDevice.getFriendlyName(true)+ " (" +
remoteDevice.getBluetoothAddress() + ")" );
} catch(Exception e){
e.printStackTrace();
}
}
|
Listing 3. DeviceDiscoverer.inquiryCompleted()
public void inquiryCompleted(int discType) {
String inqStatus = null;
if (discType == DiscoveryListener.INQUIRY_COMPLETED) {
inqStatus = "[client:] Inquiry completed";
} else if (discType == DiscoveryListener.INQUIRY_TERMINATED) {
inqStatus = "[client:] Inquiry terminated";
} else if (discType == DiscoveryListener.INQUIRY_ERROR) {
inqStatus = "[client:] Inquiry error";
}
client.updateStatus(inqStatus);
client.serviceButton.setEnabled(true);
client.deviceButton.setEnabled(false);
}
|
As you can see in Listing 2, whenever a new Bluetooth device is discovered, I just add it to a Vector and display the friendly name and Bluetooth address of the remote device. When the device discovery process has ended, I update the client with status of the discovery process, whether it succeeded or failed. Figure 3 shows FileClient.java after it has instantiated DeviceDiscoverer.java and has discovered all the Bluetooth devices in the vicinity.
Look at FileClient.java after the discovery process.
Figure 3. FileClient.java after the discovery process
OK, it looks like there's a slight problem. According to Figure 3, there are four remote Bluetooth devices in the area, but how do we know which one of them is running FileServer.java? Good question. That's where service discovery comes in very handy. And don't worry, I have another helper class that I'll introduce to you that will aid in the search for the specific service that I want.
Listing 4 contains the import statements, class declaration, and constructor for ServiceDiscoverer.java.
Listing 4. Import statement and constructor for ServiceDiscoverer.java
import javax.bluetooth.*;
import java.io.*;
import java.util.Vector;
public class ServiceDiscoverer extends Thread implements DiscoveryListener {
UUID[] uuidSet = {new UUID("8841", true)};
int[] attrSet = {0x0100, 0x0003, 0x0004};
FileClient client;
ServiceRecord serviceRecord;
String connectionURL;
Vector deviceList;
public ServiceDiscoverer(FileClient client, Vector deviceList) {
this.client = client;
this.deviceList = deviceList;
}
|
As you can see in Listing 4, ServiceDiscoverer.java is quite similar to the first helper class, DeviceDiscoverer.java, especially because both of them implement the same interface. However, ServiceDiscoverer.java differs due to the fact that it needs to run in a separate thread; if it doesn't, the user interface for FileClient.java will hang while it is searching for services on the Bluetooth devices in the vicinity.
Do you remember from Part 1 that every Bluetooth service (whether or not it uses OBEX) must have a unique identifier? You may recall that I set the UUID for FileServer.java to be 8841, the same value that I set in ServiceDiscoverer.java. This way, ServiceDiscoverer.java will find only the services on the remote Bluetooth devices that carry the UUID of 8841. Please note that UUIDs can either be 4 or 16 digits long; I chose a short UUID to provide an easier example. I also want you to notice that in the constructor, I passed in a Vector that contains all the remote Bluetooth devices that were found by the first helper class. Listing 5 provides the run() method of ServiceDiscoverer.java.
Listing 5. ServiceDiscoverer.run()
public void run(){
try {
LocalDevice localDevice = LocalDevice.getLocalDevice();
DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent();
RemoteDevice remoteDevice = null;
for(int i=0; i < deviceList.size(); i++){
remoteDevice = (RemoteDevice)deviceList.get(i);
client.updateStatus("[client:] Searching for Services on: " +
remoteDevice.getFriendlyName(true)+ " (" +
remoteDevice.getBluetoothAddress() + ")" );
discoveryAgent.searchServices(attrSet, uuidSet,
remoteDevice, this);
try{
Thread.sleep(2000);
} catch (Exception e){
}
}
}
catch(Exception e) {
e.printStackTrace();
}
}
|
Now, because ServiceDiscoverer.java implements the same interface that the other helper class does, it obtains a DiscoveryAgent in the same manner, just as I did in Listing 1. You should notice that I iterated over the Vector of remote Bluetooth devices and searched for the services that exist on each device.
These examples were tested on real Bluetooth hardware, so you should see that as I iterated through the Vector, I "backed off" for two seconds before I continued through the loop. The "backing off" period depends on your hardware; you may not need it at all. Without it though, your Bluetooth hardware may only search for services on the first remote Bluetooth devices that you pass in, and it may ignore the others. For each service that matches the UUID, the JVM calls the servicesDiscovered() method, which is shown in Listing 6.
Listing 6. ServiceDiscoverer.servicesDiscovered()
public void servicesDiscovered(int transID, ServiceRecord[] servRecord) {
for(int i = 0; i < servRecord.length; i++) {
DataElement serviceNameElement = servRecord[i].getAttributeValue(0x0100);
String serviceName = (String)serviceNameElement.getValue();
if(serviceName.equals("FTP")){
client.updateStatus("[client:] A matching service has been found");
try {
connectionURL = servRecord[i].getConnectionURL(1,false);
} catch (Exception e){
client.updateStatus("[client:] oops");
}
client.updateStatus("[client:] The connection URL is: " + connectionURL );
client.serviceButton.setEnabled(false);
client.connButton.setEnabled(true);
}
}
}
|
You may recall from Part 1 that the UUID is mandatory and the service name is optional for all Bluetooth servers; however, we specified that the service name for our service would be "FTP." As you can see in Listing 6, I'm checking to see if the service name is FTP, but just remember that every Bluetooth service may not specify a service name. After I've determined that a matching service has been found, I store the connection URL in a String and display some information on the client. A screenshot of FileClient.java after the service searching process is shown in Figure 4.
Figure 4. FileClient.java after the service searching process
Aha! So according to Figure 4, I've found a matching service and it exists on the ibook. You may notice that although I found a matching service on the ibook, I continued to iterate through the Vector and search for services. As you can see, the two helper classes came in very handy in order to get the connection URL to the server. Now that I have the URL to the server, let's connect and send a file to the server!
Connecting to the server and sending a file
To keep the code FileClient.java as clean as possible, I isolated all the OBEX client code into a file named ObjectPusher.java. Of course, ObjectPusher.java is multithreaded so that it won't hang the GUI application during the file-transfer process. Listing 7 shows the code for ObjectPusher.run().
Listing 7. ObjectPusher.run()
public void run(){
try{
connection = Connector.open(connectionURL);
client.updateStatus("Connection obtained");
ClientSession cs = (ClientSession)connection;
HeaderSet hs = cs.createHeaderSet();
cs.connect(hs);
client.updateStatus("OBEX session created");
InputStream is = new FileInputStream(file);
byte filebytes[] = new byte[is.available()];
is.read(filebytes);
is.close();
hs = cs.createHeaderSet();
hs.setHeader(HeaderSet.NAME, file.getName());
hs.setHeader(HeaderSet.TYPE, "text/plain");
hs.setHeader(HeaderSet.LENGTH, new Long(filebytes.length));
Operation putOperation = cs.put(hs);
client.updateStatus("Pushing file: " + file.getName());
client.updateStatus("Total file size: " + filebytes.length + " bytes");
OutputStream outputStream = putOperation.openOutputStream();
outputStream.write(filebytes);
client.updateStatus("File push complete");
outputStream.close();
putOperation.close();
cs.disconnect(null);
connection.close();
} catch (Exception e){
}
}
|
Obviously, the main purpose of ObjectPusher.java is to push a file from a client to the server. I start off by taking the connection URL and creating a connection object. With the connection in place, I'm able to create the OBEX session.
The next step is to convert the file that I want to send into a byte array. Afterwards, the OBEX headers are set and the OBEX PUT operation is initiated by the call to cs.put(). This returns a javax.obex.Operation object, which I named putOperation. An OutputStream was then created in order to send the file data, and the PUT operation was completed when the byte array was written to the OutputStream. Figure 5 shows FileClient.java after the file transfer process.
Figure 5. FileClient.java after the file transfer process
In conclusion: Creating the Bluetooth Music Store
In Part 1, you learned how to create an OBEX server application. In this article, you learned how to create a very versatile OBEX client application. You've seen that OBEX clients are more difficult to create, but this article presented a few helper classes to aid you in the device discovery and service discovery processes. Figure 6 shows a simple application that I call the Bluetooth Music Store.
Figure 6. The Bluetooth Music Store
It's a modified version of FileClient.java that uses the helper classes DeviceDiscoverer.java, ServiceDiscoverer.java, and ObjectPusher.java. With the Bluetooth Music Store, you can select a song or ringtone in MP3 format and push it to any phone, PDA, or computer that supports OBEX. Cool, huh?
So why do I call it a "Music Store?" Well, of course, if you own the rights to any music file, ringtone, or podcast, then you can easily use this application as a foundation to make a kiosk application and sell your audio files. Enjoy!
Learn
- Part 1 of this series "Bluetooth boogies, Part 1: File transfer with JSR-82 and OBEX" (developerWorks, September 2005) shows how JSR-82 and OBEX can be used to transfer files from client to server.
- IBM is a member of the Bluetooth SIG (Special Interest Group). The Bluetooth specifications can be obtained at the Bluetooth SIG Web site.
-
"The PalmOS data collection series" (developerWorks, November 2001) explores the key ingredients in developing data-collection applications for wireless devices.
Get products and technologies
- Download the FileClient, FileServer, and Bluetooth Music Store Application, all three used in this article, in a ZIP file.
- The examples in this article were created using the JB-22 Java Bluetooth Development kit. The JB-22 is a complete Java Bluetooth development kit featuring both Bluetooth hardware and software, starting at $199.
Discuss
- Get involved in the developerWorks community by participating in developerWorks blogs.

Bruce Hopkins is the author of the book, Bluetooth for Java (Apress) and is the creator of the JB-22 developer kit. He graduated from Wayne State University in Detroit with a B.S. degree in Electrical and Computer Engineering. He currently works as a Technical Architect for Gestalt LLC and focuses on distributed computing, Web services, and wireless technologies. He can be contacted at bhopkins@gestalt-llc.com.




