Creating the OBEX communication classes

ExchangeListener

The ExchangeListener is a callback interface. By means of the interface methods, the OBEX communication module signals to a listener about various networking events. This interface is implemented by the BCExchangerMIDlet

To create the class:

  1. Create the ExchangeListener class file.

  2. Assign the class to the Comm package and import the required classes.

    package bcexchanger.comm;
    
    import java.util.Vector;
  3. Implement the ExchangeListener class functionality.

    
    /**
     * 
     * ExchangeListner is an callback interface. By means of the interface
     * function OBEX communication module signal to a listener about
     * various networking events.
     * 
     * This interface is implemented by the BCExchangerMIDlet and OBEX
     * communication classes located in the package bcexchanger.comm use
     * it for signaling
     * 
     * @version 1.0 27.09.2005
     */
    public interface ExchangeListener {
    
      /**
       * Called when the OBEX communication module completes the inquiry
       * 
       * @param code -
       *          inquiry completion code
       */
      public void onInquiryComplete(int code);
    
      /**
       * Called when the OBEX communication module completes service
       * discoveries
       * <p>
       * This method is called when service discovery on all devices is
       * complete
       * 
       * @param
       * @param code -
       *          service discovery completion code
       */
      public void onServiceDiscoveryComplete(int code);
    
      /**
       * This method is called when OBEX communication module needs to
       * resolve services
       * <p>
       * If several services are found, the OBEX communcation module asks
       * the MIDlet to resolve the services and choose the only one to
       * connect to
       * 
       * @param friendlyNames -
       *          list of friendly names of devices which has the service
       * @return index of the chosen device/service from the vector
       * @throws InterruptedException
       */
      public int resolveMultipleServices(Vector friendlyNames) throws InterruptedException;
    
      /**
       * This method returns own business card serialized to vCard/2.1
       * format
       * <p>
       * When OBEX module needs to send the own business card to a remote
       * device it uses this method to request own business card from the
       * MIDlet
       * 
       * @return byte array containing own business card serialized to
       *         vCard/2.1 format null if there were errors or cancelation
       * @throws Exception
       */
      public byte[] getOwnBC() throws Exception;
    
      /**
       * Called when business card send (Client OBEX PUT) is finished
       * 
       * @param code -
       *          completion code
       */
      public void onSendComplete(int code);
    
      /**
       * Called when business card receive (Client OBEX GET) is finished
       * 
       * @param code -
       *          completion code
       */
      public void onReceiveComplete(int code);
    
      /**
       * Called when Server OBEX GET operation is finished
       * 
       * @param code -
       *          completion code
       */
      public void onGetComplete(int code);
    
      /**
       * Called when Server OBEX PUT operation is finished
       * 
       * @param code -
       *          completion code
       */
      public void onPutComplete(int code);
    
      /**
       * Called when a business card from a remote device is received
       * <p>
       * This method allows MIDlet to save received business card.
       * 
       * @param vCard -
       *          byte array containing receive business card serialized
       *          to vCard/2.1 format
       * @throws Exception -
       *           if saving of the business card fails
       */
      public void onReceiveBC(byte[] vCard) throws Exception;
    
      /**
       * Called in case of problems with Bluetooth OBEX server accepting connection
       * 
       */
      public void onServerError();
        
      
    }

ExchangerComm class

This interface contains methods representing the functionality of the OBEX communication module to other classes of the application. The OBEX communication module implemented by the classes from the bcexchanger.comm package realizes the exchange of the business card over OBEX API (JSR-82). The control of the module is abstracted in this interface.

The BCExchangerMIDlet calls the methods of this interface implemented by the ExchangerCommImpl class to control OBEX communication.

To create the class:

  1. Create the ExchangerComm class file.

  2. Assign the class to the Comm package.

    package bcexchanger.comm;
    
  3. Implement the interface.

    /**
     * 
     * This is an interface to OBEX communication module. OBEX
     * communication module implemented by the classes from
     * bcexchanger.comm package realize exhange of the business card over
     * JSR82 OBEX. The controls of the module is abstracted in this
     * interface.
     * 
     * BCExchanger is calling the methods of this interface implemented by
     * ExchangerCommImpl class to control OBEX communication.
     * 
     * @version 1.0 27.09.2005
     * 
     */
    public interface ExchangerComm {
    
      // Constant field declaration
      static final int DONE = 0;
      static final int CANCELED = 1;
      static final int ERROR = 2;
      static final int NO_RECORDS = 3;
    
  4. Initiate the process of sending a business card. Set methods that start and stop listening for incoming connections.

    
      /**
       * Intitiate whole procedure of sending business card to a remote
       * device
       * <p>
       * The method start the procedure of inquiry, service discovery,
       * tranfering own card and receiving the card from the remote
       * device. This method is asynchrounous and it returns immediately
       * after operation is started.
       * 
       * @exception Exception -
       *              if any immediate errors occur while the process is
       *              started. In practice it is thrown when inquiry
       *              cannot start or if this method is called while
       *              sending process is in progress
       */
      public void startSending() throws Exception;
    
      /**
       * Cancels the process of sending
       * <p>
       * The method cancels the current operation of the sending: inquiry,
       * service discovery, OBEX send or receive and returns to the idle
       * state.
       * 
       */
      public void cancelSending();
    
      /**
       * Starts to listen for incomming connections
       * <p>
       * The method start listening for incomming connections. This method
       * is asynchronous and it returns immediately after the connection
       * listening is started.
       * 
       */
      public void startWaiting();
    
      /**
       * This method stops listening for incomming connection
       * <p>
       * The method stops listening for incomming connection if
       * startWaiting() was called before. Otherwise method does nothing.
       * 
       */
      public void cancelWaiting();
    
    }
    

ExchangerCommImpl class

This class is the central class in the OBEX communication module (package bcexchanger.comm). The class implements the interface ExchangerComm and realizes the methods for controlling the process of sending and receiving business cards. It is also a parent for states of the communication state machine. It keeps the current state of the state machine and implements the interface ExchangerStateParent. Using this interface, state classes can access the required functionality of the parent.

This class waits for an incoming Bluetooth connection in a separate thread and therefore implements the Runnable interface. The class also works as an OBEX server. In order to serve OBEX requests, it extends the ServerRequestHandler class and overrides some of its methods.

To create the class:

  1. Create the ExchangerCommImpl class file.

  2. Assign the class to the Comm package and import the required classes.

    package bcexchanger.comm;
    
    import java.io.IOException;
    
    import javax.bluetooth.DiscoveryAgent;
    import javax.bluetooth.LocalDevice;
    import javax.microedition.io.Connection;
    import javax.microedition.io.Connector;
    import javax.obex.HeaderSet;
    import javax.obex.Operation;
    import javax.obex.ServerRequestHandler;
    import javax.obex.SessionNotifier;
  3. Set the class to extend the ServerRequestHandler class of the javax.bluetooth package in order to serve OBEX requests and override some of its methods.

    In order to create a server connection, set instance variables for this purpose. This is done by identifying the btgoep protocol, the server URL, and the service UUID. This will be used later in this class when the Connector.open() method is called.

    
    /**
     * 
     * This class is the central class in the OBEX communication module
     * (package bcexchanger.comm).
     * 
     * Class implements interface ExchangerComm and realizes the methods
     * for controling process of sending and receiving business cards. It
     * also is a parent for states of the BCExchanger communication state
     * machine. It keeps the current state of the state machine and
     * implements interface ExchangerStateParent. Using this interface
     * state classes access to the required functionality of the parent.
     * 
     * This class waits for Bluetooth incomming connection in the separate
     * thread and therefore it implments Runnable interface.
     * 
     * This class also works as a OBEX server. In order to serve OBEX
     * requests it extends the ServerRequestHandler class and overides
     * some of its methods.
     * 
     * @version 1.0 29.09.2005
     * @see bcexchanger.comm.ExchangerStateParent
     *      bcexchanger.comm.ExchangerComm javax.obex.ServerRequestHandler
     *      Design patterns: State
     * 
     */
    public class ExchangerCommImpl extends ServerRequestHandler
        implements ExchangerStateParent, ExchangerComm, Runnable {
    
      // Instance variables
      final private String uuid = "ed495afe28ed11da94d900e08161165f";
      final private String serverURL = "btgoep://localhost:" + uuid;
    
      private boolean cancelWaitingInvoked = false; // becomes true if
                                                    // cancelWaiting() is
                                                    // called
    
      private Thread waitingThread;
    
      private ExchangeListener listener;
      private ExchangerState currentState;
    
      private SessionNotifier notifier = null;
      private Connection con = null;
    
  4. Set a listener for the module event communication.

    
      /**
       * Constructor
       * <p>
       * description
       * 
       * @param _listener -
       *          listener of the communication module events
       * @exception
       * @see
       */
      public ExchangerCommImpl(ExchangeListener _listener) 
      {
    	listener = _listener;
    
        startWaiting();
        setState(new IdleState(this));
      }
    
      public void setState(ExchangerState state) {
    	  
        currentState = state;
      }
    
      public ExchangeListener getListener() {
        return listener;
      }
    
      public ExchangerState getState() {
        return currentState;
      }
    
      public void startSending(int oper) throws Exception {
        getState().startSending(oper);
      }
      
      public void startSending() throws Exception {
    	    getState().startSending(0);
    	  }
      
    
      public void cancelSending()
      {
        getState().cancelSending();
      }
    
      public void startWaiting() {
        cancelWaitingInvoked = false;
    
        waitingThread = new Thread(this);
        waitingThread.start();
      }
    
      public void cancelWaiting() 
      {
        cancelWaitingInvoked = true;
    
        try 
        {
          notifier.close(); // indicate to acceptAndOpen that it is
                            // canceled
        } catch (IOException e) {
          // Ignore, we're closing anyways
        }
      }
    
  5. Use the run method to initialize the stack. Make the device discoverable by using the setDiscoverable() method of the LocalDevice class in the javax.bluetooth package.

    Call the Connector.open() method to return a SessionNotifier object. Use the method acceptAndOpen() of the SessionNotifier interface to start waiting for incoming transport layer connections.

    After the transport layer connection is established, call the acceptAndOpen() method to return a Connection object, which represents the connection to a single client.

    The server communicates with the remote peer by two means: the Connection object and the ServerRequestHandler callback methods that are called on incoming OBEX requests.

    
      public synchronized void run() {
    
      	// initialize stack and make the device discoverable
    	try 
    	{
          LocalDevice local = LocalDevice.getLocalDevice();
    	  local.setDiscoverable(DiscoveryAgent.GIAC); 
    	} catch (Exception e) 
    	{
    	  // catching notifier exception
    	  listener.onServerError();
    	  return;
    	}
    	try 
    	{
    		notifier = (SessionNotifier) Connector.open(serverURL);
    	} 
    	catch (IOException e2) 
    	{
    		
    	}
        // the cycle stops only if cancelWaiting() was called
        while (!cancelWaitingInvoked) 
        {
          try 
          {      
    
            try
            {
              con = notifier.acceptAndOpen(this);
              wait(); // wait until the remote peer disconnects
              try 
              {
            	con.close();
              } 
              catch (IOException e0) 
              { }
            } 
            catch (Exception e1) 
            {
            	listener.onServerError();
            	return;
            }
          } 
          catch (Exception e) 
          {
            listener.onServerError();
            return;
          }
    
        }
    
      }
  6. Implement the OBEX server related methods.

    The ServerRequestHandler class methods onGet() and onPut() are called when a GET or PUT method is received. These methods receive an Operation object as a parameter. The application uses the Operation object to read and write data. After the request is handled, the method must return a response code. To see all the available response codes, see the ResponseCodes class.

    
      /*
       * This method is related to OBEX server functionality. This method
       * is delegating this execution to the current state
       * 
       * @see javax.obex.ServerRequestHandler#onGet()
       */
      public int onGet(Operation op) {
        return getState().onGet(op);
      }
    
      /*
       * This method is related to OBEX server functionality. This method
       * is delegating this execution to the current state
       * 
       * @see javax.obex.ServerRequestHandler#onPut()
       */
      public int onPut(Operation op) {
        return getState().onPut(op);
    
      }
    
      /*
       * This method is related to OBEX server functionality. This method
       * handles OBEX DISCONNECT command from the remote device.
       * 
       * @see javax.obex.ServerRequestHandler#onDisconnect()
       */
      public synchronized void onDisconnect(HeaderSet request,
          HeaderSet reply) {
        super.onDisconnect(request, reply);
        notify();// stops waiting in run()
    
      }
    
      public String getUUID() {
        return uuid;
      }
    
    }
    

ExchangerState class

This is the abstract base class for all the states of the communication state machine. Each state implements only those methods which can be executed in that particular case.

Classes IdleState, InquiryState, ServiceDiscoveryState, ReceiveState, and SendState inherit from this class.

To create the class:

  1. Create the ExchangerState class file.

  2. Assign the class to the Comm package and import the required class.

    package bcexchanger.comm;
    
    import javax.obex.Operation;
  3. Implement the class. Implement the way the server handles the OBEX GET and PUT methods.

    /**
     * 
     * This is the abstract base class for the all states of the
     * communication state machine. Each state is implementing methods
     * which are can be executed in that particular case.
     * 
     * Classes IdleState, InquiryState, ServiceDiscoveryState,
     * ReceiveState and SendState inherit from this class
     * 
     * @see Design patterns: State
     * 
     */
    public abstract class ExchangerState {
    
      protected ExchangerStateParent parent;
    
      /**
       * Implements OBEX GET command
       * <p>
       * Implements server handling of the OBEX GET command
       * 
       * @param op -
       *          OBEX operation class
       * @return - OBEX response code
       * @see javax.obex.ServerRequestHandler
       */
      abstract public int onGet(Operation op);
    
      /**
       * Implements OBEX PUT command
       * <p>
       * Implements server handling of the OBEX PUT command
       * 
       * @param op -
       *          OBEX operation class
       * @return - OBEX response code
       * @see javax.obex.ServerRequestHandler
       */
      abstract public int onPut(Operation op);
    
      /**
       * Implements actions related to starting sending process
       * <p>
       * Implements reaction of the state on command to start sending
       * process
       * 
       * @exception Exception -
       *              if any immediate error occur
       * @see example.BCExchanger.comm.ExchangerComm
       */
      abstract public void startSending(int oper) throws Exception;
    
      /**
       * Implements actions related to canceling sending process
       * <p>
       * Implements reaction of the state on command to cancel sending
       * process
       * 
       * @see example.BCExchanger.comm.ExchangerComm
       */
      abstract public void cancelSending();
    
      /**
       * Constructor
       * 
       * @param -
       *          _parent - the class which nests the current state of the
       *          communication machine
       */
      public ExchangerState(ExchangerStateParent _parent) {
        parent = _parent;
      }
    
    }
    

ExchangerStateParent class

This interface contains the function used by the state to address the functionality of the parent class ExchangerCommImpl. It contains the methods which the states of the communication state machine use to get certain functionality from the parent.

This interface is implemented by the ExchangerCommImpl class.

To create the class:

  1. Create the ExchangerStateParent class file.

  2. Assign the class to the Comm package.

    package bcexchanger.comm;
    
  3. Implement the methods to be used by the communication state machine.

    /**
     * 
     * This interface contains the methods which the states of the
     * communication state machine use to get needed functionality from
     * the parent
     * 
     * This interface is implemented by ExchangerCommImpl class.
     * 
     * @version 1.0 29.09.2005
     * 
     */
    public interface ExchangerStateParent {
    
      /**
       * Current state setter
       * <p>
       * Sets the current state in the parent
       * 
       * @param state -
       *          state of communication machine
       */
      public void setState(ExchangerState state);
    
      /**
       * Listener getter
       * <p>
       * Returns the communication event listener which is stored in the
       * parent
       * 
       * @return communication event listener interface
       */
      public ExchangeListener getListener();
    
      /**
       * Communication state getter
       * <p>
       * Returns current state of the communication machine which is kept
       * in the parent
       * 
       * @return current state of the communication machine
       */
      public ExchangerState getState();
    
      /**
       * Returns "Business Card Exchanger Service" UUID
       * 
       * @return string containing UUID of the Bluetooth OBEX server
       *         connection
       */
      public String getUUID();
    
    }
    
    

IdleState class

This class implements the idle state of the communication state machine. In idle state, the machine waits for external events, such as incoming Bluetooth connections or a user command to start business card exchange. The IdleState class extends the ExchangerState abstract class.

To create the class:

  1. Create the IdleState class file.

  2. Assign the class to the Comm package and import the required classes.

    package bcexchanger.comm;
    
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.DataInputStream;
    import java.io.InputStream;
    import java.io.OutputStream;
    import javax.obex.Operation;
    import javax.obex.ResponseCodes;
  3. Implement the idle state. Set up a listener for external events.

    The ServerRequestHandler class methods onGet() and onPut() are called when a GET or PUT method is received. These methods receive an Operation object as a parameter. The application uses the Operation object to read and write data. After the request is handled, the method must return a response code. All response codes are defined in the ResponseCodes class.

    
    /**
     * 
     * This class implements idle state of the communication state machine. In idle
     * state machine is waiting for external events (like incomming Bluetooth
     * connections or user command to start business card exchange)
     * 
     * This class extends ExchangerState abstract class.
     * 
     * @version 1.0 29.09.2005
     * @see bcexchanger.comm.ExchangerState Design Patterns: State
     * 
     */
    public class IdleState extends ExchangerState {
    	/**
    	 * Constructor
    	 * 
    	 * @param _parent -
    	 *            the class which is nesting the current state of the state
    	 *            machine
    	 */
    	public IdleState(ExchangerStateParent _parent) {
    		super(_parent);
    
    	}
    
    	public int onGet(Operation op) {
    
    		try {
    
    			OutputStream out = op.openOutputStream();
    			byte[] vCard = parent.getListener().getOwnBC(); // getting the
    			// own card
    
    			int vlen = vCard.length;
    			byte[] tmpBuf = new byte[vlen + 4];
    			System.arraycopy(vCard, 0, tmpBuf, 4, vlen);
    			tmpBuf[0] = (byte) ((vlen >>> 24) & 0xff);
    			tmpBuf[1] = (byte) ((vlen >>> 16) & 0xff);
    			tmpBuf[2] = (byte) ((vlen >>> 8) & 0xff);
    			tmpBuf[3] = (byte) ((vlen >>> 0) & 0xff);
    
    			out.write(tmpBuf); // sending data
    
    			op.close();
    
    			return ResponseCodes.OBEX_HTTP_OK;
    
    		} catch (Exception e) {
    
    			return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    		}
    
    	}
    
    	public int onPut(Operation op) {
    
    		try 
    		{
    			InputStream in = op.openInputStream();
    			byte[] fullResult = null;
    			{
    				byte[] buf = new byte[256];
    				ByteArrayOutputStream bout = new ByteArrayOutputStream(2048);
    				for (int len = in.read(buf); len >= 0; len = in.read(buf))
    					bout.write(buf, 0, len);
    				fullResult = bout.toByteArray();
    			}
    			ByteArrayInputStream bin = new ByteArrayInputStream(fullResult);
    			DataInputStream din = new DataInputStream(bin);
    			int size = din.readInt();
    			byte[] vCard = new byte[size];
    			din.read(vCard);
    			// card is received
    
    			op.close();
    			parent.getListener().onReceiveBC(vCard);
    
    			return ResponseCodes.OBEX_HTTP_OK;
    
    		} 
    		catch (Exception e) {
    
    			return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    		}
    
    	}
    
    	public void startSending(int oper) throws Exception {
    		parent.setState(new InquiryState(parent, oper));
    
    	}
    
    	public void cancelSending() {
    		// Internal error, but not fatal
    	}
    
    }
    

InquiryState class

This class represents the state of running inquiry. In this state an active device discovery is performed. No OBEX server commands are handled. This class extends the ExchangerState abstract class. This class also implements the DiscoveryListener interface to receive Bluetooth inquiry callbacks.

To create the class:

  1. Create the InquiryState class file.

  2. Assign the class to the Comm package and import the required classes.

    package bcexchanger.comm;
    
    import java.io.IOException;
    import java.util.Vector;
    
    import javax.bluetooth.DeviceClass;
    import javax.bluetooth.DiscoveryAgent;
    import javax.bluetooth.DiscoveryListener;
    import javax.bluetooth.LocalDevice;
    import javax.bluetooth.RemoteDevice;
    import javax.bluetooth.ServiceRecord;
    import javax.obex.Operation;
    import javax.obex.ResponseCodes;
  3. Set the class to extend ExchangerState and to implement DiscoveryListener and implement the constructor.

    Use the method startInquiry() to start an inquiry for devices. Devices that respond to the inquiry are returned to the application via the method deviceDiscovered() of the interface DiscoveryListener. Call the cancelInquiry() method to stop the inquiry.

    
    /**
     * 
     * This class implements inquiry state. In inquiry state an active
     * device discovery is peformed. No OBEX server commands are served.
     * 
     * This class extends ExchangerState abstract class. This class also
     * implments DiscoveryListener interface to receive Bluetooth inquiry
     * callbacks.
     * 
     * @version 1.0 29.09.2005
     * @see bcexchanger.comm.ExchangerState
     *      javax.bluetooth.DiscoveryListener Design Patterns: State
     * 
     */
    public class InquiryState extends ExchangerState
        implements DiscoveryListener {
    
      private DiscoveryAgent agent;
      private Vector remoteDevices; // vector of found devices
      private int operation = ServiceDiscoveryState.GET;
      
      /**
       * Constructor
       * 
       * @param _parent -
       *          the class which is nesting the current state of the
       *          state machine
       */
      public InquiryState(ExchangerStateParent _parent,int oper)
          throws IOException {
        super(_parent);
        operation = oper;
        remoteDevices = new Vector();
        // initiate Bluetooth
        LocalDevice local = LocalDevice.getLocalDevice();
        agent = local.getDiscoveryAgent();
        // start Bluetooth inquiry
        agent.startInquiry(DiscoveryAgent.GIAC, this);
    
      }
    
      /*
       * Inquiry state does not allow to start any other business card
       * exchange process.
       * 
       * @see bcexchanger.comm.ExchangerState#startSending()
       */
      public void startSending(int op) throws IOException {
        throw new IOException(
            "Inquiry is in progress. Inquiry has to be canceled before starting new sending process");
      }
    
      /*
       * InquiryState allows to cancel inquiry process.
       * 
       * @see bcexchanger.comm.ExchangerState#cancelSending()
       */
      public void cancelSending() {
        agent.cancelInquiry(this);
      }
    
  4. The method deviceDiscovered() is called when a device is found during an inquiry.

    The method inquiryCompleted() is called when the inquiry is completed. After that, the inquiry completion codes are converted into application completion codes. For more information on inquiry completion, see the interface DiscoveryListener.

    
      public void deviceDiscovered(RemoteDevice dev, DeviceClass devClass) 
      {
        remoteDevices.addElement(dev);
      }
    
      public void inquiryCompleted(int code) {
        try 
        {
        	int completionCode = ExchangerComm.ERROR;
    
          // convert the inquiry completion code to application completion
          // code
          switch (code) {
            case DiscoveryListener.INQUIRY_COMPLETED:
              completionCode = ExchangerComm.DONE;
              break;
            case DiscoveryListener.INQUIRY_TERMINATED:
              completionCode = ExchangerComm.CANCELED;
              break;
            case DiscoveryListener.INQUIRY_ERROR:
              completionCode = ExchangerComm.ERROR;
              break;
          }
          parent.getListener().onInquiryComplete(completionCode); // signal
                                                                  // that
                                                                  // inquiry
                                                                  // is
                                                                  // done
    
          if (code == DiscoveryListener.INQUIRY_COMPLETED) { // no errors
        	                                                 // or
        	                                                 // cancelations
            parent.setState(new ServiceDiscoveryState(parent,
                remoteDevices,operation));
    
          } else {
            parent.setState(new IdleState(parent));
          }
    
        } catch (Exception e) {
        	parent.setState(new IdleState(parent));
        }
      }
    
      /*
       * Service discovery callbacks are not handled and not supposed to
       * occur, since service discovery process is not started
       * 
       * @see javax.bluetooth.DiscoveryListener#servicesDiscovered(int,
       *      javax.bluetooth.ServiceRecord[])
       */
      public void servicesDiscovered(int arg0, ServiceRecord[] arg1) {
        throw new RuntimeException(
            "Internal error #4: InquiryState.servicesDiscovered() should not be called");
    
      }
    
      /*
       * Service discovery callbacks are not handled and not supposed to
       * occur, since service discovery process is not started
       * 
       * @see javax.bluetooth.DiscoveryListener#serviceSearchCompleted(int,
       *      int)
       */
      public void serviceSearchCompleted(int arg0, int arg1) {
        throw new RuntimeException(
            "Internal error #5: InquiryState.serviceSearchCompleted() should not be called");
    
      }
    
    
  5. The ServerRequestHandler class methods onGet() and onPut() are called when a GET or PUT method is received. These methods receive an Operation object as a parameter. The application uses the Operation object to read and write data. After the request is handled, the method must return a response code. All response codes are defined in the ResponseCodes class.

      /*
       * Serving OBEX GET operation is supported only in IdleState
       * 
       * @see bcexchanger.comm.ExchangerState#onGet(javax.obex.Operation)
       */
      public int onGet(Operation op) {
    
        return ResponseCodes.OBEX_HTTP_CONFLICT;
      }
    
      /*
       * Serving OBEX GET operation is supported only in
       * IdleState
       * 
       * @see bcexchanger.comm.ExchangerState#onPut(javax.obex.Operation)
       */
      public int onPut(Operation op) {
        // onPut is supported only in IdleState
        return ResponseCodes.OBEX_HTTP_CONFLICT;
      }
    
    }
    

ServiceDiscoveryState class

This class represents the state of the running service discovery. In this state an active service discovery is performed and no OBEX server commands are handled.

This class extends the ExchangerState abstract class. It also implements the DiscoveryListener interface to receive Bluetooth service discovery callbacks. Service discovery procedures are run from a separate thread and therefore the class implements the Runnable interface.

To create the class:

  1. Create the InquiryState class file.

  2. Assign the class to the Comm package and import the required classes.

    package bcexchanger.comm;
    
    import java.io.IOException;
    import java.util.Enumeration;
    import java.util.Vector;
    
    import javax.bluetooth.DeviceClass;
    import javax.bluetooth.DiscoveryAgent;
    import javax.bluetooth.DiscoveryListener;
    import javax.bluetooth.LocalDevice;
    import javax.bluetooth.RemoteDevice;
    import javax.bluetooth.ServiceRecord;
    import javax.bluetooth.UUID;
    import javax.obex.Operation;
    import javax.obex.ResponseCodes;
  3. Set the class to extend ExchangerState and to implement DiscoveryListener and Runnable. Set up instance variables.

    Initiate a separate thread for starting service discovery.

    
    /**
     * 
     * This class implements service discovery state. In this state an active
     * service discovery is peformed. No OBEX server commands are served.
     * 
     * This class extends ExchangerState abstract class. This class also implments
     * DiscoveryListener interface to receive Bluetooth service discovery callbacks.
     * Service discoveries are run from the separate thread therefore the class
     * implements Runnable interface
     * 
     * @version 1.0 29.09.2005
     * @see bcexchanger.comm.ExchangerState javax.bluetooth.DiscoveryListener Design
     *      Patterns: State
     * 
     */
    public class ServiceDiscoveryState extends ExchangerState implements
    		DiscoveryListener, Runnable {
    
    	// Instance variables
    	private Thread serviceDiscoveryThread;
    
    	private Vector services;
    
    	private DiscoveryAgent agent;
    
    	private int serviceDiscoveryID;
    
    	private Vector devices;
    
    	private boolean canceled = false;
    	
    	public static int GET = 0;
    	public static int PUT = 1;
    	
    	/**
    	 * Constructor
    	 * 
    	 * @param _parent -
    	 *            the class which is nesting the current state of the state
    	 *            machine
    	 * @param _devices -
    	 *            a vecotor of RemoteDevice object representing devices found
    	 *            during inquiry
    	 */
    	public ServiceDiscoveryState(ExchangerStateParent _parent, Vector _devices,int oper)
    			throws IOException {
    		super(_parent);
    		canceled = false;
    
    		services = new Vector();
    		devices = _devices;
    
    		// initiate Bluetooth
    		LocalDevice local = LocalDevice.getLocalDevice();
    		agent = local.getDiscoveryAgent();
    
    		serviceDiscoveryThread = new Thread(this);
    		serviceDiscoveryThread.start();
    
    	}
    
  4. Set the functionality for canceling the service discovery process and handling inquiry callbacks at this state.

    The method servicesDiscovered() is called when service(s) are found during a service search. The method serviceSearchCompleted() is called when a service search is completed or was terminated because of an error.

    
    	/*
    	 * ServiceDiscoveryState does not allow to start any other business card
    	 * exchange process.
    	 * 
    	 * @see bcexchanger.comm.ExchangerState#startSending()
    	 */
    	public void startSending(int op) throws Exception {
    		throw new IOException(
    				"Service discovery is in progress. Service discovery has to be canceled before starting new sending process");
    
    	}
    
    	/*
    	 * ServiceDiscoveryState allows to cancel discovery process.
    	 * 
    	 * @see bcexchanger.comm.ExchangerState#cancelSending()
    	 */
    	public void cancelSending() {
    
    		canceled = true;
    		agent.cancelServiceSearch(serviceDiscoveryID);
    
    	}
    
    	/*
    	 * 
    	 * Inquiry callbacks are not handled and not supposed to occur, since
    	 * inquiry process is not started
    	 * 
    	 * @see javax.bluetooth.DiscoveryListener#deviceDiscovered(javax.bluetooth.RemoteDevice,
    	 *      javax.bluetooth.DeviceClass)
    	 */
    	public void deviceDiscovered(RemoteDevice arg0, DeviceClass arg1) {
    
    		throw new RuntimeException(
    				"Internal error #8: ServiceDiscoveryState.deviceDiscovered() should not be called");
    	}
    
    	/*
    	 * 
    	 * Inquiry callbacks are not handled and not supposed to occur, since
    	 * inquiry process is not started
    	 * 
    	 * @see javax.bluetooth.DiscoveryListener#inquiryCompleted(int)
    	 */
    	public void inquiryCompleted(int arg0) {
    
    		throw new RuntimeException(
    				"Internal error #9: ServiceDiscoveryState.inquiryCompleted() should not be called");
    
    	}
    
    	public void servicesDiscovered(int id, ServiceRecord[] _services) {
    
    		for (int i = 0; i < _services.length; i++) {
    			services.addElement(_services[i]);
    		}
    
    	}
    
    	public synchronized void serviceSearchCompleted(int arg0, int arg1) {
    		notify();
    
    	}
    
  5. Use the Run() method to implement the logic of the service discovery process. Set the search for all available services unless the search is canceled. Use the method searchServices() to look for services on remote devices that have all the UUIDs specified in uuidSet. The method returns the transaction ID of the service search. For more information on UUIDs, see the class UUID.

    If one service is found, connect to that service. However, if more than one service is found, display a list of those services and let user choose the service to connect to. Set listeners for handling user actions.

    
    	/*
    	 * (non-Javadoc)
    	 * 
    	 * This methods implements logic of the service discovery process
    	 * 
    	 * @see java.lang.Runnable#run()
    	 */
    	public synchronized void run() {
    
    		Enumeration e = devices.elements();
    		// Business card exchanger service UUID
    		UUID[] uuids = new UUID[1];
    		uuids[0] = new UUID(parent.getUUID(), false);
    
    		// proceeded with all devices if not canceled
    		while (e.hasMoreElements() && !canceled) {
    
    			RemoteDevice device = (RemoteDevice) e.nextElement();
    			try {
    				serviceDiscoveryID = agent.searchServices(null, uuids, device,
    						this);
    			} catch (Exception e0) {
    				// signal error to the MIDlet
    				parent.getListener().onServiceDiscoveryComplete(
    						ExchangerComm.ERROR);
    				parent.setState(new IdleState(parent));
    				return;
    			}
    
    			try {
    				wait(); // wait until service search is done on this device
    			} catch (Exception e1) {
    				// Ignore
    			}
    		}
    
    		if (canceled) { // handle user's cancelation
    			try {
    				parent.getListener().onServiceDiscoveryComplete(
    						ExchangerComm.CANCELED);
    				parent.setState(new IdleState(parent));
    			} catch (Exception e1) {
    				throw new RuntimeException(
    						"Internal error #10: ServiceDicoveryState.run()");
    			}
    		} else 
    		{ // not canceled
    			if (services.isEmpty()) { // no services on devices
    				try {
    					parent.getListener().onServiceDiscoveryComplete(
    							ExchangerComm.NO_RECORDS);
    					parent.setState(new IdleState(parent));
    
    				} catch (Exception e1) {
    
    				}
    			} 
    			else if (services.size() == 1) 
    			{ // one service found,
    				// connect to it
    				try {
    					ServiceRecord serviceRecord = (ServiceRecord) services
    							.firstElement();
    					parent.getListener().onServiceDiscoveryComplete(
    							ExchangerComm.DONE);
    						SendState state = new SendState(parent, serviceRecord
    								.getConnectionURL(
    										ServiceRecord.NOAUTHENTICATE_NOENCRYPT,
    										false));
    						
    						parent.setState(state);
    						state.doSend();
    						state = null;
    						Thread.sleep(new Long(5000).longValue()); 
    
    						ReceiveState rstate = new ReceiveState(parent, serviceRecord
    								.getConnectionURL(
    										ServiceRecord.NOAUTHENTICATE_NOENCRYPT,
    										false));
    						parent.setState(rstate); 
    						rstate.doGet();
    						rstate = null;
    						Thread.sleep(new Long(5000).longValue()); 
    					
    				} 
    				catch (Exception e2) 
    				{
    				
    				}
    			} 
    			else 
    			{ // several services found, let user choose
    				try {
    					// list of friendly names of devices which contain services
    					Vector friendlyNames = createFriendlyNamesList(services);
    
    					int index = parent.getListener().resolveMultipleServices(
    							friendlyNames);
    
    					if (!canceled) 
    					{ // if not canceled during resolving
    						ServiceRecord serviceRecord = (ServiceRecord) services
    								.elementAt(index);
    
    						parent.getListener().onServiceDiscoveryComplete(
    								ExchangerComm.DONE);
    
    							SendState state = new SendState(parent, serviceRecord
    									.getConnectionURL(
    											ServiceRecord.NOAUTHENTICATE_NOENCRYPT,
    											false));
    							parent.setState(state);
    							state.doSend();
    							state = null;
    							Thread.sleep(new Long(5000).longValue()); 
    
    							ReceiveState rstate = new ReceiveState(parent, serviceRecord
    									.getConnectionURL(
    											ServiceRecord.NOAUTHENTICATE_NOENCRYPT,
    											false));
    							parent.setState(rstate);
    							rstate.doGet();
    							
    							rstate = null;
    							Thread.sleep(new Long(5000).longValue()); 
    
    					} 
    					else 
    					{ // if canceled during resolving
    						parent.getListener().onServiceDiscoveryComplete(
    								ExchangerComm.CANCELED);
    						parent.setState(new IdleState(parent));
    					}
    				
    					} 
    					catch (Exception e1) {
    					}
    
    			}
    
    		}
    	}
    
  6. Implement the method, used in the previous step, for creating a list of devices that contains the searched services. In case of an exception in getting the name of a device, use the method getBluetoothAddress() to retrieve the Bluetooth address of the device.

    
    	/*
    	 * This method creates a list of the friendly names of devices that contain
    	 * the servicies
    	 * 
    	 * @param _services - vector of ServiceRecord objects representing found
    	 * services @return - vectors of strings containing friendly names of
    	 * devices
    	 */
    	private Vector createFriendlyNamesList(Vector _services) {
    
    		Vector friendlyNames = new Vector();
    		Enumeration e = _services.elements();
    		while (e.hasMoreElements()) {
    			ServiceRecord serviceRecord = (ServiceRecord) e.nextElement();
    			RemoteDevice device = serviceRecord.getHostDevice();
    
    			try {
    				friendlyNames.addElement(device.getFriendlyName(false));
    			} catch (IOException e1) {
    				// If there is an excption getting the friendly name
    				// use the address
    				friendlyNames.addElement(device.getBluetoothAddress());
    			}
    		}
    		return friendlyNames;
    	}
    
    	/*
    	 * Server OBEX GET command is supported only in IdleState
    	 * 
    	 * @see bcexchanger.comm.ExchangerState#onGet(javax.obex.Operation)
    	 */
    	public int onGet(Operation op) {
    		return ResponseCodes.OBEX_HTTP_CONFLICT;
    	}
    
    	/*
    	 * Server OBEX PUT command is supported only in IdleState
    	 * 
    	 * @see bcexchanger.comm.ExchangerState#onPut(javax.obex.Operation)
    	 */
    	public int onPut(Operation op) {
    		return ResponseCodes.OBEX_HTTP_CONFLICT;
    	}
    }

ReceiveState class

This class represents the state when a business card is received from the remote device. In this state the client OBEX GET operation is performed. No OBEX server commands are handled.

To create the class:

  1. Create the InquiryState class file.

  2. Assign the class to the Comm package and import the required classes.

    package bcexchanger.comm;
    
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.DataInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import javax.microedition.io.Connector;
    import javax.obex.ClientSession;
    import javax.obex.HeaderSet;
    import javax.obex.Operation;
    import javax.obex.ResponseCodes;
  3. Set the class to extend the ExchangerState class. Set the instance variables for ClientSession and Operation.

    
    /**
     * 
     * This class implements business card receiving state. In this state
     * an client OBEX GET operation is peformed. No OBEX server commands
     * are served.
     * 
     * This class extends ExchangerState abstract class.
     * 
     * @version 1.0 29.09.2005
     * @see bcexchanger.comm.ExchangerState Design Patterns: State
     */
    public class ReceiveState extends ExchangerState{
    
      // Instance variables
      private ClientSession connection = null;
      private Operation operation = null;
      String url = null;
    
      boolean cancelInvoked = false; // true after cancelSending is
                                      // called
    
      /**
       * Constructor
       * <p>
       * description
       * 
       * @param _parent -
       *          the class which is nesting the current state of the
       *          state machine
    	 * @param _url -
    	 *            a URL of the remote OBEX service. This state is responsible
    	 *            for establishin the connection
       * @exception
       * @see
       */
      public ReceiveState(ExchangerStateParent _parent,
          String url) 
      {
        super(_parent);
        this.url = url;
      }
    
      /*
       * ReceiveState does not allow to start any other business card
       * exchange process.
       * 
       * @see bcexchanger.comm.ExchangerState#startSending()
       */
      public void startSending(int to) throws IOException {
    
        throw new IOException(
            "Receiving is in progress. Receiving has to be canceled before starting new sending process");
    
      }
    
      /*
       * ReceiveState allows to cancel receiving process.
       * 
       * @see bcexchanger.comm.ExchangerState#cancelSending()
       */
      public void cancelSending() {
        cancelInvoked = true;
    
        try { // cancel any active operation running
          operation.abort();
        } catch (IOException e) {
          // Ignore, aborting operation anyway
        }
    
        try { // send DISCONNECT command to the remote peer
          connection.disconnect(null);
        } catch (IOException e1) {
            // Ignore, disconnecting anyway
        }
    
      }
    
  4. Implement the logic for receiving a business card.

    The getResponseCode() method of the HeaderSet interface returns the response code received from the server. To see all the available response codes, see the ResponseCodes class.

    
      /*
       * This method implements the logic for
       * receiving a business card
       * 
       */
      public void doGet()
      {
    	  try 
    	  {
    		  connection = (ClientSession) Connector.open(url);
    		  HeaderSet response = connection.connect(null);
    		  operation = connection.get(null);
    		  InputStream in = operation.openInputStream();
    		  byte[] fullResult = null; 
    		  {
    			  byte[] buf = new byte[256];
    			  ByteArrayOutputStream bout = new ByteArrayOutputStream(2048);
    			  for (int len = in.read(buf); len >= 0; len = in.read(buf))
        		  {	
        		  	bout.write(buf, 0, len);
        		  } 
    			  fullResult = bout.toByteArray();
    		  }   
    		  ByteArrayInputStream bin = new ByteArrayInputStream(fullResult);
    		  DataInputStream din = new DataInputStream(bin);
    		  int size = din.readInt();    
    		  byte[] vCard = new byte[size];
    		  din.read(vCard);
    		  
    		  parent.getListener().onReceiveBC(vCard);
    		 
    		  // End the transaction
    		  in.close();
    		  int responseCode = operation.getResponseCode();
    	
    		  operation.close();
    		  
    		  if (cancelInvoked) {
    			  throw new Exception(
    			  "Cancel did not invoke any exception; throw exception then");
    		  }
    
    		  // receive is done
    		  try 
    		  
    		  {
    			  parent.setState(new IdleState(parent));
    			  parent.getListener().onReceiveComplete(ExchangerComm.DONE);
    		  } 
    		  catch (Exception e) 
    		  {
    		  }
    
        } 
    	catch (Exception e1) 
    	{
          if (cancelInvoked)
          { // if exception was caused by canceling
        	  parent.setState(new IdleState(parent));
            parent.getListener()
                .onReceiveComplete(ExchangerComm.CANCELED);
          } 
          else 
          { // if exception was caused by error
        	  
        	parent.setState(new IdleState(parent));
            parent.getListener().onReceiveComplete(ExchangerComm.ERROR);
          }
        } 
    	finally 
    	{
          try { // finalizing operation
            operation.close();
          } catch (IOException e3) 
          {
          }
    
          try { // sending DISCONNECT command
            connection.disconnect(null);
          } catch (IOException e2) 
          {
            // Ignore, disconnecting anyway
          }
          
    
        }
      }
      
      /*
       * Server OBEX GET command is supported only in
       * IdleState
       * 
       * @see bcexchanger.comm.ExchangerState#onGet(javax.obex.Operation)
       */
      public int onGet(Operation op) {
        return ResponseCodes.OBEX_HTTP_CONFLICT;
      }
    
      /*
       * Server OBEX PUT command is supported only in
       * IdleState
       * 
       * @see bcexchanger.comm.ExchangerState#onPut(javax.obex.Operation)
       */
      public int onPut(Operation op) {
        return ResponseCodes.OBEX_HTTP_CONFLICT;
      }
    
    
    }
    

SendState class

This class represents the state when a business card is sent to the remote phone. In this state the client OBEX PUT operation is performed. No OBEX server commands are handled. This class extends the ExchangerState abstract class.

To create the class:

  1. Create the InquiryState class file.

  2. Assign the class to the Comm package and import the required classes.

    package bcexchanger.comm;
    
    import java.io.IOException;
    import java.io.OutputStream;
    
    import javax.microedition.io.Connector;
    import javax.obex.ClientSession;
    import javax.obex.HeaderSet;
    import javax.obex.Operation;
    import javax.obex.ResponseCodes;
    
  3. Set the class to extend the ExchangerState class. Set the instance variables.

    
    /**
     * This class implements business card sending state. In this state a client
     * OBEX PUT operation is peformed. No OBEX server commands are served.
     * 
     * This class extends ExchangerState abstract class. 
     * 
     * @version 1.0 29.09.2005
     * @see bcexchanger.comm.ExchangerState Design Patterns: State
     */
    public class SendState extends ExchangerState {
    
    	// Instance variables
    
    	private ClientSession connection = null;
    
    	private String url = null;
    
    	private Operation operation = null;
    
    	private boolean cancelInvoked = false;
    
    	/**
    	 * Constructor
    	 * 
    	 * @param _parent -
    	 *            the class which is nesting the current state of the state
    	 *            machine
    	 * @param _url -
    	 *            a URL of the remote OBEX service. This state is responsible
    	 *            for establishin the connection
    	 */
    	public SendState(ExchangerStateParent _parent, String _url) {
    		super(_parent);
    
    		url = _url;
    	}
    
    	/*
    	 * SendState does not allow to start any other business card exchange
    	 * process.
    	 * 
    	 * @see bcexchanger.comm.ExchangerState#startSending()
    	 */
    	public void startSending(int o) throws IOException {
    		throw new IOException(
    				"Sending is in progress. Sending has to be canceled before starting new sending process");
    
    	}
    
    	/*
    	 * SendState allows to cancel sending process.
    	 * 
    	 * @see bcexchanger.comm.ExchangerState#cancelSending()
    	 */
    	public void cancelSending() {
    
    		cancelInvoked = true;
    
    		try { // cancel any active OBEX operation
    			operation.abort();
    		} catch (Exception e) { // catch NullPointer exception also
    			// Ignore, aborting anyway
    		}
    
    		try { // send DISCONNECT to the remote peer
    
    			connection.disconnect(null);
    		} catch (Exception e1) { // catch NullPointer exception also
    			// Ignore, disconnecting anyway
    		}
    
    		try { // close the connection
    			connection.close(); // 
    
    		} catch (Exception e2) { // catch NullPointer exception also
    			// Ignore, closing anyway
    		}
    
    	}
    
  4. Implement the logic for sending a business card.

    The getResponseCode() method of the HeaderSet interface returns the response code received from the server. To see all the available response codes, see the ResponseCodes class.

    
    	/*
    	 * This method implements the logic for sending a
    	 * business card
    	 * 
    	 */	
    	public void doSend()
    	{
    		try 
    		{
    			connection = (ClientSession) Connector.open(url);
    			HeaderSet response = connection.connect(null);
    
    			// Initiate the PUT request
    			operation = connection.put(null);
    			OutputStream out = operation.openOutputStream();
    			//getting the own card
    			byte[] vCard = parent.getListener().getOwnBC(); 
    			int vlen = vCard.length;
    			byte[] tmpBuf = new byte[vlen + 4];
    			System.arraycopy(vCard, 0, tmpBuf, 4, vlen);
    			tmpBuf[0] = (byte) ((vlen >>> 24) & 0xff);
    			tmpBuf[1] = (byte) ((vlen >>> 16) & 0xff);
    			tmpBuf[2] = (byte) ((vlen >>> 8) & 0xff);
    			tmpBuf[3] = (byte) ((vlen >>> 0) & 0xff);
    			//sending data
    			out.write(tmpBuf); 
    			out.close();
    			int responseCode = operation.getResponseCode();
    
                operation.close();
    
    			if (cancelInvoked) {
    				throw new Exception(
    						"Cancel did not invoke any exception; throw exception then");
    			}
    
    			// send is done
    			try {				
    				parent.setState(new IdleState(parent));
    			} catch (Exception e) 
    			{
    			}
    		} 
    		catch (Exception e1) 
    		{
    			parent.setState(new IdleState(parent));
    			if (cancelInvoked) 
    			{ // if exception is caused by cancelation
    				parent.getListener().onSendComplete(ExchangerComm.CANCELED);
    			} else 
    			{ // if exception is caused by error
    				parent.setState(new IdleState(parent));
    				parent.getListener().onSendComplete(ExchangerComm.ERROR);
    			}
    		}
    			finally 
    			{
    		      try { // finalizing operation
    		        operation.close();
    		      } 
    		      catch (IOException e3) {
    		    	
    		      }
    
    		      try { // sending DISCONNECT command
    		        connection.disconnect(null);
    		      } 
    		      catch (IOException e2) 
    		      {		    	  
    		        // Ignore, disconnecting anyway
    		      }
    		    }
    	}
    
    	/*
    	 * Server OBEX GET command is supported only in IdleState
    	 * 
    	 * @see bcexchanger.comm.ExchangerState#onGet(javax.obex.Operation)
    	 */
    	public int onGet(Operation op) {
    		return ResponseCodes.OBEX_HTTP_CONFLICT;
    	}
    
    	/*
    	 * Server OBEX PUT command is supported only in IdleState
    	 * 
    	 * @see bcexchanger.comm.ExchangerState#onPut(javax.obex.Operation)
    	 */
    	public int onPut(Operation op) {
    		return ResponseCodes.OBEX_HTTP_CONFLICT;
    	}
    
    
    }