Homework 1

Android and
Bluetooth

Modified

Resources

Disclosure

Some of the following examples are from:

Install

Downloads

Because Bluetooth is not supported by the emulator, for testing you will need either 2 Android devices or 1 Android and a computer with Bluetooth.

The client/server pairs below should interact.

An Android peer should interact with another Android peer or with a Java client or server. Remember to start the device to act as server first, the peer always attempts to start as a client and if fails to find a server, then waits as a server.

Note: service ID must agree between the client and server.

Overview

Bluetooth is a wireless protocol for local communications.

While many similarities exist between Internet and Bluetooth protocols, one key difference is that Bluetooth devices move in and out of radio frequency range. Normally, before commencing communication, devices must discover others to detect the address and services that are provided by other devices.

The following presents relevant elements of two Bluetooth implementations: Android and BlueCove JSR-82.

The two implementations differ in several significant ways, the reasons for discussing is:

  1. The Android emulator does not emulate Bluetooth so real devices are required for basic program testing,
  2. JSR-82 is implemented on a variety of platforms including J2ME, OS X, Linux, and Windows,
  3. Command line programs using JSR-82 can communicate over Bluetooth with Android devices, allowing testing using only one real device.

The last allows the homework assignment to be developed using one Android and a computer with Bluetooth.

Client/Server or Peer-to-Peer

Because Bluetooth devices move in and out of range, an important part of a Bluetooth app is discovering other devices and what services might be provided.

Devices in discovery mode broadcast their identity to other devices.

Services are also advertized by broadcasts.

Below are two cases for advertizing a service:

  1. A server advertizes a service and waits for a client to discover and connect to the service.
     
  2. Several peers advertize the same service and wait for a peer to discover and connect to the service.

Peer-to-peer communication essentially requires that each peer can act as a client or a server but does offer several advantages:

  1. Possibly only one copy of the application executes on multiple peers.
     
  2. More robust than client/server since any device can act as a client or a server.

 

Key programming issues

Discovery

Devices must be set to discovery mode.

Known address

Communication with a known remote device is established by:

btspp - Several protocols are possible, RFCOMM is connection-oriented, btspp is Bluetooth Serial Port Profile.

Address - Bluetooth devices have an 12 hexadecimal digit MAC address (e.g. 00:12:D2:5A:BD:E4)

port - The port on which the remote device will receive data.

A complete specification would be:

btspp://0012D25ABDE4:3

Known service

Usually the service desired (email, chat, etc.) is known but not device address and port. Discovery can determine the address and port used by a service (i.e. the URL). More follows.

UUID

Services are uniquely identified by a Universally Unique Identifier (UUID), a 128-bit number, in hexadecimal something similar to BAE0D0C0B0A00095570605040302011.

Service Discovery Protocol (SDP)

SDP determines the URL given a service UUID.

Server

Client

 

Bluetooth Stack

Microsoft Windows 7 provides a Bluetooth stack that can be enabled through the Control Panel.

Linksys USB device is available from the instructor that works with Windows 7.

Mac has a native Bluetooth device and stack but device discovery, etc. must be configured through the settings.

Note that real devices often must be set to discovery mode through the control panel or settings.

 

Windows/Mac (pre-Lion) JSR 82 Bluetooth programming

Install/Compile/Execute

Java - Try the Java version installed first. The most recent JDK supports only 64-bit software, BlueCove is 32-bit. Simplest solution is to install JDK 1.6 which supports 32-bit execution using the -d32 switch.

BlueCove - The JSR 82 implementation, BlueCove, can be downloaded (see above) as a jar file and placed in the classpath.

Windows - A Java program can be compiled and executed at the command prompt in Windows, assuming the current directory contains the Java program and the BlueCove jar file, by:

javac -classpath .;bluecove-2.1.0.jar test.java

java -classpath .;bluecove-2.1.0.jar test

OS X (pre-Lion) - A Java program can be compiled and executed at the command prompt in OS X, assuming the current directory contains the Java program and the BlueCove jar file, by:

javac -classpath .:bluecove-2.1.0.jar test.java

java -d32 -classpath .:bluecove-2.1.0.jar test

 

Getting started - Device discovery

An example that should work on OSX or Windows is display of discoverable devices.

The computer needs a Bluetooth device installed.

  1. Download and unzip Java Remote Discovery
     
  2. Open a command prompt window.
     
  3. Navigate to the JavaBlueToothRemoteDiscovery folder.
     
  4. Windows 32-bit

    java -classpath bin;bluecove-2.1.0.jar RemoteDeviceDiscovery

    In LF111 lab use:
    C:"\Program Files (x86)\Java\jdk1.7.0_03\bin\"javac -classpath .;bluecove-2.1.0.jar RemoteDeviceDiscovery.java
    
    C:"\Program Files (x86)\Java\jdk1.7.0_03\bin\"java -d32 -classpath .;bluecove-2.1.0.jar RemoteDeviceDiscovery
  5. OSX

    java -classpath bin:bluecove-2.1.0.jar -d32 RemoteDeviceDiscovery
     

RFCOMM service

Because RFCOMM is a connection-oriented protocol, similar to TCP, one process acts as a server accepting connections, another process acts as the client requesting the connection.

Below are basic client and server scripts, to keep things simple, we manually entered the Bluetooth device address (0012D25ABDE4) of the server (much like with the Internet) and a port; in the following, the client connects to the server on port 3.

The client as given will not work on your machine because the server device address is specific to one computer. A later example displays the address of all discoverable devices.

Note that JSR 82 assigns server ports dynamically, so the client assumes to magically know port 3 is the correct port.

More later on how the client discovers the server's address and port.

Echo Client and Server

Client
import java.io.*;
import javax.microedition.io.*;
import javax.bluetooth.*;

public class RFCOMMClient {

 public static void main( String args[] ) {
   try {





      StreamConnection conn =              // block for connect
         (StreamConnection)
          Connector.open("btspp://0012D25ABDE4:3");

      DataOutputStream out = new DataOutputStream(
                                              conn.openOutputStream());

      DataInputStream in = new DataInputStream(
                                              conn.openInputStream());

      out.writeUTF("Hello");               // Write server

      String received = in.readUTF(); // Read server

      System.out.println( received );
     
      conn.close();
   }
   catch ( IOException e ) { System.err.print(e.toString()); }
 }
}

Question 1

  1. Which (client/server) must start first?
  2. Server defines a helloService. Does client reference?
  3. What is the output and which displays?
  4. Determine the address of your computer.
  5. Install and test RFCOMMServer.java:

    BlueCove download

    Windows with jar and Java file in current directory.

    javac -classpath .;bluecove-2.1.0.jar RFCOMMServer.java

    java -classpath .;bluecove-2.1.0.jar RFCOMMServer -d32

     

Server
import java.io.*;
import javax.microedition.io.*;
import javax.bluetooth.*;

public class RFCOMMServer {
    public static void main(String args[]) {
	try {

	   StreamConnectionNotifier service = 
                    (StreamConnectionNotifier) Connector.open("btspp://localhost:" 
                            + new UUID("0000110100001000800000805F9B34FB",
			false).toString() + ";name=helloService");

	    StreamConnection conn = 		     // block for connect
                     (StreamConnection) service.acceptAndOpen();

	    System.out.println("Connected");

	    DataInputStream in = new DataInputStream(
                                                         conn.openInputStream());
	    DataOutputStream out = new DataOutputStream(
                                                          conn.openOutputStream());

	    String received = in.readUTF();	      // Read from client

	    out.writeUTF("Echo: " + received);	      // Send Echo to client

	    conn.close();
	    service.close();

	} catch (IOException e) {	System.err.print(e.toString());   }
     }
}

The server defines the service name helloService with a UUID of 0000110100001000800000805F9B34FB as an identification when discovering services. Note that in Java the server port cannot be defined but is selected dynamically, requiring service discovery by the client.

Defining a server connection is a 3 step process:

  1. Define the URL which includes:
    1. Device address, usually localhost
    2. a UUID, the ID number of the service
    3. name of the service

      String url = "btspp://localhost:" + new UUID("0000110100001000800000805F9B34FB",false).toString() +";name=helloService";
       

  2. Open the URL.

    StreamConnectionNotifier service = (StreamConnectionNotifier) Connector.open( url );

  3. Wait for a client connection.

    StreamConnection conn = (StreamConnection) service.acceptAndOpen();
     

Android Echo client

The Android client works with the RFCOMMServer.java above.

Must know server Bluetooth device MAC address, not always practical and when discovery protocols are implemented on the device, there are better solutions (discussed later).

Bluetooth functions require a thread to access and call-backs to display any received messages to the UI thread.

Note that the AndroidManifest.xml file must contain:

   <uses-permission android:name="android.permission.BLUETOOTH" />
   <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
 

AndroidBluetoothEchoClientActivity
package edu.ius.rwisman.AndroidBluetoothEchoClient;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.util.UUID;
import android.app.ListActivity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.os.Bundle;
import android.os.Handler;
import android.widget.LinearLayout;
import android.widget.ArrayAdapter;

public class AndroidBluetoothEchoClientActivity extends ListActivity {
	LinearLayout layout;

	private ArrayAdapter mArrayAdapter;

	final Handler handler = new Handler();

	final Runnable updateUI = new Runnable() {
		public void run() {
			mArrayAdapter.add( bluetoothClient.getBluetoothClientData() );
		}
	};

	BluetoothClient bluetoothClient;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		mArrayAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1);
		this.setListAdapter(mArrayAdapter);

		bluetoothClient = new BluetoothClient(handler, updateUI);
		bluetoothClient.start();
	}
}

class BluetoothClient extends Thread {

	BluetoothAdapter mBluetoothAdapter;
	private String data = null;

	final Handler handler;
	final Runnable updateUI;

	public BluetoothClient(Handler handler, Runnable updateUI) {
		this.handler = handler;
		this.updateUI = updateUI;

		mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
	}

	public String getBluetoothClientData() {
		return data;
	}

	public void run() {
		BluetoothSocket clientSocket = null;
									// Client knows the server MAC address 
		BluetoothDevice mmDevice = 
                                                     mBluetoothAdapter.getRemoteDevice("28:CF:DA:D6:41:5D");

		try {
									// UUID string same used by server 
			clientSocket = mmDevice.createRfcommSocketToServiceRecord(UUID
					.fromString("00001101-0000-1000-8000-00805F9B34FB"));

			mBluetoothAdapter.cancelDiscovery(); 	// Cancel, discovery slows connection

			clientSocket.connect();

			DataInputStream in = 
                                                               new DataInputStream(clientSocket.getInputStream());
			DataOutputStream out = 
                                                               new DataOutputStream(clientSocket.getOutputStream());

			out.writeUTF("Hello"); 			// Send to server

			data = in.readUTF(); 			// Read from server

			handler.post( updateUI );
		} catch (Exception e) {}
	}
}

Question 2 Explain

  1. mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
     
  2. BluetoothDevice mmDevice = mBluetoothAdapter.getRemoteDevice("28:CF:DA:D6:41:5D");
     
  3. clientSocket = mmDevice.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
     
  4. Install the AndroidBluetoothEchoClientActivity, changing the server device number to that of your computer.

 

Android Echo Server

The Android server works with the clients above.

Bluetooth functions require a thread to access and call-backs to display any received messages to the UI thread.

Note that the AndroidManifest.xml file must contain:

   <uses-permission android:name="android.permission.BLUETOOTH" />
   <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
 

AndroidBluetoothEchoServerActivity
package edu.ius.rwisman.AndroidBluetoothEchoServer;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.util.UUID;
import android.app.ListActivity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.widget.LinearLayout;
import android.widget.ArrayAdapter;

public class AndroidBluetoothEchoServerActivity extends ListActivity  {
	LinearLayout layout;

    	private ArrayAdapter mArrayAdapter;
    
	final Handler handler = new Handler();

	final Runnable updateUI = new Runnable() {
		public void run() {
			mArrayAdapter.add(bluetoothServer.getBluetoothServer());
		}
	};

	BluetoothServer bluetoothServer;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        mArrayAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1);
        this.setListAdapter(mArrayAdapter); 
        mArrayAdapter.add("Waiting for Client ");

        bluetoothServer = new BluetoothServer(handler, updateUI);
        bluetoothServer.start();
    }
}

class BluetoothServer extends Thread {
	BluetoothAdapter mBluetoothAdapter=null;
	String data=null;

	final Handler handler;
	final Runnable updateUI;

	public BluetoothServer(Handler handler, Runnable updateUI) {
		this.handler = handler;
		this.updateUI = updateUI;

		mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
	}

	public String getBluetoothServer() {
		return data;
	}

	public void run() {
		BluetoothServerSocket serverSocket;
		BluetoothSocket socket = null;
		try {
			serverSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord
                                       ("helloService", UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
			
			socket = serverSocket.accept();	// block for connect

			data = "Accept connection";
			handler.post(updateUI);
			
			DataInputStream in = new DataInputStream(socket.getInputStream());
			DataOutputStream out = new DataOutputStream(socket.getOutputStream());

			data = in.readUTF(); 		// Read from client

			out.writeUTF("Echo "+data); 	// Send to client

			handler.post(updateUI);
			
			Log.d("EchoServer", data);		// Log message
				
			serverSocket.close();
			socket.close();
		} catch (Exception e) {}
	}
}

    

The essentials of the Echo client/server interaction under Android are:

Android Client
mBluetoothAdapter =
             BluetoothAdapter.getDefaultAdapter
();
  
public void run() {
   BluetoothSocket clientSocket = null;
// Client knows the server MAC address
   BluetoothDevice mmDevice =
                       mBluetoothAdapter.
                             getRemoteDevice("28:CF:DA:D6:41:5D");

   try {
// UUID string same used by server
     clientSocket = mmDevice.createRfcommSocketToServiceRecord(
        UUID.fromString(
          "00001101-0000-1000-8000-00805F9B34FB"));

      mBluetoothAdapter.cancelDiscovery();

      clientSocket.connect();

      DataInputStream in =
               new DataInputStream(clientSocket.getInputStream());
      DataOutputStream out =
               new DataOutputStream(clientSocket.getOutputStream());

      out.writeUTF("Hello");                         // Send to server

      data = in.readUTF(); // Read from server

      handler.post( updateUI );

Output: Echo: Hello

Android Server
mBluetoothAdapter =
            BluetoothAdapter.getDefaultAdapter
();

public void run() {
   BluetoothServerSocket serverSocket;
   BluetoothSocket socket = null;

   try {
       serverSocket = mBluetoothAdapter.
           listenUsingRfcommWithServiceRecord("helloService",
             UUID.fromString(
                "00001101-0000-1000-8000-00805F9B34FB"));

       socket = serverSocket.accept();   // block for connect

       DataInputStream in =
                 new DataInputStream(socket.getInputStream());
       DataOutputStream out =
                 new DataOutputStream(socket.getOutputStream());

       data = in.readUTF();     // Read from client

       out.writeUTF("Echo "+data);     // Send to client

       handler.post(updateUI);

Question 3

  1. The server MAC address is used: mBluetoothAdapter.getRemoteDevice("28:CF:DA:D6:41:5D");

    Give one example for and one against using the MAC address.

  2. Actual communication is via device:port pair. How is the port determined?
     
  3. Determine the phone's Bluetooth address:

    Setting | About phone | Status

    Share with classmate.
     

  4. Install the AndroidBluetoothEchoServerActivity or AndroidBluetoothEchoClientActivity, changing the server device number.

    Attempt to communicate.

     

JSR-82 Echo Client with Service Discovery

The previous JSR-82 and Android examples required the client to know the Bluetooth MAC address of the server and the port, which is usually dynamically assigned so not known a priori.

StreamConnection conn = (StreamConnection) Connector.open("btspp://0012D25ABDE4:3");

The following client discovers the helloService offered by the either Android or JSR-82 servers above by the UUID 0000110100001000800000805F9B34FB.

UUID HELLOSERVICE_ID = new UUID("0000110100001000800000805F9B34FB",false);

DiscoveryAgent mDiscoveryAgent = LocalDevice.getLocalDevice().getDiscoveryAgent();

String url = mDiscoveryAgent.selectService(HELLOSERVICE_ID,
                                                         ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);

StreamConnection conn = (StreamConnection) Connector.open( url );

url will be the address and port of the server providing the helloService:

btspp://0012D25ABDE4:3

Android Bluetooth implementation has a more complex discovery mechanism examined later.

BluetoothEchoClient.java
import java.io.*;
import javax.bluetooth.*;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;

public class BluetoothEchoClient {

	public static void main(String args[]) {

		UUID HELLOSERVICE_ID = new UUID("0000110100001000800000805F9B34FB",false);

		DiscoveryAgent mDiscoveryAgent;
		String url = null;

		try {
			mDiscoveryAgent = LocalDevice.getLocalDevice().getDiscoveryAgent();

			url = mDiscoveryAgent.selectService(HELLOSERVICE_ID, 
                                                         ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);

			if (url == null) {
				System.out.println("Discovery failed: " + HELLOSERVICE_ID);
				return;
			}
			System.out.println("URL:" + url + "\n");

			StreamConnection conn = 					// block for connect
				(StreamConnection) Connector.open(url);

			DataInputStream in = new DataInputStream(conn.openInputStream());
			DataOutputStream out = new DataOutputStream(conn.openOutputStream());

			out.writeUTF("Hello"); 					// Send server

			String received = in.readUTF(); 				// Read server
			System.out.println(received);

			conn.close();
		} catch (Exception e) {
			System.out.print("Exception: " + e.toString() + "\n");
		}
	}
}	

Output

BlueCove version 2.1.0 on mac
URL:btspp://40FC898CE2B7:1;authenticate=false;encrypt=false;master=false

Echo Hello
BlueCove stack shutdown completed
 

Question 4

  1. What is the purpose and result of:

    url = mDiscoveryAgent.selectService(HELLOSERVICE_ID, ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
     

  2. What is the purpose and result of:

    StreamConnection conn = (StreamConnection) Connector.open(url);
     

  3. Actual communication is via device:port pair. How is the device and port determined?
     
  4. Install and test BluetoothEchoClient.java:

    Change Hello to your name.

    BlueCove download

    Windows with jar and Java file in current directory.

    Have classmate run:

    java -classpath .;bluecove-2.1.0.jar RFCOMMServer -d32

    other run:

    javac -classpath .;bluecove-2.1.0.jar BluetoothEchoClient.java

    java -classpath .;bluecove-2.1.0.jar BluetoothEchoClient -d32

 

Discovery of local Bluetooth devices

Bluetooth devices must be set to discoverable mode to allow other local devices to locate.

 

JSR-82 Device discovery

The above client discovered a device providing a service, any device providing the service would do.

When multiple devices provide the same service UUID and the device matters, we need to discover devices that provide the service. The UUID for common services such as email or an Internet gateway could be the same but only some specific device would be appropriate.

Remember that the client must ultimately determine the server address and the service port in order to make a connection by something similar to:

Connector.open("btspp://0012D25ABDE4:3")

The process is to first discover all the devices in range, then discover whether the service is provided by each device. If the desired service is discovered on a device, then the connection can be made.

JSR 82 does not provide a convenience method for combined device and service discovery, hence the following.

Device Discovery

The address and name of enabled devices within range can be discovered by other Bluetooth devices, discovery can take some time (multiple seconds) to complete, given that radio communications is unreliable and devices may miss transmissions.

The following displays address and name of all enabled devices nearby. Note that:

DiscoveryListener

JSR 82 provides a search service agent that reports device discoveries through a DiscoveryListener interface.

The listener must implement the following methods:

  1. public void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) { }
  2. public void inquiryCompleted(int discType) { }
  3. public void servicesDiscovered(int transID, ServiceRecord[] servRecord) { }
  4. public void serviceSearchCompleted(int transID, int respCode) {}

For device discovery, only 1 and 2 need to be functional.

RemoteDeviceDiscovery.java
import java.io.IOException;
import java.util.Vector;
import javax.bluetooth.*;

public class RemoteDeviceDiscovery {

    public static final Vector <RemoteDevice> devicesDiscovered = new Vector();

    public static void main(String[] args) throws IOException, InterruptedException {

        final Object inquiryCompletedEvent = new Object();

        devicesDiscovered.clear();
DiscoveryListener listener = new DiscoveryListener() {

            public void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) {
                System.out.println("Device " + btDevice.getBluetoothAddress() + " found");
                devicesDiscovered.addElement( btDevice );
                try {
                    System.out.println("     name " + btDevice.getFriendlyName(false));
                } catch (IOException cantGetDeviceName) {}
            }

            public void inquiryCompleted(int discType) {
                System.out.println("Device Inquiry completed!");
                synchronized(inquiryCompletedEvent){
                    inquiryCompletedEvent.notifyAll();
                }
            }

            public void serviceSearchCompleted(int transID, int respCode) {}
            public void servicesDiscovered(int transID, ServiceRecord[] servRecord) { }
        };

        synchronized(inquiryCompletedEvent) {
            boolean started = LocalDevice.getLocalDevice().getDiscoveryAgent().
                                                           startInquiry(DiscoveryAgent.GIAC, listener);
            if (started) {
                System.out.println("wait for device inquiry to complete...");
                inquiryCompletedEvent.wait();
                System.out.println(devicesDiscovered.size() +  " device(s) found");
            }
        }
    }
}

Question 5
  1. Install and test RemoteDeviceDiscovery.java:

    BlueCove download

    Windows with jar and Java file in current directory.

    javac -classpath .;bluecove-2.1.0.jar RemoteDeviceDiscovery.java

    java -classpath .;bluecove-2.1.0.jar RemoteDeviceDiscovery -d32

What is the implication of:

  1.  synchronized?
     
  2. synchronized(inquiryCompletedEvent){
                        inquiryCompletedEvent.notifyAll();
                    }
     
  3. inquiryCompletedEvent.wait();
     

 

Output

wait for device inquiry to complete...

Device 40FC898CE2B7 found
name Xoom

Device Inquiry completed!
1 device(s) found

 

Android Device Discovery

A good discussion of the following example can be found at: http://developer.android.com/guide/topics/wireless/bluetooth.html

Because Android does not provide a simple means for service discovery as illustrated for JSR 82 earlier, we must first locate discoverable Bluetooth devices, then ask each if they support the needed service. That is done later, for now we'll just discover the local devices.

  1. Checks if the device its running on has Bluetooth.
     
  2. Checks if Bluetooth is enabled and if not, requests the user to enable.
     
  3. Displays a list of paired devices and their address.
     
  4. Displays a list of discoverable devices and their address.

Note that the AndroidManifest.xml file must contain:

   <uses-permission android:name="android.permission.BLUETOOTH" />
   <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
 

AndroidRemoteDiscoveryActivity.java
package edu.ius.rwisman.AndroidRemoteDiscovery;

import android.app.ListActivity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import java.util.Set;

public class AndroidRemoteDiscoveryActivity extends ListActivity {
    private static final int REQUEST_ENABLE_BT = 2;
    
    private ArrayAdapter mArrayAdapter;                                                       // Used for displaying results in a list
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        mArrayAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1);
        this.setListAdapter(mArrayAdapter); 

        BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if (mBluetoothAdapter == null) mArrayAdapter.add("Does not support Bluetooth");
        
        if ( !mBluetoothAdapter.isEnabled()) {                                                   // Request user enable Bluetooth
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
        }
        
        Set pairedDevices = mBluetoothAdapter.getBondedDevices();	// Blocks till paired devices found

        if (pairedDevices.size() > 0) {						// Paired devices
        	mArrayAdapter.add("Paired devices");      
	
        	for (BluetoothDevice device : pairedDevices)				// Loop through paired devices        		
        		mArrayAdapter.add(device.getName() + "\n" +                        // Display name and address 
                                                    device.getAddress());
        }
     
        mArrayAdapter.add("Discovered devices");					
       
        final BroadcastReceiver mReceiver = new BroadcastReceiver() {	// BroadcastReceiver for ACTION_FOUND
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                
                if (BluetoothDevice.ACTION_FOUND.equals(action)) {		// When discovery finds a device
                                                                                                                     // Get the BluetoothDevice object from the Intent
                    BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                                                                                                                     // Display name and address
                    mArrayAdapter.add(device.getName() + "\n" + 
                                                   device.getAddress());
                }
            }
        };
        											// Register the BroadcastReceiver
        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        registerReceiver( mReceiver, filter); 					// Unregister during onDestroy  
        
        mBluetoothAdapter.startDiscovery();
    }
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
                 package="edu.ius.rwisman.AndroidRemoteDiscovery"
                android:versionCode="1"
                android:versionName="1.0">
    <uses-sdk android:minSdkVersion="13" />
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
 
   <application android:icon="@drawable/icon" android:label="@string/app_name">
       <activity android:name=".AndroidRemoteDiscoveryActivity" android:label="@string/app_name">
         <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
         </intent-filter>
      </activity>
   </application>
</manifest>

Question 6

Install and test AndroidRemoteDiscoveryActivity. Then answer the following:

  1. Set pairedDevices = mBluetoothAdapter.getBondedDevices();

    Does this find only paired devices currently in range?
     

  2. What does the following do in general?

    In detail?

    final BroadcastReceiver mReceiver = new BroadcastReceiver() {             // BroadcastReceiver for ACTION_FOUND
                public void onReceive(Context context, Intent intent) {
                    String action = intent.getAction();
                   
                    if (BluetoothDevice.ACTION_FOUND.equals(action)) {              // When device FOUND
                                                                                                                         // Get the BluetoothDevice object from the Intent
                        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                                                                                                                         // Display name and address
                        mArrayAdapter.add(device.getName() + "\n" +
                                                       device.getAddress());
                    }
                }
    };
           
    IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);     // Intent for BluetoothDevice.ACTION_FOUND
    registerReceiver( mReceiver, filter);                                                              // Register the BroadcastReceiver, unregister during onDestroy 
           
    mBluetoothAdapter.startDiscovery();


     

Service Discovery Protocol (SDP)

The first example required that the client know the server's Bluetooth device address and port, something not commonly known.

Client
import java.io.*;
import javax.microedition.io.*;
import javax.bluetooth.*;

public class RFCOMMClient {

 public static void main( String args[] ) {
   try {

      StreamConnection conn = (StreamConnection)
          Connector.open("btspp://0012D25ABDE4:3");

      DataOutputStream out = new DataOutputStream(
                                              conn.openOutputStream());
      DataInputStream in = new DataInputStream(
                                              conn.openInputStream());

      out.writeUTF("Hello");               // Write server
      String received = in.readUTF(); // Read server

      System.out.println(received);
     
      conn.close();
   }
   catch ( IOException e ) { System.err.print(e.toString()); }
 }
}

 

Services can be identified by a UUID.

The UUID Class

In Bluetooth, each service and service attribute is uniquely identified by a Universally Unique Identifier (UUID). As its name suggests, each such identifier is guaranteed to be unique across all time and space. The UUID class can represent short (16- or 32-bit) and long (128-bit) UUIDs. It provides constructors to create a UUID from a String or from a 16- or 32-bit value, a method to compare two UUIDs (if both are 128-bit), and a method to covert a UUID into a String. UUID instances are immutable, and only services identified by UUIDs are discoverable.

You can generate a UUID value with a command: uuidgen -t if you're running Linux, uuidgen if you're running Windows. The UUID will look something like this: 2d266186-01fb-47c2-8d9f-10b8ec891363. When using the generated UUID to build a UUID object, remove the hyphens.

The UUID is part of the parameter for opening a server port.

UUID HELLOSERVICE_ID = new UUID("BAE0D0C0B0A00095570605040302011", false);

Connector.open( "btspp://localhost:" + HELLOSERVICE_ID.toString() +";name=helloService");

 

Hello Client and Server protocol

Hello  Server Client
  1. Set device discoverable
  2. Open service
  3. Wait for client connection to service
  4. Write Hello World to connection
  5. Close connection
  1. Create service discovery agent
  2. Discover server providing service
  3. Connect to server
  4. Read message
  5. Display message
  6. Close connection

The client and server use a known service UUID (BAE0D0C0B0A00095570605040302011) to identify the Hello service.

The client, using the UUID, discovers the Bluetooth address and port of the service provider, the server.

For simplicity, we require that the client and server devices already be paired.

Android SDP

In the complete example below, the client knows the UUID of helloService but not the MAC address of the server.

The server advertises the UUID of helloService.

The client discovers paired devices and determines using SDP if a connection can be made to the service on a paired device.

If successful, the client and server operate as in previous examples.

             

Client

The client does not know the MAC address of the server but can discover paired or other remote devices, as we have seen in the remote device discovery example.

  1. Paired devices are discovered and the devices are added to a list.
     
  2. Using the list of paired devices, the bluetoothClient object attempts a connection to a paired device with the helloService UUID.
     
  3. If the helloService connection is successful, the client and server perform the service.
     
  4. If the helloService connection is unsuccessful, the next device in the list is tried. When the list is exhausted, the client fails and returns.
AndroidSDPHelloServiceClientActivity.java
package edu.ius.rwisman.AndroidSDPHelloServiceClient;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.util.Set;
import java.util.UUID;
import java.util.Vector;
import android.app.ListActivity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.widget.LinearLayout;
import android.widget.ArrayAdapter;

public class AndroidSDPHelloServiceClientActivity extends ListActivity {
	private static final int REQUEST_ENABLE_BT = 2;
	LinearLayout layout;

	BluetoothAdapter mBluetoothAdapter;

	private ArrayAdapter<String> mArrayAdapter;
	private Vector <BluetoothDevice> bluetoothDevices = new Vector <BluetoothDevice>();

	final Handler handler = new Handler();

	final Runnable updateUI = new Runnable() {
		public void run() {
			mArrayAdapter.add(bluetoothClient.getBluetoothClientData());
		}
	};

	BluetoothClient bluetoothClient;

	@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        mArrayAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1);
        this.setListAdapter(mArrayAdapter); 

        BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if (mBluetoothAdapter == null) mArrayAdapter.add("Device does not support Bluetooth");
        
        if (!mBluetoothAdapter.isEnabled()) {
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
        }
        
        Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
        if (pairedDevices.size() > 0) {
        	mArrayAdapter.add("Paired devices");        	
        	for (BluetoothDevice device : pairedDevices) {				             // Loop through paired devices
        		mArrayAdapter.add(device.getName() + "\n" + device.getAddress());   // Add the name/address to array adapter for ListView
        		bluetoothDevices.add(device);
        	}
        }

        bluetoothClient = new BluetoothClient(handler, updateUI, bluetoothDevices);
        bluetoothClient.start();
     }
}

class BluetoothClient extends Thread {

	BluetoothAdapter mBluetoothAdapter;
	Vector <BluetoothDevice> bluetoothDevices=null;
	private String data = null;

	final Handler handler;
	final Runnable updateUI;

	public BluetoothClient(Handler handler, Runnable updateUI, Vector <BluetoothDevice> bluetoothDevices) {
		this.handler = handler;
		this.updateUI = updateUI;
		this.bluetoothDevices = bluetoothDevices;

		mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
	}

	public String getBluetoothClientData() {
		return data;
	}

	public void run() {
		BluetoothSocket clientSocket = null;
		Boolean connection = false;
		
		// Client discovers the MAC address of server, if one exists
		for (BluetoothDevice device : bluetoothDevices) 
		   try { 
			Log.d("EchoSDP Client", device.getName());
																					
			clientSocket = device.createRfcommSocketToServiceRecord(UUID.fromString("0BAE0D0C-0B0A-0009-5570-605040302011"));
			clientSocket.connect();
			connection = true;
			break;
		   }
		   catch(Exception e) { Log.d("EchoSDP Client fail!!!", device.getName());  }

		mBluetoothAdapter.cancelDiscovery(); // Cancel, discovery slows connection
		
		if(connection)
			try {
			DataInputStream in = new DataInputStream( clientSocket.getInputStream());
			DataOutputStream out = new DataOutputStream( clientSocket.getOutputStream());

			out.writeUTF("Hello"); 	// Send to server

			data = in.readUTF(); 	// Read from server
			handler.post(updateUI);
			
			clientSocket.close();
		} catch (Exception e) {}
		else {
			data = "Server not found";
			handler.post(updateUI);
		}
	}
}

JSR-82 Server

The server defines the service name helloService with a UUID of BAE0D0C0B0A00095570605040302011 as an identification when discovering services. Note that in Java the server port cannot be defined but is selected dynamically, requiring service discovery by the client.

import java.io.*;
import javax.microedition.io.*;
import javax.bluetooth.*;

public class RFCOMMServer {
    public static void main(String args[]) {
      try {

         StreamConnectionNotifier service = (StreamConnectionNotifier) Connector.open("btspp://localhost:"
                            + new UUID("BAE0D0C0B0A00095570605040302011",false).toString()
                            + ";name=helloService");

         StreamConnection conn = (StreamConnection) service.acceptAndOpen();
         System.out.println("Connected");

         DataInputStream in = new DataInputStream(conn.openInputStream());
         DataOutputStream out = new DataOutputStream(conn.openOutputStream());

         String received = in.readUTF(); // Read from client

         out.writeUTF("Echo: " + received); // Send Echo to client

         conn.close();
         service.close();

      } catch (IOException e) { System.err.print(e.toString());   }
   }
}

Question 7

  1. Explain code 1-4.
    1.   for (BluetoothDevice device : bluetoothDevices)
    2.     try {
                clientSocket = device.createRfcommSocketToServiceRecord(UUID.fromString("0BAE0D0C-0B0A-0009-5570-605040302011"));
                clientSocket.connect();
                connection = true;
                break;
            }
            catch(Exception e) { Log.d("EchoSDP Client fail!!!", device.getName());  }

    3.   mBluetoothAdapter.cancelDiscovery();                                                                         // Cancel, discovery slows connection

    4.   if(connection)
                  try {
                            DataInputStream in = new DataInputStream( clientSocket.getInputStream());
                            DataOutputStream out = new DataOutputStream( clientSocket.getOutputStream());

                            out.writeUTF("Hello");                                                                               // Send to server

                            data = in.readUTF();                                                                                 // Read from server
                            handler.post(updateUI);

                            clientSocket.close();
                  } catch (Exception e) {}
          else {
                data = "Server not found";
                handler.post(updateUI);
          }
  2. To test software and hardware configurations, do the following:

Pair

Android: Settings | Bluetooth | select one of available devices

Windows: Start | Right click | Control panel | Add device | select Android device

Download

Java SDK jdk-7u3-windows-i586.exe

BlueCove

RFCOMMServer.java
In LF111 or with above JDK use:
C:"\Program Files (x86)\Java\jdk1.7.0_03\bin\"javac -classpath .;bluecove-2.1.0.jar RFCOMMServer.java

C:"\Program Files (x86)\Java\jdk1.7.0_03\bin\"java -d32 -classpath .;bluecove-2.1.0.jar RFCOMMServer

AndroidSDPHelloServiceClientActivity

Change Hello to your name.

See http://developer.android.com/tools/extras/oem-usb.html#Win7 to install USB drivers to Android device.

 

 

JSR-82 Service discovery - Can be skipped

The above client discovered a device providing a service, appropriate if only one device provides the service or any device is acceptable.

When multiple devices provide the same service UUID and the device matters, we need to discover devices that provide the service. The UUID for common services such as email or an Internet gateway could be the same but only some specific device would be appropriate.

Remember that the client must ultimately determine the server address and the service port in order to make a connection by something similar to:

Connector.open("btspp://0012D25ABDE4:3")

The process is to first discover all the devices in range, then discover whether the service is provided by each device. If the desired service is discovered on a device, then the connection can be made.

JSR 82 does not provide a convenience method for combined device and service discovery, hence the following.

Device Discovery

The address and name of enabled devices within range can be discovered by other Bluetooth devices, discovery can take some time (multiple seconds) to complete, given that radio communications is unreliable and devices may miss transmissions.

The following displays address and name of all enabled devices nearby. Note that:

DiscoveryListener

JSR 82 provides a search service agent that reports device discoveries through a DiscoveryListener interface. The listener must implement the following methods:

  1. public void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) { }
  2. public void inquiryCompleted(int discType) { }
  3. public void servicesDiscovered(int transID, ServiceRecord[] servRecord) { }
  4. public void serviceSearchCompleted(int transID, int respCode) {}

For device discovery, only 1 and 2 need to be functional.

import java.io.IOException;
import java.util.Vector;
import javax.bluetooth.*;

public class RemoteDeviceDiscovery {

    public static final Vector <RemoteDevice> devicesDiscovered = new Vector();

    public static void main(String[] args) throws IOException, InterruptedException {

        final Object inquiryCompletedEvent = new Object();

        devicesDiscovered.clear();
DiscoveryListener listener = new DiscoveryListener() {

            public void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) {
                System.out.println("Device " + btDevice.getBluetoothAddress() + " found");
                devicesDiscovered.addElement( btDevice );
                try {
                    System.out.println("     name " + btDevice.getFriendlyName(false));
                } catch (IOException cantGetDeviceName) {
                }
            }

            public void inquiryCompleted(int discType) {
                System.out.println("Device Inquiry completed!");
                synchronized(inquiryCompletedEvent){
                    inquiryCompletedEvent.notifyAll();
                }
            }

            public void serviceSearchCompleted(int transID, int respCode) {}
            public void servicesDiscovered(int transID, ServiceRecord[] servRecord) { }
        };

        synchronized(inquiryCompletedEvent) {
            boolean started = LocalDevice.getLocalDevice().getDiscoveryAgent().startInquiry(DiscoveryAgent.GIAC, listener);
            if (started) {
                System.out.println("wait for device inquiry to complete...");
                inquiryCompletedEvent.wait();
                System.out.println(devicesDiscovered.size() +  " device(s) found");
            }
        }
    }
}

Output

wait for device inquiry to complete...
 Device 0012D25ABDE4 found
   name DELL01
   Device Inquiry completed!
   1 device(s) found

 
Try
  1. Save the above file as:

    RemoteDeviceDiscovery.java

  2. Compile and execute by:

    javac -classpath bluecove-2.1.0.jar RemoteDeviceDiscovery.java

    java -classpath .;bluecove-2.1.0.jar RemoteDeviceDiscovery

  3. If the search takes too long (more than a minute or two), terminate by Ctrl C.

 

Service Discovery

Service discovery first requires device discovery, the RemoteDeviceDiscovery is reused to fill the devicesDiscovered Vector with devices.

Using JSR 82, in principle one needs to know the UUID of the service rather than the service name for discovery; here the helloService is assigned the UUID of 0x1101. searchServices() method searches each device for advertised services, using a list of UUIDs supplied; in this case UUID of 0x1101 is the helloService number. In fact, BlueCove seems to search for any publicly advertised service and all are returned whether or not a UUID list is supplied.

The following displays address and name of all enabled devices nearby. Note that:

DiscoveryListener

JSR 82 provides a search service agent that reports service discoveries through a DiscoveryListener interface. The listener must implement the following methods:

  1. public void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) { }
  2. public void inquiryCompleted(int discType) { }
  3. public void servicesDiscovered(int transID, ServiceRecord[] servRecord) { }
  4. public void serviceSearchCompleted(int transID, int respCode) {}

For service discovery, only 3 and 4 need to be functional.

 

import java.io.IOException;
import java.util.Enumeration;
import java.util.Vector;

import javax.bluetooth.*;

public class ServicesSearch {

   static final UUID HELLO_SERVICE = new UUID(0x1101);

   public static final Vector<String> serviceFound = new Vector();

   public static void main(String[] args) throws IOException, InterruptedException {

      UUID[] searchUuidSet = new UUID[] { HELLO_SERVICE };
      int[] attrIDs = new int[] { 0x0100 }; // Service name

      RemoteDeviceDiscovery.main(null);    // First run RemoteDeviceDiscovery and use discovered devices

      serviceFound.clear();

      final Object serviceSearchCompletedEvent = new Object();

DiscoveryListener listener = new DiscoveryListener() {
         public void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) { }
         public void inquiryCompleted(int discType) { }

         public void servicesDiscovered(int transID, ServiceRecord[] servRecord) {
               for (int i = 0; i < servRecord.length; i++) {
                  String url = servRecord[i].getConnectionURL(ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
                  if (url == null) { continue; }
                  serviceFound.add(url);
                  DataElement serviceName = servRecord[i].getAttributeValue(0x0100);
                  if (serviceName != null) {
                     System.out.println("service " + serviceName.getValue() + " found " + url);
                  } else {
                     System.out.println("service found " + url);
                  }
               }
         }
         public void serviceSearchCompleted(int transID, int respCode) {
            System.out.println("service search completed!");

            synchronized( serviceSearchCompletedEvent ){
                serviceSearchCompletedEvent.notifyAll();
            }
         }
      };
 

      for(Enumeration en = RemoteDeviceDiscovery.devicesDiscovered.elements(); en.hasMoreElements(); ) {
         RemoteDevice btDevice = (RemoteDevice)en.nextElement();

         synchronized( serviceSearchCompletedEvent ) {
            System.out.println("search services on " +  btDevice.getBluetoothAddress() + " " + btDevice.getFriendlyName(false));

            LocalDevice.getLocalDevice().getDiscoveryAgent().searchServices(attrIDs, searchUuidSet, btDevice, listener);

            serviceSearchCompletedEvent.wait();
         }
      }
   }
}

 

Output

wait for device inquiry to complete...
 Device 0012D25ABDE4 found
   name DELL01
   Device Inquiry completed!
   1 device(s) found

search services on 0012D25ABDE4 DELL01
  service helloService found btspp://0012D25ABDE4:3;authenticate=false;encrypt=false;master=false
  service Bluetooth Serial Port found btspp://0018F8898BA5:1;authenticate=false;encrypt=false;master=false
service search completed!

 

Try
  1. Copy the above file to:
    • ServicesSearch.java
  2. Compile by:

    javac -classpath bluecove-2.1.0.jar ServicesSearch.java

  3. Execute on two different machines by:

    java -classpath .;bluecove-2.1.0.jar RFCOMMServer -d32

    java -classpath .;bluecove-2.1.0.jar ServicesSearch -d32

  4. The helloService should be displayed. Locate the address and port used by the service.
  5. Edit RFCOMMClient.java line:

     String url = "btspp://0012D25ABDE4:3";

     changing the 0012D25ABDE4:3 to the address and port used by the helloService.

  6. Compile and execute RFCOMMClient.

 

 

Peer-to-Peer (P2P)

P2P as used here means that the application can act as either client or server.

The simplest approach, using SDP, is to:

  1. start as a client,
     
  2. discover a service server and connect as a client,
     
  3. if no service server discovered, open a server socket, advertize the service and wait for a connection.
     

Basic P2P Example

A simplified Android example follows of a P2P Echo.

If acting as a client, it sends Hello and waits for a reply message. If acting as a server, it waits for a client message, sending Echo prepended to the client message.

Nothing is displayed on the UI.

AndroidP2P
public class AndroidP2P extends Activity {
   Activity myActivity;

   public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        myActivity = this;
 
        new BluetoothP2P(myActivity).start();
   }
}

class BluetoothP2P extends Thread {
	private static final int REQUEST_ENABLE_BT = 2;
	BluetoothAdapter mBluetoothAdapter;
	BluetoothSocket socket = null;
	final UUID IMSERVICE_ID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
	Activity activity;

	public BluetoothP2P(Activity activity) {
		this.activity = activity;
		this.mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
	}

	public void run() {
		Boolean connection = false;

		if (!mBluetoothAdapter.isEnabled()) {
			Intent enableBtIntent = new Intent( BluetoothAdapter.ACTION_REQUEST_ENABLE);
			activity.startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
		}
										           // Get all devices paired with this one.	
		Set <BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();	

		mBluetoothAdapter.cancelDiscovery(); 				// Cancel, discovery slows connection

		                                                                                                // Discover MAC address of server
		if (pairedDevices.size() > 0)
			for (BluetoothDevice device : pairedDevices) 		// Loop through paired devices
				try {
					socket = device.createRfcommSocketToServiceRecord(IMSERVICE_ID);
					socket.connect();
					connection = true;
					break;						// Break if connect to server
				} catch (Exception e) {}

		if (connection) {							// Client
			in = new DataInputStream(socket.getInputStream());
			out = new DataOutputStream(socket.getOutputStream());
							
			out.writeUTF("Hello");
			String s = in.readUTF();
			System.out.println( s );
		} else {								// Server
			BluetoothServerSocket serverSocket;
			try {
				serverSocket = mBluetoothAdapter
						.listenUsingRfcommWithServiceRecord("IMService",
								IMSERVICE_ID);
				socket = serverSocket.accept();
				serverSocket.close();
				in = new DataInputStream(socket.getInputStream());
				out = new DataOutputStream(socket.getOutputStream());

				String s = in.readUTF();
				out.writeUTF("Echo: "+s);
				System.out.println( s );
			} catch (Exception e) {}
		}
	}
}

Question 8 - Explain the function of each set of code 1-4.

1. Set <BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();                // Get all devices paired with this one.

    mBluetoothAdapter.cancelDiscovery();
                                                                                                                                 // Discover MAC address of server
2. if (pairedDevices.size() > 0)
       for (BluetoothDevice device : pairedDevices) // Loop through paired devices
              try {
                     socket = device.createRfcommSocketToServiceRecord(IMSERVICE_ID);
                     socket.connect();
                     connection = true;
                     break;                                                                                                    // Break if connect to server
              } catch (Exception e) {}

3. if (connection) {                                                                                                       // Client
       in = new DataInputStream(socket.getInputStream());
       out = new DataOutputStream(socket.getOutputStream());

       out.writeUTF("Hello");
       String s = in.readUTF();
       System.out.println( s );
   } else {                                                                                                                     // Server
4.    BluetoothServerSocket serverSocket;
       try {
              serverSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord("IMService",IMSERVICE_ID);
              socket = serverSocket.accept();
              serverSocket.close();
              in = new DataInputStream(socket.getInputStream());
              out = new DataOutputStream(socket.getOutputStream());

              String s = in.readUTF();
              out.writeUTF("Echo: "+s);
              System.out.println( s );
       } catch (Exception e) {}
  }

 

The previous example couples managing Bluetooth connections with the client/server functions.

The following example decouples connections from client/server functions.

The key difference is the class BluetoothP2P performs a call-back to a ConnectionListener with:

  1. whether connected as client or server
     
  2. the connected socket to use for the Bluetooth communication.

Testing

  1. With two Android devices, pair the devices and execute.
     
  2. With one Android device running as the server:

    Execute the app, it should start as a server.

    Run the Java client below.
     

  3. With one Android device running as the client:

    Run the Java server below.

    Execute the app, it should start as a client.

AndroidSDPHelloServicePeerActivity.java
package edu.ius.rwisman.AndroidSDPHelloServicePeer;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.util.Set;
import java.util.UUID;
import android.app.Activity;
import android.app.ListActivity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.Bundle;
import android.widget.LinearLayout;
import android.widget.ArrayAdapter;

interface ConnectionListener {
	public void setSocketServer(BluetoothSocket socket, boolean server);
	public void setMessage(final String msg);
}

public class AndroidSDPHelloServicePeerActivity extends ListActivity implements ConnectionListener {
	LinearLayout layout;
	private DataInputStream in = null;
	private DataOutputStream out = null;
	Activity myActivity;

	private ArrayAdapter mArrayAdapter;

	@Override
           public void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              myActivity = this;
        
              mArrayAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1);
              this.setListAdapter(mArrayAdapter); 

             new BluetoothP2P(myActivity, this).start();
           }
	
	// ConnectionListener methods
	public void setSocketServer(BluetoothSocket socket, boolean server) {
		try {
			in = new DataInputStream(socket.getInputStream());
			out = new DataOutputStream(socket.getOutputStream());
			if( server ) 
                             	        doServer();
			else  doClient();
		} catch (Exception e) {}
	}

	public void setMessage(final String s) {				// Callback on BluetoothP2P thread
		myActivity.runOnUiThread(new Runnable() {
			public void run() {
	       		mArrayAdapter.add(s);
			}
		});
	}
	
	// Bluetooth Input/Output
	private void doClient() {
		new Thread(new Runnable() {
			public void run() {
				try {
					out.writeUTF("Hello");
					String s = in.readUTF();
					setMessage(s);
				} catch (Exception e) {}
			}
		}).start();
	}

	private void doServer() {
		new Thread(new Runnable() {
			public void run() {
				try {
					String s = in.readUTF();
					out.writeUTF("Echo: "+s);
					setMessage(s);
				} catch (Exception e) {}
			}
		}).start();
	}
}

class BluetoothP2P extends Thread {
	private static final int REQUEST_ENABLE_BT = 2;
	BluetoothAdapter mBluetoothAdapter;
	BluetoothSocket socket = null;
	Boolean server = true;
	final UUID IMSERVICE_ID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
	Activity activity;
	ConnectionListener delegate;

	public BluetoothP2P(Activity activity, ConnectionListener delegate) {
		this.activity = activity;
		this.delegate = delegate;
		this.mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
	}

	public void run() {
		Boolean connection = false;
		delegate.setMessage("Waiting as client");

		if (!mBluetoothAdapter.isEnabled()) {
			Intent enableBtIntent = new Intent( BluetoothAdapter.ACTION_REQUEST_ENABLE);
			activity.startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
		}
											// Get all devices paired with this one.
		Set <BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
		mBluetoothAdapter.cancelDiscovery(); 				// Cancel, discovery slows connection

		                                                                                                // Discover MAC address of server
		if (pairedDevices.size() > 0)
			for (BluetoothDevice device : pairedDevices) 		// Loop through paired devices
				try {
					socket = device.createRfcommSocketToServiceRecord(IMSERVICE_ID);
					delegate.setMessage(device.getName() + "\n   " + device.getAddress());
					socket.connect();
					connection = true;
					break;
				} catch (Exception e) {}

		if (connection) {
			delegate.setSocketServer(socket, false);		// Call-back as a client
		} else {
			delegate.setMessage("Waiting as server");
			BluetoothServerSocket serverSocket;
			try {
				serverSocket = mBluetoothAdapter
						.listenUsingRfcommWithServiceRecord("IMService",
								IMSERVICE_ID);
				socket = serverSocket.accept();
				serverSocket.close();

				delegate.setSocketServer(socket, true);	// Call-back as a server
			} catch (Exception e) {}
		}
	}
}

Question 9 - Explain the function of each set of code 1 and 2.

1. public void setSocketServer(BluetoothSocket socket, boolean server) {
try {
in = new DataInputStream(socket.getInputStream());
out = new DataOutputStream(socket.getOutputStream());
if( server )
                                     doServer();
else  doClient();
} catch (Exception e) {}
}

           public void run() {
                              :
if (pairedDevices.size() > 0)
for (BluetoothDevice device : pairedDevices) // Loop through paired devices
try {
socket = device.createRfcommSocketToServiceRecord(IMSERVICE_ID);
delegate.setMessage(device.getName() + "\n   " + device.getAddress());
socket.connect();
connection = true;
break;
} catch (Exception e) {}

                      if (connection) {
delegate.setSocketServer(socket, false); // Call-back as a client
} else {
delegate.setMessage("Waiting as server");
BluetoothServerSocket serverSocket;
try {
serverSocket = mBluetoothAdapter
.listenUsingRfcommWithServiceRecord("IMService",
IMSERVICE_ID);
socket = serverSocket.accept();
serverSocket.close();

delegate.setSocketServer(socket, true); // Call-back as a server
} catch (Exception e) {}
}

2. private void doClient() {
new Thread(new Runnable() {
public void run() {
try {
out.writeUTF("Hello");
String s = in.readUTF();
setMessage(s);
} catch (Exception e) {}
}
}).start();
}

private void doServer() {
new Thread(new Runnable() {
public void run() {
try {
String s = in.readUTF();
out.writeUTF("Echo: "+s);

setMessage(s);
} catch (Exception e) {}
}
}).start();
}

 

JSR-82 Server for testing with one Android device

import java.io.*;
import javax.microedition.io.*;
import javax.bluetooth.*;

public class RFCOMMServer {
    public static void main(String args[]) {
      try {

         StreamConnectionNotifier service = (StreamConnectionNotifier) Connector.open("btspp://localhost:"
                            + new UUID("0000110100001000800000805F9B34FB",false).toString()
                            + ";name=helloService");

         StreamConnection conn = (StreamConnection) service.acceptAndOpen();
         System.out.println("Connected");

         DataInputStream in = new DataInputStream(conn.openInputStream());
         DataOutputStream out = new DataOutputStream(conn.openOutputStream());

         String received = in.readUTF(); // Read from client

         out.writeUTF("Echo: " + received); // Send Echo to client

         conn.close();
         service.close();

      } catch (IOException e) { System.err.print(e.toString());   }
   }
}

JSR-82 Client for testing with one Android device

BluetoothEchoClient.java
import java.io.*;
import javax.bluetooth.*;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;

public class BluetoothEchoClient {

	public static void main(String args[]) {

		UUID HELLOSERVICE_ID = new UUID("0000110100001000800000805F9B34FB",false);

		DiscoveryAgent mDiscoveryAgent;
		String url = null;

		try {
			mDiscoveryAgent = LocalDevice.getLocalDevice().getDiscoveryAgent();

			url = mDiscoveryAgent.selectService(HELLOSERVICE_ID, 
                                                         ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);

			if (url == null) {
				System.out.println("Discovery failed: " + HELLOSERVICE_ID);
				return;
			}
			System.out.println("URL:" + url + "\n");

			StreamConnection conn = (StreamConnection) Connector.open(url);

			DataInputStream in = new DataInputStream(conn.openInputStream());
			DataOutputStream out = new DataOutputStream(conn.openOutputStream());

			out.writeUTF("Hello"); // Send server
			String received = in.readUTF(); // Read server
			System.out.println(received);

			conn.close();
		} catch (Exception e) {
			System.out.print("Exception: " + e.toString() + "\n");
		}
	}
}	

 

P2P Instant Messaging (IM)

Instant Message app is a somewhat more realistic P2P example, similar to the MVC TicTacToe. One difference is that no Model is necessary since state (the chat text) is stored as visual objects in the View.

The images below illustrate the IM JSR-82 server running on the console and the Android client.



Waiting for Connect button press.


Connected as client.

 

 

 


Server receives and echoes.

 

Establishing a P2P Bluetooth connection is the key purpose of the example which is performed by class BluetoothP2P.

BluetoothP2P, when a connection is made, performs a call-back to the ConnectionListener method setSocketServer().

ConnectionListener has the following interface:

interface ConnectionListener {
    public void setSocketServer(BluetoothSocket socket, boolean server);
    public void setMessage(final String msg);
}

A simple Java IM server-only program provides for testing with only one Android device. It echoes back whatever is typed on the Android device running as a client.

AndroidIMBluetoothP2P.java
package edu.ius.rwisman.AndroidIMBluetoothP2P;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.ViewGroup;

public class AndroidIMBluetoothP2PActivity extends Activity {
	MyController myController = null;
	MyView myView;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		try {
			super.onCreate(savedInstanceState);
			setContentView(R.layout.main);

			myView = new MyView(this);

			myController = new MyController(this, myView);

			ViewGroup container = (ViewGroup) findViewById(R.id.myLayout);
			container.addView(myView);
			container.addView(myController);
		} catch (Exception e) {
			Log.e("ERROR", "ERROR IN CODE: " + e.toString());
			e.printStackTrace();
		}
	}
}
MyController.java
package edu.ius.rwisman.AndroidIMBluetoothP2P;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.util.Set;
import java.util.UUID;
import android.content.Context;
import android.content.Intent;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.Button;

interface ConnectionListener {
	public void setSocketServer(BluetoothSocket socket, boolean server);
	public void setMessage(final String msg);
}

class MyController extends LinearLayout implements ConnectionListener, TextWatcher {
	final MyView myView;
	View view;
	private DataInputStream in = null;
	private DataOutputStream out = null;
	Boolean server = true;
	Boolean connected = false;
	final Activity myActivity;
	BluetoothSocket socket;
	
	public MyController(final Activity activity, final MyView myView) {
		super(activity, null);
		this.myView = myView;
		myActivity = activity;
		
		myView.getEditText().addTextChangedListener(this);

		LayoutInflater layoutInflater = (LayoutInflater) activity
				.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		view = layoutInflater.inflate(R.layout.keyboard, this);

		myView.setViewMessage("Connect to start");
		Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
		discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
		activity.startActivity(discoverableIntent);

		final Button disconnectButton = (Button) view.findViewById(R.id.buttonDisconnect);
		disconnectButton.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				try {
					socket.close();
					in.close();
					out.close();
				} catch (Exception e) {}
				connected = false;
			}
		});

		final Button buttonConnect = (Button) view.findViewById(R.id.buttonConnect);
		buttonConnect.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				new BluetoothP2P(myActivity, MyController.this).start();
			}
		});
	}
	
	// ConnectionListener methods
	public void setSocketServer(BluetoothSocket socket, boolean server) {
		this.server = server;
		this.socket = socket;
		connected = true;

		try {
			in = new DataInputStream(socket.getInputStream());
			out = new DataOutputStream(socket.getOutputStream());
		} catch (Exception e) {
			connected = false;
		}

		final String message = connected ? "Connected" : "Connection failed";

		myActivity.runOnUiThread(new Runnable() {
			public void run() {
				myView.setViewMessage(message);
			}
		});
		getRemote();
	}

	public void setMessage(final String s) {
		myActivity.runOnUiThread(new Runnable() {
			public void run() {
				myView.setViewMessage(s);
			}
		});
	}
	// TextWatcher methods
	public void onTextChanged (CharSequence cs, int start, int before, int count){
		String s = cs.subSequence(start+count-1, start+count).toString();
		sendRemote(s);
	}
	public void afterTextChanged(Editable s){}
	public void beforeTextChanged(CharSequence s, int start, int count, int after){}

	public void setRemote(final String s) {
		myActivity.runOnUiThread(new Runnable() {
			public void run() {
				myView.setViewRemote(s);
			}
		});
	}

	// Bluetooth Input/Output
	private void sendRemote(final String s) {
		new Thread(new Runnable() {
			public void run() {
				try {
					out.writeUTF(s);
				} catch (Exception e) {}
			}
		}).start();
	}

	private void getRemote() {				// Note: returns immediately after start()
		new Thread(new Runnable() {
			public void run() {
			      while(connected)
				try {
					String s = in.readUTF();

					setRemote(s);	// Display received text
				} catch (Exception e) {
					connected=false;
				}
			      setMessage("Disconnected");
			}
		}).start();
	}
}

class BluetoothP2P extends Thread {
	private static final int REQUEST_ENABLE_BT = 2;
	BluetoothAdapter mBluetoothAdapter;
	BluetoothSocket socket = null;
	Boolean server = true;
	final UUID IMSERVICE_ID = UUID.fromString("00001101-0000-1000-8000-00805F9B34F7");
	Activity activity;
	ConnectionListener delegate;

	public BluetoothP2P(Activity activity, ConnectionListener delegate) {
		this.activity = activity;
		this.delegate = delegate;
		this.mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
	}

	public void run() {
		Boolean connection = false;
		delegate.setMessage("Waiting as client");

		if (!mBluetoothAdapter.isEnabled()) {
			Intent enableBtIntent = new Intent( BluetoothAdapter.ACTION_REQUEST_ENABLE);
			activity.startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
		}
										// Get all devices paired with this one.
		Set pairedDevices = mBluetoothAdapter.getBondedDevices();
		mBluetoothAdapter.cancelDiscovery(); 			// Cancel, discovery slows connection

										// Client discovers server MAC address 
		if (pairedDevices.size() > 0)					// if one exists
			for (BluetoothDevice device : pairedDevices) 	// Loop through paired devices
				try {
					socket = device.createRfcommSocketToServiceRecord(IMSERVICE_ID);
					socket.connect();
					connection = true;
					break;
				} catch (Exception e) {}

		if (connection) {
			delegate.setSocketServer(socket, false);
		} else {
			delegate.setMessage("Waiting as server");
			BluetoothServerSocket serverSocket;
			try {
				serverSocket = mBluetoothAdapter
						.listenUsingRfcommWithServiceRecord("IMService",
								IMSERVICE_ID);
				socket = serverSocket.accept();
				serverSocket.close();
				delegate.setSocketServer(socket, true);
			} catch (Exception e) {}
		}
 	}
}
MyView.java
package edu.ius.rwisman.AndroidIMBluetoothP2P;

import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;

class MyView extends LinearLayout {	
	private EditText local;
	private TextView remote;
	private TextView message;

	public MyView(Activity activity) {
		super(activity, null);
		LayoutInflater layoutInflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		layoutInflater.inflate(R.layout.display, this);
		local = (EditText) findViewById(R.id.local);
		remote = (TextView) findViewById(R.id.remote);
		message = (TextView) findViewById(R.id.message);
	}
	
	public void setViewLocal(String s) { local.setText(s); }
	public void setViewRemote(String s) { remote.append((CharSequence) s); }
	public void setViewMessage(String s) { message.setText(s); }
	public EditText getEditText() { return local; }
}

IM Server

Allows testing with only one Android device running AndroidIMBluetoothP2P. The server echoes back whatever is typed on the Android device running as a client.

IMBlueToothServer.java
import java.io.*;
import javax.microedition.io.*;
import javax.bluetooth.*;

public class IMBlueToothServer {

	public static void main(String args[]) {
		try {

			StreamConnectionNotifier service = (StreamConnectionNotifier) Connector
					.open("btspp://localhost:"
							+ new UUID("0000110100001000800000805F9B34F7",
									false).toString() + ";name=helloService");

			StreamConnection conn = (StreamConnection) service.acceptAndOpen();
			System.out.println("Connected");

			DataInputStream in = new DataInputStream(conn.openInputStream());
			DataOutputStream out = new DataOutputStream(conn.openOutputStream());

			boolean running=true;
			while(running) {
				String received = in.readUTF();	// Read from client
				System.out.print(received);
				out.writeUTF(received);		// Send Echo to client
			}
			conn.close();
			service.close();
		} catch (IOException e) {
			System.err.println(e.toString());
		}
	}
}

Question 10 - Explain the function of each set of code 1-4.

public void setMessage(final String s) {
myActivity.runOnUiThread(new Runnable() {
public void run() {
myView.setViewMessage(s);
}
});
 }

 public void setRemote(final String s) {
myActivity.runOnUiThread(new Runnable() {
public void run() {
myView.setViewRemote(s);
}
});
  }

1. class MyController extends LinearLayout implements ConnectionListener, TextWatcher {

2. Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
    discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
    activity.startActivity(discoverableIntent);

    // Bluetooth Input/Output
3. private void sendRemote(final String s) {
new Thread(new Runnable() {
public void run() {
try {
out.writeUTF(s);
} catch (Exception e) {}
}
}).start();
     }

4. private void getRemote() {
new Thread(new Runnable() {
public void run() {
      while(connected)
try {
String s = in.readUTF();

setRemote(s); // Display received text
} catch (Exception e) {
connected=false;
}
      setMessage("Disconnected");
}
}).start();
    }