0

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);
        }
    }
}

}

Ido123
  • 1
  • 2

1 Answers1

0

Well, after a lot of tries I realized the problem - The chat service was instantiated twice, so I made it into a singleton.

Ido123
  • 1
  • 2
  • Well thats interesting. The problem was clear from the beginning. The singleon was one of the answers here too: http://stackoverflow.com/questions/2463175/how-to-have-android-service-communicate-with-activity Did you have to change something in your activities too? Nothing better than solving a problem yourself ;-). If you close the second activity then how does the service gets updated with the right handler? – greenapps Mar 20 '15 at 16:32
  • When I changed the service's class into a singleton, I also created an instance method called setHandler(), which received handler as a parmaeter. For each activity, in it's onCreate() and onResume() I have called this method with the matching handler - ConversationsActivity with it's handler, and BluetoothChatActivity with it's handler. If I remember correctly, this is the only thing I have changed in the activites. Hope it answers your question :) – Ido123 Mar 20 '15 at 20:16
  • No Problem :) Hope it helped – Ido123 Mar 21 '15 at 13:17
  • Not yet. I still have to start with bluetooth. Will try the chat example from the sdk first. – greenapps Mar 21 '15 at 13:40