Building an Advanced Java Game on Symbian OS (part 4) - Using the Bluetooth API

1/3/2012 4:07:35 PM

7. Using the Bluetooth API (JSR-82)

Multiplayer games are one of the fastest growing areas in the games industry and one of the great things about mobile phones is multiplayer games over Bluetooth. Bluetooth is a wireless radio technology that operates in the 2.4 GHz range over distances of up to 10 meters. These days, we use Bluetooth to connect all types of peripheral devices so it is a great technology to know how to use. Before proceeding, we recommend reading an excellent series of articles on multiplayer games available from the Sun Developers' Network,[] which gives a great background into a number of concepts.


Bluetooth can be reasonably difficult to understand at first because, unlike most other areas of networking, the actual topology of the network can be very dynamic as new devices may come in and out of range at any time. Even the process of establishing a connection between two Bluetooth devices is unusual because connections are initiated from the server (or 'master') device to clients that listen on server sockets. Most examples you find online mix UI code directly in with connection management code and events, often making it difficult to focus on the key concepts.

The Bluetooth code included in our sample game has been taken directly from Forum Nokia. The main changes we have introduced are the use of an observer class and simplification of the code to support only two players. If you wish to learn about Bluetooth technologies in detail, these articles are required reading as we cannot cover everything here.

Selecting the Multi option from the main game menu (see Figure 4) displays the multiplayer menu shown in Figure 8. In this game, if you choose Player1 you are the 'Master' (or game server, if you like). If you choose Player2, you are the 'Slave'.

Figure 8. Selecting the role

Before connections can be established between Bluetooth devices, the Bluetooth stack must be initialized, devices must be found (known as the inquiry phase) and then the available services on each device must be queried (known as the discovery phase).

In the inquiry phase, the master device detects other devices in the area in order to determine their types and Bluetooth addresses. The type of device is referred to as the class of device (CoD). The CoD is simply a number that indicates whether the remote device is a computer, an audio headset, or a mobile phone. In JSR-82, this is represented by the DeviceClass class. You retrieve the CoD by calling the getMajorDeviceClass() method, which returns an integer. These integers are well-known, centrally assigned fixed numbers – for example, mobile phones have the value 0x0200.

There are two modes commonly used to find other devices – the General Inquiry Access Code (GIAC) and the Limited Inquiry Access Code (LIAC). The Symbian OS Bluetooth stack does not support the use of the LIAC so we won't discuss it any further here.

The following code snippet shows how to start a search for nearby devices using JSR-82 classes:

// this throws a BluetoothStateException if Bluetooth is not enabled,
// which is a good way to check that it is before proceeding
LocalDevice localDevice = LocalDevice.getLocalDevice();
// start an inquiry for other devices in GIAC mode
DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent();
discoveryAgent.startInquiry(DiscoveryAgent.GIAC, listener);

Notice the listener variable passed on the last line above. This is a reference to an object that implements the DiscoveryListener interface which defines a handler interface for a series of callbacks that occur during the inquiry and discovery phases. To stop an inquiry in progress, you can also pass this handler to the cancelInquiry() method of the DiscoveryAgent class. For reference, this interface is shown below:

public interface DiscoveryListener
// called once for each device found during the inquiry phase
void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod);
void inquiryCompleted(int discType);
// called during service discovery
void servicesDiscovered(int transID, ServiceRecord[] servRecord);
void serviceSearchCompleted(int transID, int respCode);

Once a set of devices matching the desired CoD has been located, they need to be queried in order to find out what services (identified using UUIDs) they offer. In the case of a Bluetooth game, we are interested in the game 'service' that we have built. Each device is queried in turn and the servicesDiscovered() callback method is called on the supplied listener, passing an array of ServiceRecord objects. These represent the various attributes of services found on the remote device. You can specify what attributes (such as, service name) that you want returned in this process as part of the discovery setup:

// an array of service ids we want to find
UUID[] uuid = new UUID[1];
RemoteDevice remoteDevice;
// include service name attribute in returned service records
int attribs[] = new int[]{0x0100};
discoveryAgent.searchServices(attribs, uuid, remoteDevice, listener);

The discovery process callback methods are also supplied with a transaction identifier that can be used to stop an active service discovery process by passing this to the cancelServiceSearch() method of the DiscoveryAgent class.

Once devices and services have been located and queried, we are ready to open a connection. As already mentioned, setting up a Bluetooth connection can be a little confusing at first because, unlike in other networking scenarios, connections are initiated by the server rather than the client. To make sure that our design remains clean, we've defined an observer interface that is notified whenever our Bluetooth state changes:

public interface IBluetoothObserver {
public void notifyStateChange(int newState);
public void notifyMessageReceived(String message);

In Figure 8.5, note that the GameController class implements the IBluetoothObserver interface shown above. Using an observer is good practice as it keeps UI-related code separate from Bluetooth management code.

The listing below shows the states that are defined in the Bluetooth-Manager class for this game. You can see that each state change notifies the registered observer instance in the setState() method:

// states
public static final int IDLE = 0;
public static final int DETECTING_BLUETOOTH = 1;
public static final int BLUETOOTH_ENABLED = 2;
public static final int BLUETOOTH_DISABLED = 3;
public static final int FINDING_DEVICES = 4;
public static final int FINDING_SERVICES = 5;
public static final int WAITING = 6;
public static final int CONNECTING = 7;
public static final int CONNECTED = 8;
public static final int CONNECTION_FAILED = 9;
public static final int CANCELLING = 10;
public static final int DISCONNECTED = 11;
// called on state transitions
private void setState(int state){
this.state = state;

It is the slave (client) that opens a server socket and waits for a connection request from the master as illustrated in the code snippet below taken from the BluetoothManager class in our sample game:

String SERVICE_URL = "btspp://localhost:" + SERVICE_UUID;
private void startClientMode(){
// advertise that we have the game
String clientName = localDevice.getFriendlyName();
String url = SERVICE_URL + ";name=" + clientName;
notifier = (StreamConnectionNotifier);
// wait for server-initiated connection
connection = (StreamConnection) notifier.acceptAndOpen();
// get I/O streams
inputStream = connection.openInputStream();
outputStream = connection.openOutputStream();
// notify our Bluetooth observer that we are connected

Since the master device has already run an inquiry for devices and discovered their available services, all that remains is for the master to initiate a connection to the remote device (the slave). Bluetooth addresses are cumbersome and change frequently so the master uses the getConnectionUrl() method of a remote device's ServiceRecord to determine how to open a connection to it:

private void startServerMode(){
ServiceRecord serviceRecord;
// specify connection settings
int security = ServiceRecord.NOAUTHENTICATE_NOENCRYPT;
boolean mustBeMaster = false;
// determine url to use given above
String url = serviceRecord.getConnectionURL(security, mustBeMaster);
// initiate the connection
connection = (StreamConnection);
// get I/O streams
inputStream = connection.openInputStream();
outputStream = connection.openOutputStream();
// notify our Bluetooth observer that we are connected

Once connections have been established you can use normal read and write methods to send data between the connected handsets. In Finding Higgs, we have kept data transfers minimal as the main point of them is simply to keep each player aware of the other player's current score.

Of particular interest, however, is that in this case we want the game on each handset to start at the same time (as closely as possible) since it's only a one-minute game and it's far less fun when your opponent finishes five seconds before you do because they started five seconds earlier. To achieve this, we wait until each device is connected, show the game screen (which may be faster on more powerful hardware) and then inform the other device that it is ready. Only when both devices have received the initialization message do they start the game loop:

// called when Bluetooth connection is established
public void startNewGame() throws Exception {
private void notifyInitialised(){
// single player game so just start

// called whenever the Bluetooth connection receives a message
public void notifyMessageReceived(String message){
else{ // remote player score updated

Once everything has been constructed and synchronized, we only exchange messages when either player's score changes. In this case, it is our GameEngine instance that handles this:

// called from game thread – update local points and notify
// remote player when playing multi-player game
private void updateScore(){

In a more complex game, it would be advisable to define a message abstraction class instead of just passing strings back and forth. For example, a common approach is to use messages containing pre-defined integer codes (opcodes) and an optional payload. However you do it, in the end your approach need only match your requirements and you can be as complex or simplistic as you need.
- Building an Advanced Java Game on Symbian OS (part 3) - Using the Mobile 3D Graphics API
- Building an Advanced Java Game on Symbian OS (part 2) - Using the Mobile Media API & Using the Scalable 2D Vector Graphics API
- Building an Advanced Java Game on Symbian OS (part 1)
- jQuery 1.3 : Simultaneous versus queued effects (part 2) - Working with multiple sets of elements & Callbacks
- jQuery 1.3 : Simultaneous versus queued effects (part 1) - Working with a single set of elements
- iPhone 3D Programming : Crisper Text with Distance Fields (part 3) - Implementing Outline, Glow, and Shadow Effects
- iPhone 3D Programming : Crisper Text with Distance Fields (part 2) - Smoothing and Derivatives
- iPhone 3D Programming : Crisper Text with Distance Fields (part 1) - Generating Distance Fields with Python
- Mapping Well-Known Patterns onto Symbian OS : Singleton
- Mapping Well-Known Patterns onto Symbian OS : Model–View–Controller
- The Anatomy of a Mobile Site : STYLING WITH CSS - CSS Considerations for Mobile & Optimizing CSS
- iPad Development : The Dual-Action Color Popover (part 3) - Serving Two Masters
- iPad Development : The Dual-Action Color Popover (part 2) - Hooking Up the Grid
- iPad Development : The Dual-Action Color Popover (part 1) - Creating a Simple Color Grid
- XNA Game Studio 3.0 : Creating Fake 3-D - Creating Shadows Using Transparent Colors
- Android Application Development : ViewGroups (part 2) - ListView, ListActivity, ScrollView & TabHost
- Android Application Development : ViewGroups (part 1) - Gallery and GridView
- Java ME on Symbian OS : MIDP 2.0 Game API Core Concepts
- Java ME on Symbian OS : Building a Simple MIDP Game
Most View
- Windows 8 : Network and Sharing Center (part 2)
- SQL Server 2012 : Fault Tolerance - Configuring an AlwaysOn Availability Group (part 3) - Creating an Availability Group
- Microsoft PowerPoint 2010 : Preparing a Slide Show - Creating a Custom Slide Show
- Microsoft PowerPoint 2010 : Coordinating Multiple Animations (part 2) - Set Time Between Animations,Modify an Animation
- Exchange Server 2013 administration overview : Exchange Server 2013 editions
- Microsoft Project 2010 : Project on the Internet (part 5) - Integrating Project and Outlook - Routing a Project file to several recipients
- Microsoft Lync Server 2013 : Director Overview (part 1) - Benefits of a Director - Internal Endpoint Sign-In Process
- Windows 7 : Using System Protection (part 2) - Creating a restore point, Returning to a Previous Restore Point, Undoing a System Restore
- Using the Windows 8 Interface : Working with Running Apps - Switching Between Running Apps
- Windows 8 : Creating a Windows Network - Installing a Wireless Network (part 3) - Getting Maximum Wireless Speed
Top 10
- Microsoft OneNore 2010 : Distributing Your Notes - Transferring a Notebook to Another Computer
- Microsoft OneNore 2010 : Distributing Your Notes - Saving Pages, Sections, and Notebooks in Alternative File Formats
- Microsoft OneNore 2010 : Distributing Your Notes - Emailing a Page
- Microsoft OneNore 2010 : Distributing Your Notes - Printing a Section
- Microsoft Exchange Server 2010 Requirements : Additional Requirements
- Microsoft Exchange Server 2010 Requirements : Software Requirements (part 2) - Windows Server Roles and Features
- Microsoft Exchange Server 2010 Requirements : Software Requirements (part 1) - Additional Software
- Microsoft Exchange Server 2010 Requirements : Getting the Right Server Hardware (part 3) - Disk Requirements
- Microsoft Exchange Server 2010 Requirements : Getting the Right Server Hardware (part 2) - Memory Recommendations, Network Requirements
- Microsoft Exchange Server 2010 Requirements : Getting the Right Server Hardware (part 1) - The Typical User , CPU Recommendations