Sorry about the red box, but we really need you to update your browser. Read this excellent article if you're wondering why we are no longer supporting this browser version. Go to Browse Happy for browser suggestions and how to update.

NFC connection handover

An NFC connection handover is a communications bridging solution documented in the NFC specification and supported by the BlackBerry Java SDK. Establishing a communication link between two devices with NFC is fast, but limits users to a short broadcast range and small file size. When a data transfer takes longer than 3 seconds, requiring that devices stay in close proximity, it detracts from the user experience. Bluetooth communication supports a greater broadcast range and high-speed data exchange but requires more effort to establish the initial connection.

By taking advantage of the NFC connection handover, applications can provide the best of both communication protocols; the NFC link is established quickly between devices, then that connection is handed over to a Bluetooth pairing for a sustained connection. Users can quickly and easily share pictures and movies, chat, and play games.

Using NFC connection handover

The following are general requirements for implementing NFC connection handover functionality:

  • All devices being linked must support NFC connection handover and Bluetooth communication protocols.
  • Devices must be physically tapped to initiate the NFC connection.
  • Applications must be in the foreground to initiate an NFC connection handover.
  • Applications must have an implemented ConnectionHandoverListener interface.
  • The application listener interface must be registered using ConnectionHandoverManager.

Several core apps support NFC connection handover, including Contacts, File Explorer, and Documents To Go, among others. You can implement NFC connection handover functionality with the classes and interfaces in the net.rim.device.api.io.nfc.handover package. Details about Bluetooth communication features are provied at net.rim.device.api.bluetooth.

Static and dynamic NFC connection handover

BlackBerry Java SDK 7.1 supports both static and dynamic NFC to Bluetooth connection handover.

A static NFC connection handover event is used to establish communication with a receiving device, for example, connecting a BlackBerry device with a Bluetooth earpiece. In this example, after it is tapped against the earpiece, the BlackBerry device detects an NDEF tag that contains a static handover message. The BlackBerry device analyzes the message, and if it contains a valid Bluetooth handover record, the device initiates a connection with the earpiece on Bluetooth protocols.

A dynamic NFC handover event is managed with the LLCP protocol. A tap initiates connection handover negotiation between two enabled devices. MAC addresses are exchanged between devices, and then listener messages are executed.

Implementing the interface

The BlackBerry Java SDK 7.1 supports NFC handover connection to a Bluetooth interface, but does not currently support handover to a Wi-Fi connection.

The NFC connection handover has three requirements for successful completion:
  • Implement the ConnectionHandoverListener interface.
  • Register the listener with ConnectionHandoverManager using a class.
  • Interpret protocol messages over the connection.
A successful process concludes with handover to the Bluetooth connection.

Managing listener messages

When the ConnectionHandoverListener interface is implemented, the results of the connection negotiation are provided to the application.

The method completeConnectionHandover() is invoked when a connection handover is ready to start between two NFC-enabled devices. The NFC connection handover is performed based on the response to this method. A failure condition is normally produced if a response is not received to a negotiation within 1 second.

Successful NFC connection handover events invoke the method onConnectionHandoverCompleted(), as follows:

  • BLUETOOTH_TRANSPORT - Indicates the Bluetooth transport type.
  • REQUESTER_P2P_DETECTED - Indicates that an NFC connection handover requester was detected.
  • SELECTOR_P2P_DETECTED - Indicates that an NFC connection handover selector device was detected.
  • SELECTOR_TAG_READ - Indicates that an NFC tag with an NDEF record that contains connection handover information was read.

Failed NFC connection handover events invoke the method onConnectionHandoverFailed(), as follows:

  • FATAL_NFC_SUBSYSTEM_ERROR - Indicates that a failure occurred because of a fatal NFC subsystem error.
  • HANDOVER_TIMEOUT - Indicates that a failure occurred because the handover process timed out.
  • LOCAL_TRANSPORT_UNAVAILABLE - Indicates that a failure occurred because the transport type was unavailable on the device.
  • NO_TRANSPORT_AVAILABLE - Indicates that a failure occurred because the transport type is unavailable.
  • REMOTE_TRANSPORT_UNAVAILABLE - Indicates that a failure occurred because the transport type was unavailable on the partner device.
  • TRANSPORT_ERROR - Indicates that a failure occurred for a transport-specific reason.
  • TRANSPORT_HANDOVER_CANCELED - Indicates that a failure occurred because the user canceled the request.
  • TRANSPORT_POWERED_OFF - Indicates that a failure occurred because the transport type is powered off.
  • TRANSPORT_UNKNOWN - Indicates that a failure occurred for a transport-specific reason.

To provide a failure message for the user, use a switch block with getFailedReason() to capture the failure type and then convert the data to a string that can be displayed.

Implementing an NFC connection handover

package BCH;

import net.rim.device.api.bluetooth.BluetoothSerialPort;
import net.rim.device.api.bluetooth.BluetoothSerialPortInfo;
import net.rim.device.api.bluetooth.BluetoothSerialPortListener;
import net.rim.device.api.command.Command;
import net.rim.device.api.command.CommandHandler;
import net.rim.device.api.command.ReadOnlyCommandMetadata;
import net.rim.device.api.io.nfc.handover.*;
import net.rim.device.api.ui.component.LabelField;
import net.rim.device.api.ui.component.ButtonField;
import net.rim.device.api.ui.container.MainScreen;
import net.rim.device.api.system.Characters;
import net.rim.device.api.util.Arrays;
import net.rim.device.api.util.DataBuffer;
import net.rim.device.api.ui.MenuItem;
import net.rim.device.api.ui.component.RichTextField;
import net.rim.device.api.ui.component.Status;

import net.rim.device.api.ui.UiApplication;

import java.io.IOException;

/*Create the application framework by extending
the UiApplication class. In main(), create an instance 
of the new class and invoke enterEventDispatcher() to 
enable the application to receive events.*/
class BCHDemo extends UiApplication
{
    public static void main(String args[])
    {
        BCHDemo theApp = new BCHDemo();
        theApp.enterEventDispatcher();
    }
    
    BCHDemo() 
    {

/*In a try block, use ConnectionHandoverManager to register an 
event listener and push the WelcomeScreen() to the top of the 
display stack.*/
        try
        {
            ConnectionHandoverManager.getInstance().addConnectionHandoverListener(
												new BCHListener(), ConnectionHandoverEvent.BLUETOOTH_TRANSPORT,false);
            System.out.println("BCH DEMO: Listener registered");
            pushScreen(new WelcomeScreen());

//Capture any exceptions which may occur.
        } catch (Exception e)
        {
        }
    }

/*Use the BCHListener class to implement the ConnectionHandoverListener 
interface. The ConnectionHandoverListener interface processes success and 
failure conditions of handover events when your app 
establishes a wireless connection.*/
    private class BCHListener implements ConnectionHandoverListener
    {
        ChooseConnScreen _screen;
        
        BCHListener()
        {
            _screen = new ChooseConnScreen();
            System.out.println("BCH DEMO: Listener created");
        }
        
        public boolean completeConnectionHandover()
        {
            return true;
        }
   
//Use onConnectionHandoverCompleted to handle success conditions.     
        public void onConnectionHandoverCompleted(
								ConnectionHandoverCompletedEvent event)
        {
/*In the event of a successful exchange, the user is 
notified that a handover has been completed.*/
            System.out.println("BCH DEMO: Handover completed");
            if(event instanceof BluetoothConnectionHandoverCompletedEvent)
            {
                System.out.println("BCH DEMO: BCH detected");

                if(_screen.initializeUI())
                {
                    System.out.println("BCH DEMO: screen initalized");
                    net.rim.device.api.ui.UiApplication.
                    getUiApplication().pushScreen(_screen);
                }
            }
        }
//Use the method onConnectionHandoverFailed to handle the failure conditions.       
        public void onConnectionHandoverFailed(ConnectionHandoverFailedEvent event)
        {
//Use a switch block to display the reason for a failed connection 
//handover. The information can be displayed to the user by capturing the 
//event type using .getFailedReason() and storing the information
//as a String which can be displayed using a System.out.println.
            String fail = null;
            switch(event.getFailedReason())
            {
          case ConnectionHandoverFailedEvent.FATAL_NFC_SUBSYSTEM_ERROR:
          fail = "NFC Error"; break;
          case ConnectionHandoverFailedEvent.HANDOVER_TIMEOUT: 
          fail = "Timeout"; break;
          case ConnectionHandoverFailedEvent.LOCAL_TRANSPORT_UNAVAILABLE: 
          fail = "Local XPORT Unavailable"; break;
          case ConnectionHandoverFailedEvent.TRANSPORT_ERROR:  
          fail = "XPORT Error"; break;
          case ConnectionHandoverFailedEvent.TRANSPORT_HANDOVER_CANCELED: 
          fail = "Canceled"; break;
          case ConnectionHandoverFailedEvent.TRANSPORT_UNKNOWN: 
          fail = "XPORT Unknown"; break;
           }
           System.out.println("BCH DEMO: Handover failed: " + fail);
        }
    }
    
    private class WelcomeScreen extends MainScreen
    {
        WelcomeScreen()
        {
            RichTextField rtf = new RichTextField(
            "Tap your device with another to begin");
            add(rtf);
        }
    }
  
//Use getSerialPortInfo() to retrieve the device connection 
//information, and create a button using btnConnect
//to allow the user choose which device they would like to connect with.  
    private class ChooseConnScreen extends MainScreen
    {
        CommScreen _listenScreen = new CommScreen(null);
        
        public boolean initializeUI()
        {
            boolean serviceReady = false;
            ConnectButton btnConnect = null;

            System.out.println("BCH DEMO: looking for service");
            BluetoothSerialPortInfo portInfo[] = 
												BluetoothSerialPort.getSerialPortInfo();
            int numServices = portInfo.length;
            for(int count = numServices-1; count>=0; count--)
            {
                String serviceName = portInfo[count].getServiceName();
                if(serviceName.equals("BCHDemo"))
                {
        System.out.println("BCH DEMO: service found");
        serviceReady = true;
        btnConnect = new ConnectButton("Connect to: " +
        portInfo[count].getDeviceName(), portInfo[count]);
                }
            }
            
            if(serviceReady)
            {
          RichTextField rtf = new RichTextField(
          "You've tapped a device that has a chat application loaded."
          +"Choose a device to chat with, or Listen for a connection");
                add(rtf);

                add(btnConnect);

                ButtonField btnListen = new ButtonField("Listen for Connection");

                btnListen.setCommand(new Command(new CommandHandler()
                {
                    public void execute(
                    ReadOnlyCommandMetadata metadata, Object context)
                    {
                        UiApplication.getUiApplication().pushScreen(_listenScreen);
                        close();            
                    }
                }));
                add(btnListen);
            }
            return serviceReady;
        }
    }
    
    private class ConnectButton extends ButtonField
    {
        private BluetoothSerialPortInfo _info;
        public ConnectButton(String labelText, BluetoothSerialPortInfo info)
        {
            super(labelText);
            _info = info;
            this.setCommand(new Command(new CommandHandler()
            {
                public void execute(ReadOnlyCommandMetadata metadata, Object context)
                {
                    UiApplication.getUiApplication().pushScreen(
                    new CommScreen(_info));           
                }
            }));
        }
    }
  
//Use the CommScreen class to implement the BluetoothSerialPortListener interface.  
private class CommScreen extends MainScreen implements BluetoothSerialPortListener
    {
        private RichTextField _rtf;    
        private StringBuffer _data;
        private byte[] _receiveBuffer = new byte[1024];
        private BluetoothSerialPort _port;
        private boolean _dataSent = true;
        private String _deviceName;
        private DataBuffer _db;

        public CommScreen(BluetoothSerialPortInfo info)
        {
//Fill an lk array with the 'a' character.
            Arrays.fill(_receiveBuffer, (byte)'a');
    
//Initialize the buffers
            _data = new StringBuffer();
            _db = new DataBuffer();
    
            try
            {
                if(info == null)
                {
//Open a port to listen for incoming connections
      _rtf = new RichTextField(
      "Waiting for other device to begin chat session...",
      RichTextField.NON_FOCUSABLE);

                    _port = new BluetoothSerialPort("BCHDemo", 
																																BluetoothSerialPort.BAUD_115200, 
                                BluetoothSerialPort.DATA_FORMAT_PARITY_NONE | 
                                BluetoothSerialPort.DATA_FORMAT_STOP_BITS_1 | 
                                BluetoothSerialPort.DATA_FORMAT_DATA_BITS_8, 
                                BluetoothSerialPort.FLOW_CONTROL_NONE, 
                                1024, 1024, this);
                    _deviceName = "unknown";
                }
                else
                {
//Connect to the selected device
        _rtf = new RichTextField("Connected. You can begin the chat.",
        RichTextField.NON_FOCUSABLE);

              _port = new BluetoothSerialPort(info, 
                    BluetoothSerialPort.BAUD_115200, 
                    BluetoothSerialPort.DATA_FORMAT_PARITY_NONE | 
                    BluetoothSerialPort.DATA_FORMAT_STOP_BITS_1 | 
                    BluetoothSerialPort.DATA_FORMAT_DATA_BITS_8, 
                    BluetoothSerialPort.FLOW_CONTROL_NONE, 
                    1024, 1024, this);
              _deviceName = info.getDeviceName();
                }
            }
            catch(IOException ex)
            {
//Process errors
            }
    
            add(_rtf);
        }
        
        public void deviceConnected(boolean success)
        {
            if (success)
                Status.show("Bluetooth SPP connected to " + _deviceName);
            else
                Status.show("Bluetooth SPP failed to connect to " + _deviceName);
        }
      
        public void deviceDisconnected()
        {
            Status.show("Disconnected from " + _deviceName);
        }
     
        public void dtrStateChange(boolean high)
        {
            Status.show("DTR: " + high);
        }
    
        public void dataReceived(int length)
        {
            int len;
            try
            {
//Read the data that arrived.
          if((len = _port.read(_receiveBuffer, 0,
          length == -1 ? _receiveBuffer.length : length)) != 0)
                {
//Update the screen with the new data that arrived
                    _data.append(new String( _receiveBuffer, 0, len));
                    _rtf.setText(_data.toString());
                }
//Process errors
            } catch(IOException ioex)
            {

            }
        }
     
        public void dataSent()
        {
//Set the _dataSent flag to true to allow more data to be written.
            _dataSent = true;
    
//Call sendData in case there is data waiting to be sent
            sendData();
        }
    
//Invoked when a key is pressed
        public boolean keyChar(char key, int status, int time)
        {
            if(!(key == Characters.ESCAPE))
            {    
//Send the key if a Bluetooth connection has been established.
                if(_port != null)
                {
                    if(key == '\n')
                    {
                        writeData((byte)'\r');
                    }
                    else
                    {
                        writeData((byte)key);
                    }
        
//Update the screen adding the character just pressed.
                    _data.append(key);
                    _rtf.setText(_data.toString());
                    
                    return true;
                }           
            }
            
            return super.keyChar(key, status, time);
        }
    
//Writes a byte to the 'send' buffer
        private void writeData(byte theData)
        {
            synchronized(_db)
            {
                _db.write(theData);
    
//Call sendData to send the data
                sendData();
            }
        }
    
//Sends the data currently stored in the DataBuffer to the other device
        private void sendData()
        {
//Ensure there is data to send
            if (_db.getArrayLength() > 0)
            {
//Ensure the last write call has resulted in the sending of the data
//prior to calling write again.  Calling write in sequence without waiting
//for the data to be sent can overwrite existing requests and result in
//data loss.
                if (_dataSent)
                {
                    try
                    {
//Set the _dataSent flag to false so we don't send any more
//data until it has been verified that this data was sent.
                        _dataSent = false;
    
                        synchronized(_db)
                        {
//Write out the data in the DataBuffer and reset the DataBuffer
                            _port.write(_db.getArray(), 0, _db.getArrayLength());
                            _db.reset();
                        }
                    }
                    catch (IOException ioex)
                    {
//Reset _dataSent to true so we can attempt another data write
                        _dataSent = true;
//Process errors
                    }
                }
                else
                {
      System.out.println("Can't send data right now,
      data will be sent after dataSent notify call.");
                }
            }
        }
        
        public void close()
        {
            if (_port != null)
            {
                _port.close();
            }        
            super.close();
        }
    }
}