Aegidius
 Plüss   Aplulogo
     
 www.aplu.ch      Print Text 
© 2021, V10.4
 
  JGameGrid
 
 

The source code of all examples is included in the JDroidLib distribution.

You must add the following two lines as second to last lines in AndroidManifest.xml
(the line before </manifest>):

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


(or check the "Use Bluetooth" checkbox in ProjectBuilder)

 

Bluetooth Communication

1 Implementing Bluetooth Communication in Java SE and Android

Without going into technical details, we present here the necessary information how to implement data exchange between two Bluetooth enabled devices in Java SE and Android. The Java SE libraries (JGameGrid and the Bluetooth addon BtJLib) has been ported to Android with an almost identical API (JDroidLib with integrated ch.aplu.android.bluetooth package), so it is very easy to port Bluetooth based applications from the Java SE platform to Android and vice-versa.

Bluetooth communication is based on the Client-Server model with many analogies to TCP/IP communication. This is good news because the knowledge is easily transferred from one field to the other. Our explanations use terms from the Android Java Bluetooth API.

In the client-server model the two interacting nodes are not equivalent. One node acts as a server that exposes services and the other node as client that consumes the services. The server must be started first and "listens" for a client to connect. When the server starts, it creates a thread that calls a blocking method ServerSocket.accept(). When a client connects, the method returns by delivering a socket instance reference socket for this particular connection. At this time, input and output streams are opened that can be accessed with is = socket.getInputStream() and os = socket.getOutputStream(). Now a thread (called receiver thread) must handle the input data using the blocking is.read() method in a loop and handle the incoming data. Data are send to the client by yet another thread (called transmitter thread). As usual care must be taken that the receiver thread returns quickly to the blocking is.read() command to receive the next data block.

In some circumstances, e.g. if you just echo incoming data or send some simple data back, it is enough to use only one thread as receiver and transmitter. We call it transceiver thread. It is even possible that the server thread handles incoming and outgoing data after it returns from ServerSocket.accept().

The client starts a connection trial by calling the blocking BluetoothSocket.connect(). If the call returns, the client is successfully connected to the server and an input and output stream is available to handle the data transfer. If the connection trial fails after a certain timeout, connect() throws an exception that must be handled accordingly.

Data are send by the output stream write() method. Do not forget to flush() the stream because data are normally queued and sent by blocks. It is also important to close input and output streams and the socket when the connection is finished. Otherwise the system may block or even crash because some internal resources are not freed.

client-server

Surprisingly closing the connection is not a trivial issue. Closing the connection may be requested intentionally by the server or the client program. But the connection can also break unexpectedly because the transmission channel fails. In every case both systems should get a closing notification so they can release all resources (buffers, blocked threads, etc.). Because clients and servers may be quiet (not sending any data) for quite a long time, there is no way to distinguish a quiet partner from a dead partner.

Breaking the connection is performed by closing the input and output streams and the socket (is.close(), os.close() and socket.close() should be called in this order). All stream read and write methods can throw exceptions. When the connected partner closes the socket, the blocking read() in the receiver thread should be notified by unblocking and throwing an IOException. Unfortunately this is not the case in practice for all Bluetooth systems. To solve this deficiency, a closing notification message (disconnection tag) can be sent by the closer over the still working transmission channel. Of course the disconnection tag must be clearly distinguishable from the normal data stream which is not always easy to accomplish.

On some Android devices the Bluetooth server's service remains connectable even when the server socket is closed until the Bluetooth device is turned off and on again. This may cause erratic connection failures.

2 Bluetooth device and service search, authentication

Every Bluetooth device is identified by its Bluetooth MAC address with a length of 48 bits. The address is normally written as 6 hex pairs separated by a colon hh:hh:hh:hh:hh:hh, where h is 0..9,A,B,C,E,F, e.g. 7F:40:A7:55:BB:22. This address is linked with a Bluetooth friendly name in string format that should be unique within the same region.

A Bluetooth device can or cannot broadcast its address and name on the air, so it can be detected by other devices. We say that the device is visible or invisible. (For security reasons the visibility of most Android devices must be enabled manually and is limited to a certain time.) To allow data transmission even when the device is invisible, Bluetooth devices must be paired (coupled). The pairing is also used as authentication process where a common code has to be accepted by the users in a popup window. During the pairing process the Bluetooth address and the Bluetooth name are stored in a internal database of all paired devices.

A Bluetooth device sniffer detects all visible Bluetooth devices that are currently reachable. Using the package ch.aplu.android.bluetooth, a sniffer app takes just a few lines of code.

package ch.aplu.btex;

import ch.aplu.android.*;
import ch.aplu.android.bluetooth.*;
import android.bluetooth.*;
import java.util.Set;

public class RemoteDiscovery extends GameGrid
  implements BluetoothDiscoveryListener
{
  private GGConsole c;
  private volatile boolean isDiscovering;
   
  public void main()
  {
    c = GGConsole.init();
    BluetoothAdapter bluetoothAdapter = 
      BluetoothDiscovery.getBluetoothAdapter(this);
    if (!getBluetoothAdapterInfo(bluetoothAdapter))
      return;
    isDiscovering = true;
    c.println("Starting Bluetooth discovery...");
    BluetoothDiscovery bf = new BluetoothDiscovery(this);
    bf.startBluetoothDiscovery(this);
    while (isDiscovering)
      delay(10);
    c.println("All done");
  }

  public void bluetoothDeviceDiscovered(BluetoothDevice device)
  {
    c.println("Found device name: " + device.getName());
    c.println("Address: " + device.getAddress());
  }

  public void bluetoothDiscoveryFinished()
  {
    c.println("Discovery finished");
    isDiscovering = false;
  }

  private boolean
 getBluetoothAdapterInfo(BluetoothAdapter adapter)
  {
    c.println("Checking if Bluetooth is supported...");
    if (adapter == null)
    {
      c.println("Failed.");
      return false;
    }
    c.println("OK. Checking if Bluetooth is enabled...");
    if (!adapter.isEnabled())
    {
      c.println("No. Please enable Bluetooth device");
      return false;
    }
    c.println("OK. Bluetooth friendly name: " + adapter.getName());
    c.println("Bluetooth MAC address: " + adapter.getAddress());
    c.println("Paired Bluetooth devices:");
    Set<BluetoothDevice> pairedDevices = adapter.getBondedDevices();
    if (pairedDevices.size() == 0)
      c.println("None");
    else
    {
      for (BluetoothDevice device : pairedDevices)
        c.println(device.getName() +
         
" (" + device.getAddress() + ")");
    }
    return true;
  }
}

 

Download RemoteDiscovery app for installation on a smartphone

Create QR code to download Android app to your smartphone.

Download sources (RemoteDiscovery.zip).

remotediscovery

A Bluetooth server offers some services, each with a unique service name. To display all Bluetooth devices and their services you may use a PC based sniffer RFCommBrowser available from the Java SE Bluetooth site (Webstart from here). The services for a Android smartphone, where our BtBattleship server (used below) is running, show as follows:

 
servicesearch
 

RFCommBrowser lists all visible devices and their services

Because Android devices are most of the time invisible, it is not an appropriate strategy to start a Bluetooth client connection with a device search. At startup the client program should request the server's Bluetooth name and look in the local database for a corresponding paired device, retrieve its Bluetooth MAC address and perform a service search using this address. If the server device is on the air, it returns the connection URLs that can be used by the client to connect.

 

3 Client-server sample application: An echo server

Unfortunately the Android Bluetooth API does not conform to the JSR-82 recommendations widely used by the Java community. JDroidLib offers the ch.aplu.android.bluetooth package that mimics many features of the JSR-82 implementation. It also conforms to the Bluetooth library BtJLib developed on top of the Bluecove framework for Java SE. This makes it easy to port JGameGrid applications to Android and vice-versa. BtJLib and its Android counterpart are thin layers sitting on top of the Bluecove and the Android Bluetooth API. They shield application programmer from Bluetooth internals when searching devices and services and provide a BluetoothPeer class that helps to build peer-to-peer applications. The server thread is internal to the library class. When a client connects an event callback notifyConnection() is fired that delivers the input and output streams.

Many socket tutorials use the echo server scenario to demonstrate essential features of the client-server model. We do the same here and present present echo servers and its connecting clients for Android and Java SE. They are interchangeable between the two platforms (you can use the server on one platform and the client on the other one).

notifyConnection() in Android uses a boolean return value to let you decide whether the server thread loops and calls ServerSocket.connect() for the next client or terminates.

First we present the Android server BtServerApp. We write important actions out into the convenient console-like window (GGConsole) what disengages us from explaining the code in details. We override onPause() to perform the necessary cleanup when the user hits the [HOME] button.

package ch.aplu.btserver;

import
 ch.aplu.android.*;
import
 ch.aplu.android.bluetooth.*;
import
 android.bluetooth.*;
import
 java.io.*;
import
 ch.aplu.util.Monitor;

public
 class BtServerApp extends GameGrid
  implements
 BluetoothServerListener, GGNavigationListener
{
  private
 class ReceiverThread extends Thread
  {
    private
 boolean isRunning;

    public
 void run()
    {
      isRunning =
 true;
      while
 (isRunning)
      {
        try
        {
          int
 data = dis.readInt();
          c.println("Got data: "
 + data);
          if
 (data == disconnectTag)
          {
            c.println("Got disconnect notification"
);
            isRunning =
 false;
            continue
;
          }
          // Echo inverse
          dos.writeInt(-
data);
          dos.flush();
        }
        catch
 (IOException ex)
        {
          c.println("IOExeption in blocking readInt()"
);
          isRunning =
 false;
        }
      }
      c.println("Receiver thread terminated"
);
      Monitor.wakeUp(); // Wake-up sleeping notifyConnection()
    }

  }

  //
  
private final String serviceName = "BtServer";
  private
 final int disconnectTag = 9999;
  private
 BluetoothAdapter adapter;
  private
 BluetoothServer bs;
  private
 DataInputStream dis;
  private
 DataOutputStream dos;
  private
 int nbConnections = 0;
  private
 boolean isClientConnected = false;
  private
 GGConsole c;

  public
 void main()
  {
    c =
 GGConsole.init();
    addNavigationListener(this
);

    adapter =
 BluetoothDiscovery.getBluetoothAdapter(this);
    c.println("Creating server with Bluetooth name '"
      
+ adapter.getName() + "'");
    bs =
 new BluetoothServer(adapter, serviceName, this);
    c.println("Waiting for a Bluetooth client to connect "
);
    c.println("exposing service '"
 + serviceName + "'");
  }

  public
 boolean notifyClientConnection(BluetoothDevice device,
    InputStream
 is, OutputStream os)
  {
    c.println("notifyClientConnection() starting"
);
    nbConnections++
;
    c.println("Client '"
 + device.getName() + "' connected as # " + nbConnections);
    isClientConnected =
 true;
    c.println("Retrieving input and output streams"
);
    dis =
 new DataInputStream(is);
    dos =
 new DataOutputStream(os);
    new
 ReceiverThread().start();
    c.println("Starting receiver thread"
);
    c.println("notifyClientConnection() blocking now"
);
    Monitor.putSleep();  // Thread goes to sleep
    c.println("notifyClientConnection() wakes up"
);
    c.println("Lost client connection. Server will close streams and socket"
);
    c.println("Waiting for next client..."
);
    isClientConnected =
 false;
    return
 true;
  }

  // Is called by the system
  
public void onPause()
  {
    if
 (isClientConnected)
      sendDisconnectTag();
    if
 (bs != null)
      bs.abort();
    super
.onPause();
  }

  private
 void sendDisconnectTag()
  {
    try
    {
      dos.writeInt(disconnectTag); 
      dos.flush();
    }
    catch
 (IOException ex)
    {
    }
  }

  public
 void navigationEvent(GGNavigationEvent event)
  {
    showToast("Not implemented"
);
  }
}

Download BtServerApp app for installation on a smartphone

Create QR code to download Android app to your smartphone.

Download sources (BtServerApp.zip).

The code for the JavaSE version BtServerSE.java is almost identical and can be downloaded from here. To check the compatibility of the two version, BtServerSE may be executed using Webstart.

For your convenience the client app stores the server's Bluetooth name as Android preferences. It sends 100 integers 1..100 to the server and waits for the server to send back the inverse. We also show important actions in the GGConsole window. A separate TransceiverThread is used to handle data, despite the main thread could also be used for.

package ch.aplu.btclient;

import
 android.bluetooth.*;
import
 ch.aplu.android.*;
import
 ch.aplu.android.bluetooth.*;
import
 java.io.DataInputStream;
import
 java.io.DataOutputStream;
import
 java.io.IOException;

public
 class BtClientApp extends GameGrid
  implements
 GGNavigationListener
{
  // ------------- Inner class TransceiverThread ---------------
  
private class TransceiverThread extends Thread
  {
    public
 void run()
    {
      delay(2000);  // Wait until server is ready
      
try
      {
        for
 (int = 1; n <= 100; n++)
        {
          dos.writeInt(n);
          dos.flush();
          c.println("Sent: "
 + n);

          // Blocking, throws IOException
          
// when server closes connection or BluetoothClient.disconnect() is called
          
int = dis.readInt();
          c.println("Got: "
 + k);
          if
 (k == disconnectTag)
            throw
 new IOException("Got disconnect notification");
        }
        dos.writeInt(disconnectTag);
        dos.flush();
      }
      catch
 (IOException ex)
      {
        c.println("Transceiver thread: "
 + ex.getMessage());
      }
      delay(2000);
      bc.disconnect();
      c.println("Transceiver thread terminated"
);
      c.println("Streams and socket closed"
);
    }

  }
  // ------------- End of inner class -----------------------

  private
 final int disconnectTag = 9999;
  private
 BluetoothClient bc;
  private
 DataInputStream dis;
  private
 DataOutputStream dos;
  private
 GGConsole c;

  public
 void main()
  {
    c =
 GGConsole.init();
    addNavigationListener(this
);
    connect();
  }

  private
 void connect()
  {
    c.println("Asking for server Bluetooth name"
);
    String
 serverName = askName();
    c.println("Searching for client '"
 + serverName + "' ...");
    BluetoothDevice serverDevice =
 searchDevice(serverName);
    if
 (serverDevice != null)
    {
      c.println("Trying to connect to server..."
);
      bc =
 new BluetoothClient(serverDevice);
      boolean
 rc = bc.connect();
      if
 (!rc)
      {
        c.println("Connection failed."
);
        return
;
      }
      c.println("Connection established."
);

      dis =
 new DataInputStream(bc.getInputStream());
      dos =
 new DataOutputStream(bc.getOutputStream());
      c.println("Starting transceiver thread"
);
      new
 TransceiverThread().start();
    }
  }

  // Is called by the system
  
public void onPause()
  {
    if
 (bc != null && bc.isConnected())
    {
      try
      {
        dos.writeInt(disconnectTag);
        dos.flush();
      }
      catch
 (IOException ex)
      {
      }
      bc.disconnect();
    }
    super
.onPause();
  }

  private
 String askName()
  {
    GGPreferences prefs =
 new GGPreferences(this);
    String
 oldName = prefs.retrieveString("BluetoothName");
    String
 newName = null;
    while
 (newName == null)
    {
      newName =
 GGInputDialog.show(c.getActivity(), "Search Device",
        "Enter Bluetooth Name"
,
        oldName ==
 null ? "" : oldName);
    }
    prefs.storeString("BluetoothName"
newName);
    return
 newName;
  }

  // Searches for device in set of paired devices. Returns null, if search fails.
  
private BluetoothDevice searchDevice(String btName)
  {
    BluetoothAdapter adapter =
      BluetoothDiscovery.getBluetoothAdapter(this
);
    if
 (adapter == null)
    {
      c.println("Bluetooth not supported"
);  // e.g. in emulator
      
return null;
    }
    if
 (!adapter.isEnabled())
    {
      requestBluetoothEnable();
      if
 (!adapter.isEnabled())
      {
        c.println("Can' enable Bluetooth"
);
        return
 null;
      }
    }
    c.println("Searching for paired device '"
 + btName + "'...");
    BluetoothDiscovery bd =
 new BluetoothDiscovery(this);
    BluetoothDevice device =
 bd.getPairedDevice(btName);
    if
 (device != null)
      c.println("Paired device '"
 + btName + "' found.");
    else
      c.println("Please pair with device '"
 + btName + "'");
    return
 device;
  }

  public
 void navigationEvent(GGNavigationEvent event)
  {
    showToast("Not implemented"
);
  }
}

Download BtClientApp app for installation on a smartphone

Create QR code to download Android app to your smartphone.

Download sources (BtClientApp.zip).

The code for the JavaSE version BtClientSE.java is almost identical and can be downloaded from here. To check the compatibility of the two version, BtClientSE may be executed using Webstart. If you pair all devices correctly you should be able to interchange Android and JavaSE versions.

 
btserver
btclient
 
Bluetooth Server
Bluetooth Client

 

4 A typical client-server based application: A smartphone as remote photo camera

In the following section we present a typical client-server application to take snapshots using a Android smartphone triggered by a remote desktop application and transfer the image from the Android device to the desktop via Bluetooth.The project is designed as follows:

The Android smartphone acts as Bluetooth server waiting for a PC client to connect. After a successful connection the server listens for incoming commands. A integer number between n = 1 and 100 is sent by the client to trigger a snapshot. The captured photo image is zoomed to n percent and stored as JPG file on the sdcard. The serve then transfers the file to the client via Bluetooth where it is stored on the local disk.

The experience with some older Android devices shows that the transfer of large data may block the transmission channel. It is best to split the transfer into small blocks of about 256 bytes and let the transmitter thread sleep for about 10 ms between each block. This slows down the Bluetooth transfer only a few but increases quite a lot the stabilty of the system. The client checks for the end of file tag for JPEG files (FFD9 (hex)) to know when the file transmission is finished. The tag FFFE (hex) is used to indicate that the server is closing. When the client quits, it sends -1 to the server to initiate the cleanup there.

The desktop application uses the JGameGrid library because of the easy handling of images and files. The Android app is base on JDroidLib's GGCamera class to take the snapshot with just one single command.

The code is not presented here, but may be downloaded below and studied in details.

Download RemotePhoto app for installation on a smartphone

Create QR code to download Android app to your smartphone.

Download sources (RemotePhoto.zip).

 

Execute RemotePhotoClient locally using WebStart.

Download RemotePhotoClient source (also part of the JGameGrid distribution)

  remotephotoserver remotephotoclient
  RemotePhoto (JDroidLib)
    RemotePhotoClient (JGameGrid)

 

5 Peer-to-Peer communication

It is fun to play a computer game with human partner some distance away. Bluetooth is the ideal communication system to interlink two players in the same room. Because the two players are equivalent, the client-server model is not very appropriate. This kind of communication is better implemented using the Peer-to-Peer model, but like withTCP/IP, Bluetooth does not directly support peer-to-peer communication. But it is quite easy to implement with the following strategy: A node first starts in client mode and looks for a server node to connect. If no server is found, it starts a server node.

This concept works fine apart from the problem that both nodes could start at the same time and none of them find a server. To handle this situation some retry mechanism must be implemented using an arbitrary time delay.

BtJLib and JDroidLib include the class BluetoothPeer that provides basic support for peer-to-peer communication. We explain its use in a typical example in a extremely simplified Battleship game, where two players deploys a single type of ships in a linear array if ten cells. At startup three ships are placed arbitrarily in three of the ten cells. By turns each player launches a "bomb" by tapping on a cell and tries to hit an enemy ship. The player whose fleet is destroyed first it the looser.

A more advanced game with a two dimensional board and where you can deploy three kinds of battleships yourself is also available. The implementation principles may be applied to many other games of "Nim-Type" where two players take turns removing objects from a common store.

First we present the implementation running under Android. Many games are modelled as finite state machine using a enum State. There are special game situations that must be handled with care such as the Game-Over state. It is a good idea to mandate a special thread to worry about. The main thread is a good candidate because it has nothing else to do. If you include a short delay() in the looping main thread, the thread consumes very little CPU time and termintates automatically when the app exits.

package ch.aplu.btpeerex1;

import android.bluetooth.*;
import android.graphics.Color;
import ch.aplu.android.*;
import ch.aplu.android.bluetooth.*;

public class BtPeerEx1 extends GameGrid
  implements GGTouchListener, BluetoothPeerListener
{
  private enum State
  {
    READY, GAME_OVER, CONNECTION_LOST
  }

  private interface Command
  {
    int all_sunk = -1;
    int sunk = -2;
    int disconnect = -3;
  }

  private final String serviceName = "BtPeerEx1";
  private BluetoothPeer bp;
  private final int DKGREEN = Color.rgb(0800);
  private final int ORANGE = Color.rgb(255800);
  private GGStatusBar status;
  private volatile State state = State.READY;
  private final String msgMyShoot = "Tap a cell to fire";
  private final String msgEnemyShoot = "Please wait enemy bomb";
  private boolean isWinner;
  private Location touchLocation;

  public BtPeerEx1()
  {
    super(101cellZoom(40), RED, nullfalse);
    setScreenOrientation(LANDSCAPE);
    status = addStatusBar(30);
  }

  public void main()
  {
    status.setRefreshEnabled(true);
    setSimulationPeriod(50);
    setTouchEnabled(false);
    addTouchListener(this, GGTouch.press);
    deploy();
    connect();
    while (true)
    {
      if (state == State.GAME_OVER)
      {
        deploy();
        if (isWinner)
        {
          status.setText("You won! " + msgEnemyShoot, BLUE);
          setTouchEnabled(false);
        }
        else
        {
          status.setText("You lost! " + msgMyShoot, BLUE);
          setTouchEnabled(true);
        }
        state = State.READY;
      }
      delay(500);
    }
  }

  private void deploy()
  {
    removeAllActors();
    for (int i = 0; i < 3; i++)
      addActor(new Ship()getRandomEmptyLocation());
  }

  public boolean touchEvent(GGTouch touch)
  {
    touchLocation = toLocation(touch.getX(), touch.getY());
    if (!isInGrid(touchLocation))
      return false;
    setTouchEnabled(false);
    addActor(new Actor("marker"), touchLocation);
    int[] data = new int[1];
    data[0] = touchLocation.x; // cell coordinate
    bp.sendDataBlock(data);
    status.setText(msgEnemyShoot, RED);
    return false;
  }

  private void connect()
  {
    status.setText("BtBattleShip", DKGRAY);
    String partnerName = askName();
    status.setText("Searching for partner name '" + partnerName + "' ...", DKGRAY);
    BluetoothDevice partnerDevice = searchDevice(partnerName);
    if (partnerDevice != null)
    {
      status.setText("Trying to connect to '" + partnerName + "' ...");
      bp = new BluetoothPeer(this, partnerDevice, serviceName, thistrue);
      if (bp.isConnected())
      {
        status.setText("Connection established. " + msgMyShoot, DKGREEN);
        setTouchEnabled(true);
      }
      else
        status.setText("'" + partnerName + "' not found. Waiting as server '"
          + BluetoothDiscovery.getBluetoothAdapter(this).getName() + "' ...",
          ORANGE);
    }
  }

  public void notifyConnection(BluetoothDevice device, boolean connected)
  {
    if (connected)
    {
      status.setText("Connection established. Wait for enemy's shoot", RED);
    }
    else
    {
      status.setText("Connection lost. Press [HOME] and restart the app.", RED);
      state = State.CONNECTION_LOST;
      setTouchEnabled(false);
    }
  }

  // Signal to server that we quit
  public void onPause()
  {
    // Because closing the Bluetooth socket is not
    // detected by every partner, we send a 'disconnect' command
    if (bp != null && bp.isConnected())
    {
      sendCommand(Command.disconnect);
      bp.releaseConnection();
      state = State.CONNECTION_LOST;
    }
    super.onPause();
  }

  public void receiveDataBlock(int[] data)
  {
    int x = data[0];
    // Command dispatcher
    switch (data[0])
    {
      case Command.disconnect:
        notifyConnection(nullfalse);
        return;
      case Command.all_sunk:
        state = State.GAME_OVER;
        isWinner = true;
        return;
      case Command.sunk:
        addActor(new Actor("hit"), touchLocation);
        return;
      default// Got 'location' command
        Actor actor = getOneActorAt(new Location(x, 0), Ship.class);
        {
          if (actor != null)
          {
            actor.removeSelf();
            sendCommand(Command.sunk);
          }
        }
        break;
    }

    if (getNumberOfActors(Ship.class) == 0)
    {
      sendCommand(Command.all_sunk);
      state = State.GAME_OVER;
      isWinner = false;
      return;
    }
    
    status.setText(msgMyShoot, DKGREEN);
    setTouchEnabled(true);
  }

  private void sendCommand(int command)
  {
    int[] data = new int[1];
    data[0] = command;
    bp.sendDataBlock(data);
  }

  private String askName()
  {
    GGPreferences prefs = new GGPreferences(this);
    String oldName = prefs.retrieveString("BluetoothName");
    String newName = null;
    while (newName == null)
    {
      newName = GGInputDialog.show(this"Search Device",
        "Enter Bluetooth Name",
        oldName == null ? "" : oldName);
    }
    prefs.storeString("BluetoothName", newName);
    return newName;
  }

  // Searches for device in set of paired devices. Returns null, if search fails.
  private BluetoothDevice searchDevice(String btName)
  {
    BluetoothAdapter adapter =
      BluetoothDiscovery.getBluetoothAdapter(this);
    if (adapter == null)
    {
      status.setText("Bluetooth not supported", RED);  // e.g. in emulator
      return null;
    }
    if (!adapter.isEnabled())
    {
      requestBluetoothEnable();
      if (!adapter.isEnabled())
      {
        status.setText("Can' enable Bluetooth", RED);
        return null;
      }
    }
    status.setText("Searching for paired device '" + btName + "'...");
    BluetoothDiscovery bd = new BluetoothDiscovery(this);
    BluetoothDevice device = bd.getPairedDevice(btName);
    if (device != null)
      status.setText("Paired device '" + btName + "' found.");
    else
      status.setText("Please pair with device '" + btName + "'");
    return device;
  }
  
  
  // ------------------ Class Ship ------------------
  class Ship extends Actor
  {
    public Ship()
    {
      super("ship");
    }
  }
}

Download BtPeerEx1 app for installation on a smartphone

Create QR code to download Android app to your smartphone.

Download sources (BtPeerEx1.zip).

Connection sequence:

btpeer
btpeer
Ask partner's Bluetooth name
Client 'aplusam' tries to connect to 'EDGE'

(one of the cases:)

btpeer
btpeer
Server 'EDGE' found. Client connected
Server 'EDGE' not found. Starting as server

The code for the JavaSE version is essentially the same. Instead of preferences we use Java properties what requires some additional code.

import ch.aplu.jgamegrid.*;
import
 java.awt.Color;
import
 ch.aplu.bluetooth.*;
import
 java.io.*;
import
 java.util.Properties;
import
 javax.bluetooth.RemoteDevice;
import
 javax.swing.JOptionPane;

public
 class BtPeerEx1 extends GameGrid
  
implements GGMouseListener, BtPeerListener, GGExitListener
{
  
private enum State
  
{
    READY, GAME_OVER, CONNECTION_LOST
  
}

  
private interface Command
  
{
    
int all_sunk = -1;
    
int sunk = -2;
    
int disconnect = -3;
  
}

  
private final boolean isVerbose = true;
  
private final String serviceName = "BtPeerEx1";
  
private BluetoothPeer bp;
  
private volatile State state = State.READY;
  
private final String msgMyShoot = "Click a cell to fire";
  
private final String msgEnemyShoot = "Please wait enemy bomb";
  
private boolean isWinner;
  
private Location touchLocation;
  
private final String userHome = System.getProperty("user.home");
  
private final String fs = System.getProperty("file.separator");
  
private String propPath = userHome + fs + "BtPeerEx1.properties";
  
private File propFile = new File(propPath);
  
private Properties prop = new Properties();

  
public BtPeerEx1()
  
{
    
super(10, 1, 40, Color.red, false);
    
show();
    
addMouseListener(this, GGMouse.lClick);
    
addExitListener(this);
    
addStatusBar(30);
    
deploy();
    
connect();
    
while (true)
    
{
      
if (state == State.GAME_OVER)
      
{
        
deploy();
        
if (isWinner)
        
{
          
setStatusText("You won! " + msgEnemyShoot);
          
setMouseEnabled(false);
        
}
        
else
        
{
          
setStatusText("You lost! " + msgMyShoot);
          
setMouseEnabled(true);
        
}
        state 
= State.READY;
      
}
      
delay(500);
    
}
  
}

  
private void deploy()
  
{
    
removeAllActors();
    
for (int i = 0; i < 3; i++)
      
addActor(new Ship(), getRandomEmptyLocation());
  
}

  
public boolean mouseEvent(GGMouse mouse)
  
{
    
setMouseEnabled(false);
    touchLocation 
= toLocationInGrid(mouse.getX(), mouse.getY());
    
addActor(new Actor("sprites/marker.gif"), touchLocation);
    
int[] data = new int[1];
    data
[0] = touchLocation.x; // cell coordinate
    bp.
sendDataBlock(data);
    
setStatusText(msgEnemyShoot);
    
return false;
  
}

  
private void connect()
  
{
    
setStatusText("BtBattleShip");
    
Properties prop = loadProperties();
    
String btName = "";
    
if (prop != null)
    
{
      btName 
= prop.getProperty("BluetoothName");
      
if (btName == null)
        btName 
= "";
      
else
        btName 
= btName.trim();
    
}
    
String partnerName = askName(btName);
    
setProperty("BluetoothName", partnerName);
    
setStatusText("Searching for partner name '" + partnerName + "' ...");

    
// Try to get device from paired devices database
    RemoteDevice rd 
= BluetoothFinder.searchPreknownDevice(partnerName);
    
if (rd == null)
    
{
      
System.out.println("Sorry. Device '" + partnerName + "' not paired.");
      
System.out.println("Please perform Bluetooth device pairing!");
      
setStatusText("Device '" + partnerName + "' not paired. Perform pairing");
      
return;
    
}
    
else
    
{
      
System.out.println("Paired device found. Searching service now...");
      
setStatusText("Paired device found. Searching service now...");
    
}
    
setStatusText("Trying to connect to '" + partnerName + "' ...");
    bp 
= new BluetoothPeer(partnerName, serviceName, this, isVerbose);
    
if (bp.isConnected())
    
{
      
setStatusText("Connection established. " + msgMyShoot);
      
setMouseEnabled(true);
    
}
    
else
      
setStatusText("'" + partnerName + "' not found. Waiting as server '"
        
+ BluetoothFinder.getLocalBluetoothName() + "' ...");
  
}

  
public void notifyConnection(boolean connected)
  
{
    
if (connected)
    
{
      
setStatusText("Connection established. Wait for enemy's shoot");
    
}
    
else
    
{
      
setStatusText("Connection lost. Press [HOME] and restart the app.");
      state 
= State.CONNECTION_LOST;
      
setMouseEnabled(false);
    
}
  
}

  
// Signal to server that we quit
  
public boolean notifyExit()
  
{
    
// Because closing the Bluetooth socket is not
    
// detected by every partner, we send an 'disconnect' command
    
if (bp != null && bp.isConnected())
    
{
      
sendCommand(Command.disconnect);
      bp.
releaseConnection();
      state 
= State.CONNECTION_LOST;
    
}
    
return true;
  
}

  
public void receiveDataBlock(int[] data)
  
{
    
int x = data[0];
    
// Command dispatcher
    
switch (data[0])
    
{
      
case Command.disconnect:
        
notifyConnection(false);
        
return;
      
case Command.all_sunk:
        state 
= State.GAME_OVER;
        isWinner 
= true;
        
return;
      
case Command.sunk:
        
addActor(new Actor("sprites/hit.gif"), touchLocation);
        
return;
      
default// Got 'location' command
        Actor actor 
= getOneActorAt(new Location(x, 0), Ship.class);
      
{
        
if (actor != null)
        
{
          actor.
removeSelf();
          
refresh();
          
sendCommand(Command.sunk);
        
}
      
}
      
break;
    
}

    
if (getNumberOfActors(Ship.class== 0)
    
{
      
sendCommand(Command.all_sunk);
      state 
= State.GAME_OVER;
      isWinner 
= false;
      
return;
    
}

    
setStatusText(msgMyShoot);
    
setMouseEnabled(true);
  
}

  
private void sendCommand(int command)
  
{
    
int[] data = new int[1];
    data
[0] = command;
    bp.
sendDataBlock(data);
  
}

  
private String askName(String btName)
  
{
    
setStatusText("Select partner's Bluetooth name");
    
String prompt = "Enter Partner's Bluetooth Name";
    
String serverName;
    
do
    
{
      serverName 
= JOptionPane.showInputDialog(null, prompt, btName);
      
if (serverName == null)
        
System.exit(0);
    
}
    
while (serverName.trim().length() == 0);
    serverName 
= serverName.trim();
    
return serverName;
  
}

  
private Properties loadProperties()
  
{
    
// Return null, if error
    
if (!propFile.exists())
    
{
      
try
      
{
        propFile.
createNewFile();
      
}
      
catch (IOException ex)
      
{
        
return null;
      
}
    
}
    
FileInputStream fis = null;
    
try
    
{
      fis 
= new FileInputStream(propFile);
      prop.
load(fis);
    
}
    
catch (IOException ex)
    
{
      
return null;
    
}
    
finally
    
{
      
try
      
{
        fis.
close();
      
}
      
catch (Exception ex)
      
{
      
}
    
}
    
return prop;
  
}

  
private void setProperty(String key, String value)
  
{
    
if (prop == null)
      
return;
    prop.
setProperty(key, value);
    
try
    
{
      
FileOutputStream fos = new FileOutputStream(propFile);
      prop.
store(fos, null);
      fos.
close();
    
}
    
catch (IOException ex)
    
{
      
System.out.println("Can't set property " + key);
    
}
  
}

  
public static void main(String[] args)
  
{
    
new BtPeerEx1();
  
}
}

// ------------------ Class Ship ------------------
class
 Ship extends Actor
{
  
public Ship()
  
{
    
super("sprites/ship.gif");
  
}
}

Execute the program locally using WebStart.

Download BtPeerEx1 source (also part of the JGameGrid distribution)

Here you find the code and the installable app of a more professional Android battleship version with a two dimensional game board and the possibility to deploy freely three types of ship.