I am developing a bluetooth chat app, which is a much extended version of the one in the samples that are coming with the SDK. It looks much more like WhatsApp. I have the main Conversations activity (which is basically a list view containing the user's chats), and the actual chat activity (BluetoothChatActivity), with text view for text entering and send button etc. I have the BluetoothChatService class, just like in the example, but the problem is that both the conversations activity and the chat activity supposed to use it (ConversationsActivity for connection and BluetoothChatActivity for messages passing). Each activity has it's own handler, and for example when I tried to write a message with the BluetoothChatActivity class, the ConversationsActivity handler was the one that handled it. What I did is putting a BluetoothChatService member for each activity, and in it's constructor I passed the activity's handler, one time in the ConversationsActivity and one time in the BluetoothChatActivity. I am stuck on this problem a long time and I don't know what to do.. I would be very happy if someone could help me please.
Thanks :)
Adding code (it isn't everthing, just the relevants parts):
ConversationsActivity:
public class ConversationsActivity extends ListActivity implements OnItemClickListener {
private static final String TAG = "ConversationsActivity";
public static final String DIRECTORY_PATH = Environment.getExternalStorageDirectory()+"/ChatApp";
// Message types sent from the BluetoothChatService Handler
public static final int START_CONVERSATION = 7;
public final Handler messagesHandler = new Handler() {
@Override
public void handleMessage(Message receivedMessage) {
Log.d(TAG, "Inside Handle message of conversation activity");
Log.d(TAG, String.valueOf(receivedMessage.what));
Log.d(TAG, String.valueOf(chatService.getState()));
switch (receivedMessage.what) {
case START_CONVERSATION:
String address = receivedMessage.getData().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
String deviceName = receivedMessage.getData().getString(DeviceListActivity.EXTRA_DEVICE_NAME);
Log.d(TAG, "address = " + address + " deviceName = " + deviceName);
if (conversations.getUserByAddress(address) == null) // If the device is not inside the conversation array list
{
// Then add it to the conversation list
conversations.addConversation(new User(deviceName, address));
refreshConversationsList();
}
Log.e("Inside Connect Device", "YAY");
// Get the device MAC address
Intent chatIntent = new Intent(getBaseContext(), BluetoothChatActivity.class);
chatIntent.putExtra(DeviceListActivity.EXTRA_DEVICE_ADDRESS, address);
chatIntent.putExtra(DeviceListActivity.EXTRA_DEVICE_NAME, deviceName);
startActivity(chatIntent);
break;
case BluetoothChatActivity.MESSAGE_BE_SERVER:
chatService.stop();
chatService.start();
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initializeComponenets();
}
// Initialize neeed data
private void initializeComponenets() {
// TODO Auto-generated method stu
chatService = new BluetoothChatService(this, messagesHandler);
}
@Override
protected void onResume() {
super.onResume();
Log.e(TAG, "onResume");
if (chatService != null) {
// Only if the state is STATE_NONE, do we know that we haven't
// started already. Of course we check that BT is enabled
System.out.println(chatService.getState());
if (chatService.getState() == BluetoothChatService.STATE_NONE && bluetoothAdapter.isEnabled()) {
// Start the Bluetooth chat services
Log.d(String.valueOf(chatService.getState()), "IN RESUME");
chatService.start();
}
}
if (conversations.size() != 0)
refreshConversationsList();
}
BluetoothChatActivity:
public class BluetoothChatActivity extends Activity implements OnClickListener
{
// The Handler that gets information back from the BluetoothChatService
public final Handler messagesHandler = new Handler() {
@Override
public void handleMessage(Message receivedMessage) {
Log.d(TAG, "Message has obtained: " + String.valueOf(receivedMessage.what));
switch (receivedMessage.what) {
case MESSAGE_STATE_CHANGE:
switch (receivedMessage.arg1) {
case BluetoothChatService.STATE_CONNECTED:
Log.d(TAG, "state changed - STATE_CONNECTED");
conversationMessagesArrayAdapter.clear();
break;
case BluetoothChatService.STATE_CONNECTING:
Log.d(TAG, "state changed - STATE_CONNECTING");
actionBar.setSubtitle(getString(R.string.conversation_title_connecting));
break;
case BluetoothChatService.STATE_LISTEN:
Log.d(TAG, "state changed - STATE_LISTEN");
case BluetoothChatService.STATE_NONE:
Log.d(TAG, "state changed - STATE_NONE");
actionBar.setSubtitle(getString(R.string.conversation_title_not_connected));
break;
}
break;
case MESSAGE_WRITE:
Log.d(TAG, "Inside MESSAGE_WRITE");
byte[] writeBuf = (byte[]) receivedMessage.obj;
// construct a string from the buffer
String writeMessage = new String(writeBuf);
conversationMessagesArrayAdapter.add("Me: " + writeMessage);
refreshConversationChat();
break;
case MESSAGE_READ:
Log.d(TAG, "MESSAGE_READ");
byte[] readBuf = (byte[]) receivedMessage.obj;
// construct a string from the valid bytes in the buffer
String readMessage = new String(readBuf, 0, receivedMessage.arg1);
Log.e(TAG, "message received = " + readMessage);
conversationMessagesArrayAdapter.add(connectedDeviceName + ": " + readMessage);
refreshConversationChat();
break;
case MESSAGE_DEVICE_NAME:
// save the connected device's name
connectedDeviceName = receivedMessage.getData().getString(DEVICE_NAME);
actionBar.setSubtitle(getString(R.string.conversation_title_connected_to) + " " + connectedDeviceName);
Toast.makeText(getApplicationContext(), "Connected to "
+ connectedDeviceName, Toast.LENGTH_SHORT).show();
break;
case MESSAGE_TOAST:
Toast.makeText(getApplicationContext(), receivedMessage.getData().getString(TOAST),
Toast.LENGTH_SHORT).show();
break;
case MESSAGE_CANCEL_CONNECTION:
cancelConnection();
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bluetooth_chat);
initializeComponents();
}
private void initializeComponents() {
chatService = new BluetoothChatService(this, messagesHandler);
connectDevice(getIntent());
}
BluetoothChatService:
public class BluetoothChatService {
private final BluetoothAdapter bluetoothAdapter;
private final Handler messagesHandler; // A handler to send messages back to the UI activity
private AcceptThread acceptThread;
private ConnectThread connectThread;
private ConnectedThread connectedThread;
private int currentState;
private boolean isServer = false;
public static final String DEVICE_NAME = "deviceName";
public static final String DEVICE_ADDRESS = "deviceAddress";
// For Debugging
private static final String TAG = "BluetoothChatService";
public static final int STATE_NONE = 0; // we're doing nothing
public static final int STATE_LISTEN = 1; // now listening for incoming connections
public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection
public static final int STATE_CONNECTED = 3; // now connected to a remote device
private static final String NAME = "BluetoothChat";
private static UUID APP_UUID = UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66"); // Default UUID at the beggining
public BluetoothChatService(Context context, Handler handler)
{
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
messagesHandler = handler;
currentState = STATE_NONE;
}
// this method sets the current state of the chat connection
private synchronized void setState(int state)
{
Log.d(TAG, "setState");
currentState = state;
// Give the new state to the Handler so the UI Activity can update
String stateDebug = "";
if (currentState == STATE_LISTEN)
isServer = true;
else
isServer = false;
messagesHandler.obtainMessage(BluetoothChatActivity.MESSAGE_STATE_CHANGE, currentState, -1).sendToTarget();
}
// This method returns the current connection state
public synchronized int getState()
{
String stateDebug = "";
switch (currentState)
{
case 0:
stateDebug = "STATE_NONE";
break;
case 1:
stateDebug = "STATE_LISTEN";
break;
case 2:
stateDebug = "STATE_CONNECTING";
break;
case 3:
stateDebug = "STATE_CONNECTED";
break;
}
Log.d(TAG, stateDebug);
return currentState;
}
/*
* This method starts the chat service. It initiates the Accept Thread in order to listen
* for incoming requests
*/
public synchronized void start()
{
Log.d(TAG, "start");
// Cancel any thread attempting to make a connection
if (connectThread != null)
{
connectThread.cancel();
connectThread = null;
}
// Cancel any thread currently running a connection
if (connectThread != null)
{
connectThread.cancel();
connectThread = null;
}
setState(STATE_LISTEN);
if (acceptThread == null)
{
acceptThread = new AcceptThread();
acceptThread.start();
}
isServer = true;
}
/*
* This method starts the ConnectThread to make a connection to a remote device
* This method receives as paramter the bluetooth device to connect to
*/
public synchronized void connect(BluetoothDevice remoteDevice)
{
Log.d(TAG, "connect");
// Cancel any thread attempting to make a connection
if (currentState == STATE_CONNECTING)
{
if (connectThread != null)
{
connectThread.cancel();
connectThread = null;
}
}
// Cancel any thread currently running a connection
// TODO: make the connected thread
if (connectedThread != null) {
connectedThread.cancel();
connectedThread = null;
}
// Start the thread to connect to the given device
connectThread = new ConnectThread(remoteDevice);
connectThread.start();
isServer = false;
setState(STATE_CONNECTING);
}
public synchronized void connected (BluetoothSocket socket, BluetoothDevice remoteDevice,
final String socketType)
{
Log.d(TAG, "connected");
// Cancel the thread that completed the connection
if (connectThread != null) {
connectThread.cancel();
connectThread = null;
}
// Cancel any thread currently running a connection
if (connectedThread != null) {
connectedThread.cancel();
connectedThread = null;
}
// Cancel the accept thread because we only want to connect to one
// device
if (acceptThread != null) {
acceptThread.cancel();
acceptThread = null;
}
// Start the thread to manage the connection and perform transmissions
connectedThread = new ConnectedThread(socket, socketType);
connectedThread.start();
if (currentState != STATE_CONNECTED)
{
getState();
// Send the name of the connected device back to the UI Activity
if (!isServer)
{
Message msg = messagesHandler.obtainMessage(BluetoothChatActivity.MESSAGE_DEVICE_NAME);
Bundle bundle = new Bundle();
bundle.putString(BluetoothChatActivity.DEVICE_NAME, remoteDevice.getName());
msg.setData(bundle);
messagesHandler.sendMessage(msg);
}
else
{
Message msg = messagesHandler.obtainMessage(ConversationsActivity.START_CONVERSATION);
Bundle bundle = new Bundle();
bundle.putString(DeviceListActivity.EXTRA_DEVICE_NAME, remoteDevice.getName());
bundle.putString(DeviceListActivity.EXTRA_DEVICE_ADDRESS, remoteDevice.getAddress());
Log.e(TAG, "address = " + DeviceListActivity.EXTRA_DEVICE_ADDRESS + remoteDevice.getAddress() + " Name = " + DeviceListActivity.EXTRA_DEVICE_NAME + remoteDevice.getName());
msg.setData(bundle);
messagesHandler.sendMessage(msg);
}
setState(STATE_CONNECTED);
Log.d(TAG, "Message state change");
messagesHandler.obtainMessage(BluetoothChatActivity.MESSAGE_STATE_CHANGE, currentState, -1).sendToTarget();
}
}
/*
* This method stops all of the threads
*/
public synchronized void stop()
{
Log.d(TAG, "stop");
if (connectThread != null)
{
connectThread.cancel();
connectThread = null;
}
if (acceptThread != null)
{
acceptThread.cancel();
acceptThread = null;
}
messagesHandler.obtainMessage(BluetoothChatActivity.MESSAGE_CANCEL_CONNECTION).sendToTarget();
// TODO: continue with the connected thread
setState(STATE_NONE);
}
/*
* This method indicates that the connection to the device has failed and it notifies the UI activity
*/
public void connectionFailed()
{
Log.d(TAG, "connectionFailed");
// Send a failure message back to the Activity
setState(STATE_NONE);
messagesHandler.obtainMessage(BluetoothChatActivity.MESSAGE_STATE_CHANGE, currentState, -1).sendToTarget();
Message failedMsg = messagesHandler.obtainMessage(BluetoothChatActivity.MESSAGE_TOAST);
Bundle bundle = new Bundle();
bundle.putString(BluetoothChatActivity.TOAST, "Unable to connect devices");
failedMsg.setData(bundle);
messagesHandler.sendMessage(failedMsg);
// Start the service over to restart listening mode
BluetoothChatService.this.start();
setState(STATE_NONE);
}
/*
* This method indicates that the connection to the device has failed and it notifies the UI activity
*/
public void connectionLost()
{
Log.d(TAG, "connectionLost");
// Send a failure message back to the activity
setState(STATE_NONE);
//messagesHandler.obtainMessage(BluetoothChatActivity.MESSAGE_STATE_CHANGE, currentState, -1).sendToTarget();
Message lostMsg = messagesHandler.obtainMessage(BluetoothChatActivity.MESSAGE_TOAST);
Bundle bundle = new Bundle();
bundle.putString(BluetoothChatActivity.TOAST, "Device connection was lost");
lostMsg.setData(bundle);
messagesHandler.sendMessage(lostMsg);
// Start the service over to restart listening mode
BluetoothChatService.this.start();
}
public void write(byte[] out) {
Log.d(TAG, "Write()");
// Create temporary object
ConnectedThread tmp;
// Synchronize a copy of the ConnectedThread
synchronized (this) {
if (currentState != STATE_CONNECTED)
return;
tmp = connectedThread;
}
// Perform the write unsynchronized
tmp.write(out);
}
/*
* This thread runs while listening for incoming connections. It behaves
* like a server-side client. It runs until a connection is accepted (or
* until cancelled).
*/
private class AcceptThread extends Thread
{
// The local server socket
private final BluetoothServerSocket bluetoothServerSocket;
private String socketType;
public AcceptThread()
{
// A temporary object that later will be assigned to bluetoothServerSocket,
// Because it is final
BluetoothServerSocket tmpSocket = null;
try
{
tmpSocket = bluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, APP_UUID);
}
catch (IOException e)
{
Log.e("Failed", "Temp server");
}
finally
{
bluetoothServerSocket = tmpSocket;
}
}
@Override
public void run() {
// TODO Auto-generated method stub
BluetoothSocket bluetoothSocket = null;
setName("AcceptThread" + socketType);
// Keep listening until exception occurs or a socket is returned
while (true)
{
try
{
bluetoothSocket = bluetoothServerSocket.accept();
}
catch (IOException e)
{
Log.e("FAILED", "Socket Type: " + bluetoothSocket
+ "accept() failed");
break;
}
// If a connection was accepted
if (bluetoothSocket != null)
{
// Do work to manage the connection (in a separate thread)
synchronized (BluetoothChatService.this)
{
switch (currentState)
{
case STATE_LISTEN:
case STATE_CONNECTING:
// Situation normal. Start the connected thread.
Log.e(TAG, "Calling connected");
connected(bluetoothSocket, bluetoothSocket.getRemoteDevice(), socketType);
break;
case STATE_NONE:
case STATE_CONNECTED:
// Either not ready or already connected. Terminate
// new socket.
try
{
bluetoothSocket.close();
}
catch (IOException e)
{
Log.e("FAILED", "Could not close unwanted socket");
}
break;
}
}
}
}
}
/* Will cancel the listening socket, and cause the thread to finish */
public void cancel() {
try {
bluetoothServerSocket.close();
} catch (IOException e) { }
}
}
/*
* This thread runs while attempting to make an outgoing connection with a
* device. It runs straight through; the connection either succeeds or
* fails.
*/
private class ConnectThread extends Thread
{
private final BluetoothSocket bluetoothSocket;
private final BluetoothDevice remoteDevice;
private String socketType;
public ConnectThread(BluetoothDevice remoteDevice)
{
this.remoteDevice = remoteDevice;
// Use a temporary object that is later assigned to bluetoothSocket,
// because mmSocket is final
BluetoothSocket tmp = null;
// Get a BluetoothSocket to connect with the given BluetoothDevice
try {
// MY_UUID is the app's UUID string, also used by the server code
tmp = remoteDevice.createRfcommSocketToServiceRecord(APP_UUID);
Log.d(TAG, tmp.getRemoteDevice().getAddress());
}
catch (IOException e)
{
Log.e("FAILED", "RFC Comm");
}
bluetoothSocket = tmp;
}
@Override
public void run() {
// Cancel discovery because it will slow down the connection
bluetoothAdapter.cancelDiscovery();
try
{
// Connect the device through the socket. This will block
// until it succeeds or throws an exception
bluetoothSocket.connect();
}
catch (IOException connectException)
{
// Unable to connect; close the socket and get out
try
{
bluetoothSocket.close();
}
catch (IOException closeException)
{
Log.e("FAILED", "Connect");
}
connectionFailed();
return;
}
// Reset the ConnectThread because we're done
synchronized (BluetoothChatService.this)
{
connectThread = null;
}
// Start the connected thread
Log.e(TAG, "Calling connected");
connected(bluetoothSocket, remoteDevice, socketType);
}
public void cancel() {
try {
bluetoothSocket.close();
} catch (IOException e) { }
}
}
private class ConnectedThread extends Thread
{
private final BluetoothSocket bluetoothSocket;
private final InputStream chatInputStream;
private final OutputStream chatOutputStream;
public ConnectedThread(BluetoothSocket connectedSocket, String socketType)
{
this.bluetoothSocket = connectedSocket;
InputStream tmpInputStream = null;
OutputStream tmpOutputStream = null;
// Get the bluetooth socket input and output streams
try
{
tmpInputStream = bluetoothSocket.getInputStream();
tmpOutputStream = bluetoothSocket.getOutputStream();
}
catch (IOException e)
{
Log.e(TAG, "Connected Thread Constructor");
}
chatInputStream = tmpInputStream;
chatOutputStream = tmpOutputStream;
}
@Override
public void run() {
byte[] buffer = new byte[1024];
int bytes;
// Keep listening to the InputStream while connected
while (true)
{
try
{
// Read from input stream
bytes = chatInputStream.read(buffer);
Log.d(TAG, "MESSAGE_READ");
// Send the obtained bytes to the UI Activity
messagesHandler.obtainMessage(BluetoothChatActivity.MESSAGE_READ, bytes, -1, buffer).sendToTarget();
Log.d(TAG, "Message read has sent by handler");
}
catch (IOException e)
{
Log.e(TAG, "disconnected", e);
BluetoothChatService.this.stop();
connectionLost();
// Start the service over to restart listening mode
break;
}
}
}
public void write(byte[] buffer) {
try {
chatOutputStream.write(buffer);
// Share the sent message back to the UI Activity
messagesHandler.obtainMessage(BluetoothChatActivity.MESSAGE_WRITE, -1, -1, buffer).sendToTarget();
} catch (IOException e) {
Log.e("Failed", "Exception during write");
}
}
public void cancel() {
try {
bluetoothSocket.close();
} catch (IOException e) {
Log.e("Failed", "close() of connect socket failed", e);
}
}
}
}