Sun Java Solaris Communities My SDN Account Join SDN
 
Article

Using the JSR-82 API for OBEX Image Transfers

 
By Bruce Hopkins, November, 2006  

Source Download

Introduction

The purpose of this article is to provide hands-on experience with the Java APIs for Bluetooth Wireless Technology, JSR-82 API. If you're unfamiliar with the semantics of Bluetooth, don't worry. I'll cover those items with a brief introduction to the Bluetooth protocol and its use cases (called Bluetooth profiles). Because this application will demonstrate how to use Bluetooth to transfer images to other Bluetooth devices, I'll also show you how to use the File Connection API of the JSR-75 in order to have programmatic access to the file system of your mobile device. At the end of the article, we'll have a working example that's capable of transferring images (and any file for that matter) to remote Bluetooth devices.

The Bluetooth Protocol

Here's a little known fact about Bluetooth: It's one of the most (if not the most) widely deployed and successful SOA (Service Oriented Architecture) systems in the world. Bluetooth technology has a very large installed based (over 500 million devices deployed) with current figures estimating that an additional five million Bluetooth devices are shipped every week. Long before the term "Service Oriented Architecture" became a buzzword, the Bluetooth protocol provided a mechanism for service registration, service discovery, and service invocation.

So, the Bluetooth protocol incorporates a service-oriented architecture and employs a familiar client/server communication scheme used in other protocols such as HTTP and FTP: Servers wait patiently until a client initiates a request. Current Bluetooth devices on the market are capable of communicating at 3 Mb/s and can support wireless audio in stereo. Figure 1, below is a diagram showing the layers of the Bluetooth protocol stack.

Figure 1: The Bluetooth protocol stack and its layers
Figure 1: The Bluetooth protocol stack and its layers
 

Because the focus of this article is OBEX, I won't go into details about all the layers shown in Figure 1, but I do want to provide some details on the major layers that support OBEX. As you can see, one of the major protocol layers in the stack is L2CAP (the Logical Link Control and Adaptation Protocol). L2CAP functions as the multiplexer of packet data between all the other upper layers. RFCOMM, on the other hand, is known as the "virtual serial port" layer. RFCOMM works well when you need to communicate with devices that support data streams. OBEX (which stands for Object Exchange) is the protocol layer that is best suited for file transfers. With OBEX you can create and send messages to remote Bluetooth devices that contain the payload (i.e. the file that you want to send) and also important metadata (such as the filename, file size, and file type).

Bluetooth ProfilesTh

Bluetooth Profiles allow various Bluetooth devices with different capabilities to interact and cooperate. Each profile is a use case that defines functionality for a specific purpose. For instance, if you want to print from your mobile device to a printer, both devices must implement the Basic Printing Profile. Or, for instance, if you want to sync a desktop and PDA contact list, both devices must support the Synchronization Profile. Table 1, located below, lists the profiles that use the OBEX protocol layer of the Bluetooth stack.

Table 1. Current OBEX-based Profiles
Profile Name
Abbreviation
UUID
Object Push Profile
OPP
0x1105
File Transfer Profile
FTP
0x1106
Synchronization Profile
SYP
0x1104
Basic Imaging Profile
BIP
0x111A
Phone Book Access Profile
PBAP
0x1130
Basic Printing Profile
BPP
0x1122
 

According to the Bluetooth SIG, there are well over 30 Bluetooth profiles defined, ranging from audio distribution to personal area networking. In this article, we'll use the JSR-82 API to implement the Object Push Profile and send images to any Bluetooth device that also supports OPP.

Creating the ImageSender Midlet

The ImageSender Midlet was created using the NetBeans Mobility Pack of the NetBeans 5.0 IDE. The Mobility Pack includes a very handy GUI designer tool that allows mobile developers to quickly create mobile applications using drag-and-drop techniques. The ImageSender Midlet contains several static GUI components (i.e. components that are not created dynamically), and the Mobility Pack is very efficient at creating the GUI components and the workflow between them. Figure 2, shown below, depicts the NetBeans project used for the ImageSender Midlet.

Figure 2: NetBeans Project used to create the ImageSender Midlet
Figure 2: NetBeans Project used to create the ImageSender Midlet (Click on image for larger view.)
 

In order for the ImageSender to accomplish its duties (such as reading files from the file system, and sending data to remote Bluetooth devices), the ImageSender employs the functionality of inner classes that encapsulate three significant areas of functionality:

  1. Reading files and traversing a file system (this will be handled by FileNavigator)

  2. Discovering remote Bluetooth devices (this will be handled by BTUtility)

  3. Sending a file to a remote Bluetooth device using the Object Push Profile (this will be handled by FilePusher)

That takes care of the preliminaries, so let's get started!

ImageSender.FileNavigator

Figure 3, located below, is a sequence diagram showing the interactions between the ImageSender Midlet and its inner class, FileNavigator, which is used exclusively for reading and traversing the file system of a mobile device.

Figure 3: Sequence Diagram showing the usage of the FileNavigator inner class
Figure 3: Sequence Diagram showing the usage of the FileNavigator inner class
 

To start things off, you'll see that ImageSender obtains an instance of the FileNavigator and calls the method getListofFolder() which returns a javax.microedition.lcdui.List. FileNavigator will in turn use the FileSystemRegsitry class in the JSR-75 File Connection API in order to get an Enumeration of the "roots" of the filesystem, which are the mount points of your device. If your mobile device contains removable media (like an SD memory card), it will also show up in the Enumeration. For each root of the filesystem, a FileConnection is made on that item to determine if it's a file or a folder. This is necessary because you definitely want to handle them differently (i.e. if the item is a folder, then you want to traverse that folder, but if the item is a file, then you would want to open that file to get the contents). After the enumeration has been iterated over, the FileNavigator inner class will return a List to the ImageSender, which simply displays the List on the mobile device, as shown in Figure 4 below.

Figure 4: ImageSender showing a List of files and folders in a directory
Figure 4: ImageSender showing a List of files and folders in a directory (Click on image for larger view.)
 

Because the FileNavigator inner class implements the CommandListener interface, it will handle all requests from the user interface to change directories or to select a file. This approach allows parent class, ImageSender, to be free from the responsibility of responding to the user's input and knowing what to do with it. The inner class already has the reference to the JSR-75 classes that allow it to connect to the filesystem, so it's the best candidate for handling the user's requests and working with the filesystem. Located below is a portion of the commandAction() method of the FileNavigator; this section of code is executed when a user selects an item in the List:

	    ...
                    if(isFolderSelected == true){
                        // the user selected a folder, so navigate down it
                        String folderUrl = (String)curr_dir_urls.elementAt(selectedIndex);
                        getDisplay().setCurrent(fileNavigator.getListofFolder(folderUrl, false));      
                    
                    } else {
                        getDisplay().setCurrent(get_fileSelectedAlert(), displayable);
                        
                        // the user has obviously selected a file, so let's read it in
                   
                        String file_url = (String)curr_dir_urls.elementAt(selectedIndex);

                        ...
                            fileConn = (FileConnection) Connector.open(file_url);
                            InputStream is = fileConn.openInputStream();
                            
                            // now let's read the file in into our byte[]
                            
                            file = new byte[(int)fileConn.fileSize()];
                            is.read(file);
                            is.close();
                            
		...
 

As you can see, if the user selects a folder, the FileNavigator will traverse the folder, return another list, and display it. However, if the user selects a file, the FileNavigator inner class will open a FileConnection to the file and read the contents in a byte[] named "file".

ImageSender.BTUtility

The second helper class employed by the ImageSender Midlet is BTUtility. As you can probably guess, the BTUtility inner class encapsulates all the JSR-82 Bluetooth API method calls from the rest of the code. The BTUtility provides two major areas of functionality to the ImageSender Midlet: It discovers remote Bluetooth devices in the vicinity and it performs a service search on those devices. Figure 5 is a sequence diagram showing how the ImageSender uses the BTUtility.

Figure 5: A sequence diagram depicting the interaction between ImageSender and BTUtility.
Figure 5: A sequence diagram depicting the interaction between ImageSender and BTUtility.
 

As shown above, after the ImageSender gets a new instance of the BTUtility, which in turn obtains a reference to the LocalDevice and DiscoveryAgent classes. In order to find Bluetooth devices in the vicinity, you need to call DiscoveryAgent.startInquiry(). The implementation will asynchronously call your deviceDiscovered() method for each remote Bluetooth device found in the area. Finally, when no more Bluetooth devices can be found, the JVM will call your inquiryCompleted() method. Figure 6, shows a list of Bluetooth enabled devices from the BTUtility class.

Figure 6: Bluetooth devices found by the BTUtility class
Figure 6: Bluetooth devices found by the BTUtility class
 

The other job that BTUtility performs is that searches for services on remote Bluetooth devices. As shown in Figure 7 below, the process of searching for services is more CPU intensive that discovering devices. That's the reason why the device discovery process could be initiated from the constructor of BTUtility, but the service searching portion must be started in a Thread.

Figure 7: Using the BTUtility to search for services on remote Bluetooth devices.
Figure 7: Using the BTUtility to search for services on remote Bluetooth devices.
 

Fortunately, BTUtility extends the Thread class, so that will prevent the user interface from hanging. When BTUtility calls DiscoveryAgent.searchServices(), the JVM will asynchronously call his serviceDiscovered() method when a matching service is found. When the service search process is completed, the JVM will call its serviceSearchCompleted() method. Alternately, we could have called DiscoveryAgent.selectService(), but according to the JSR-82 specification, that would have returned the connectionURL of only one service provider in the vicinity. As you can recall from Figure 6 above, in most circumstances you'd want to know who you're going to send the file to. Below is a listing of the BTUtility in its entirety:

    /**
     * This is an inner class that is used for finding
     * Bluetooth devices in the vicinity
     *
     */
    class BTUtility extends Thread implements DiscoveryListener {
        
        Vector remoteDevices = new Vector();
        Vector deviceNames = new Vector();
        
        DiscoveryAgent discoveryAgent;
        
        // obviously, 0x1105 is the UUID for
        // the Object Push Profile
        UUID[] uuidSet = {new UUID(0x1105) };
        
        // 0x0100 is the attrubute for the service name element
        // in the service record
        int[] attrSet = {0x0100};
        
        
        
        public void run(){
            
            try {
                RemoteDevice remoteDevice = (RemoteDevice)remoteDevices.elementAt(get_devicesList().getSelectedIndex());
                discoveryAgent.searchServices(attrSet, uuidSet, remoteDevice , this);
                
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
        
        public BTUtility() {
            
            // clear the list out, just in case it's not
            get_devicesList().deleteAll();
            
            try {
                LocalDevice localDevice = LocalDevice.getLocalDevice();
                discoveryAgent = localDevice.getDiscoveryAgent();
                //deviceDiscoveryPanel.updateStatus(" Searching for Bluetooth devices in the vicinity...\n");
                discoveryAgent.startInquiry(DiscoveryAgent.GIAC, this);
                
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
        
        
        
        public void deviceDiscovered(RemoteDevice remoteDevice, DeviceClass cod) {
            
            try{
                remoteDevices.addElement(remoteDevice);
                
            } catch(Exception e){
                e.printStackTrace();
            }
            
        }
        
        
        public void inquiryCompleted(int discType) {
            
            
            if (remoteDevices.size() > 0) {
                
                // the discovery process was a success
                // so let's out them in a List and display it to the user
                for (int i=0; i
 

In the BTUtility inner class, you may notice that we're using a few hexadecimal values here and there. In particular:

        UUID[] uuidSet = {new UUID(0x1105) };
        
        // 0x0100 is the attribute for the service name element
        // in the service record
        int[] attrSet = {0x0100};
 

Now, if you remember the values in Table 1, above, then you can understand why we created a UUID value of 0x1105, since that's the UUID for the Object Push Profile. However, we're also using the value of 0x0100 in the attrSet, which allows us to know the service name of the remote service.

ImageSender.FilePusher

So what do have so far? Well, first of all, we have a Midlet that can browse a filesystem using the JSR-75 FileConnection API (this of course, is all handled by our inner class, FileNavigator). This Midlet also has the capability of discovering remote Bluetooth devices in the vicinity and determining the services available on those devices (that capability is graciously provided to us by another inner class, BTUtility). So, all we need now is a mechanism for sending files to remote Bluetooth devices using OBEX.

FilePusher accomplishes this task very easily by using the org.netbeans.microedition.lcdui.WaitScreen (depicted in Figure 8) and implementing the org.netbeans.microedition.util.CancellableTask which extends Runnable.

Figure 8: A snapshot of the NetBeans flow design using the WaitScreen
Figure 8: A snapshot of the NetBeans flow design using the WaitScreen
 

As illustrated above, the WaitScreen will execute CPU-intensive tasks (such as performing network I/O). That task must implement the CancellableTask interface (which is exactly what the FilePusher inner class does). Using the IDE, you can graphically connect to a success or failure screen depending upon the outcome of your operation. The code for the FilePusher is shown in the Listing below:

    class FilePusher implements CancellableTask{
        
        // this is used for the purposes of the Cancellable task
        boolean isOperationFailed = false;
        
        // this is the failure message used by the Cancellable task
        String failure_message = null;
        
        // this is the connection object to be used for
        //  bluetooth i/o
        Connection connection = null;
        
        public FilePusher(){
            
        }
        
        // this is method 1 of 4 methods needed to be implemented by
        // the CancellableTask interface
        public boolean cancel(){
            // sorry this can't be cancelled
            return false;
        }
        
        // this is method 2 of 4 needed to be implemented by
        // the CancellableTask interface
        public String getFailureMessage(){
            // of course, if there's no problem
            // then this method should return null as specified by the javadoc
            return failure_message;
        }
        
        // this is method 3 of 4 needed to be implemented by
        // the CancellableTask interface
        public boolean hasFailed(){
            return isOperationFailed;
        }
 
        // this is method 4 of 4 needed to be implemented by
        // the CancellableTask interface       
        public void run(){
            
            try{
                connection = Connector.open(btConnectionURL);
                // connection obtained
                
                // now, let's create a session and a headerset objects
                ClientSession cs = (ClientSession)connection;
                HeaderSet hs = cs.createHeaderSet();
                
                // now let's send the connect header
                cs.connect(hs);
                
                hs.setHeader(HeaderSet.NAME, filename);
                hs.setHeader(HeaderSet.TYPE, "image/jpeg"); 
                hs.setHeader(HeaderSet.LENGTH, new Long(file.length));
                
                Operation putOperation = cs.put(hs);
                
                OutputStream outputStream = putOperation.openOutputStream();
                outputStream.write(file);
                // file push complete
                
                outputStream.close();
                putOperation.close();
                
                cs.disconnect(null);
                
                connection.close();
            } catch (Exception e){
                isOperationFailed = true;
                failure_message = e.toString();
            }
            
            
        }
        
        
    }  
 

Now let's delve into the depths of the OBEX protocol by scrutinizing the run() method of the ObjectPusher inner class. The best way to understand the OBEX protocol is to look at the interaction of an OBEX client and server as depicted in Figure 9, below:

Figure 9: An example of client and server interaction using the OBEX protocol
Figure 9: An example of client and server interaction using the OBEX protocol
 

The scenario depicted in Figure 9 will take place after the physical Bluetooth connection between the client and server are established. In FilePusher, this is accomplished with the following line of code:

                connection = Connector.open(btConnectionURL);
                // connection obtained
 

Now that the connection has been established, it's time to create a session between the client and the server by casting the connection object into a ClientSession object:

                // now, let's create a session and a headerset object
                ClientSession cs = (ClientSession)connection;
 

Now, with a session established, take another look at Figure 9. Be sure to observe that clients send OBEX Operations (like Connect, Setpath, Get, Push, etc.) and servers will respond to clients with OBEX Response Codes (160, 161, 193, 196, etc.). The code below demonstrates how to send the Connect operation:

                // now let's send the connect operation
                cs.connect(hs);
 

As you can observe from the following code snippet, OBEX is the preferred means for file transfers over Bluetooth (as compared to RFCOMM or L2CAP) because you can set metadata about the file that you want to transfer using headers:

                hs.setHeader(HeaderSet.NAME, filename);
                hs.setHeader(HeaderSet.TYPE, "image/jpeg"); 
                hs.setHeader(HeaderSet.LENGTH, new Long(file.length));
 

This allows the receiver to know the name, file type, and size of the file before accepting the file -- a very powerful capability the other protocol layers in the Bluetooth stack don't support. Now, after the appropriate headers have been set, we'll send the Put operation to the server:

		Operation putOperation = cs.put(hs);
 

When the method returns, you'll have an Operation object where you can open an OutputStream and send the file data stored in the byte[] named "file" (if you remember, we populated "file" from the FileNavigator inner class). Now if you'd like to know what the response code was from the server, optionally call the getResponseCodes() method on your instantiated Operation object.

So, after we've sent our file to its destination using the OutputStreams that we've opened from the putOperation, we send the final OBEX Operation to wrap everything up: the Disconnect Operation.

		cs.disconnect(null);
 
Conclusion

As you can see, it didn't take a lot of effort to create a composite application that uses both the JSR-82 and JSR-75 APIs for wireless image transfer. Although this article set out to show you how to transfer images over OBEX, you probably realized that the code can be easily modified to send out file types. Enjoy!

Acknowledgements

I would like to thank Uri Katz from Sun Microsystems for helping me improve this article.

About the Author

Bruce Hopkins is the author of the book, Bluetooth for Java (by Apress Publishers) 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.

Rate and Review
Tell us what you think of the content of this page.
Excellent   Good   Fair   Poor  
Comments:
Your email address (no reply is possible without an address):
Sun Privacy Policy

Note: We are not able to respond to all submitted comments.