I'm trying to connect my device to another one via Bluetooth, but when I select the device I want to connect with, I get an IOException
saying
read failed, socket might closed or timeout, read ret: -1
Just to illustrate how my app works, I have a RecyclerView
populated with the devices my Bluetooth scan has found, then when I click an item the app is supposed to connect with that device.
Below is my the code for my connection thread:
private val MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb")
private lateinit var device: BluetoothDevice
private lateinit var onDeviceActionListener: OnDeviceActionListener
private lateinit var socket: BluetoothSocket
fun init(device: BluetoothDevice,
onDeviceActionListener: OnDeviceActionListener): ConnectionThread {
this.device = device
this.onDeviceActionListener = onDeviceActionListener
try {
socket = device.createRfcommSocketToServiceRecord(MY_UUID)
} catch (e: IOException) {
Log.e(TAG, "Error creating socket", e)
}
return this
}
override fun run() {
try {
socket.connect()
} catch (openException: IOException) {
Log.e(TAG, "Error opening connection. Trying to close...", openException)
try {
socket.close()
} catch (closeException: IOException) {
Log.e(TAG, "Error closing socket", closeException)
}
return
}
onDeviceActionListener.onDeviceConnect(device)
}
My guess is there is something wrong with my UUID. I've tried some other values but still didn't work.
Any help will be much appreciated.
Well, I don't see exactly what you are doing wrong here. However, I have done quite a bit of Bluetooth work. More recently just focused in BLE. You should be able to discover your nearby BT devices and see their UUIDs.
I have written a helper class about 3 years ago so it's a little old, but should be mostly the same code. Happy to share it with you if it helps.
public class BluetoothConnector {
private static final String TAG = Globals.SEARCH_STRING + BluetoothConnector.class.getSimpleName();
private static final String DEFAULT_SERVER_NAME_FOR_APP = "tn_bt_default_server";
private static final int DEFAULT_DISCOVERABLE_DURATION_MS = 30000;
private static final UUID DEFAULT_UUID = UUID.fromString("6534c201-039c-4e4f-89f9-5ca8cfeb9667");
public static final int ENABLE_DISCOVER_INTENT = 1002;
protected boolean mIsToastEnabled = false; //Access from calling class to enable toasting of progress to screen if necessary
private Handler mUIHandler;
private static ServerSocketThread mServerSocketThread;
private static ClientSocketThread mClientSocketThread;
private ManageConnectionThread mManageConnectionThread;
private Context mContext;
private IBluetoothDataListener mBluetoothDataListener;
public final Object ServerSocketLock = new Object();
public final Object ClientSocketLock = new Object();
public final Object ManageConnectionLock = new Object();
public BluetoothConnector(Context context, IBluetoothDataListener listener){
this(context, new Handler(Looper.getMainLooper()), listener);
}
public BluetoothConnector(Context context, Handler UIHandler, IBluetoothDataListener listener){
Log.v(TAG, "BluetoothConnector(context=" + context + ", Handler=" + UIHandler.getClass().getSimpleName() + ", IBluetoothDataListener=" + listener.getClass().getSimpleName());
mContext = context;
mUIHandler = UIHandler;
mBluetoothDataListener = listener;
}
public void makeThisDeviceDiscoverable(Activity callingActivity){
makeThisDeviceDiscoverable(callingActivity, BluetoothAdapter.getDefaultAdapter(), DEFAULT_DISCOVERABLE_DURATION_MS);
}
public void makeThisDeviceDiscoverable(Activity callingActivity, BluetoothAdapter adapter){
makeThisDeviceDiscoverable(callingActivity, adapter, DEFAULT_DISCOVERABLE_DURATION_MS);
}
public void makeThisDeviceDiscoverable(Activity callingActivity, int durationInMs){
makeThisDeviceDiscoverable(callingActivity, BluetoothAdapter.getDefaultAdapter(), durationInMs);
}
public void makeThisDeviceDiscoverable(Activity callingActivity, BluetoothAdapter adapter, int durationInMs) {
Log.v(TAG, "makeThisDeviceDiscoverable(callingActivity=" + callingActivity.getClass().getSimpleName() + ", BluetoothAdapter=" + (adapter == null ? "null" : adapter.getName()) + ", duration=" + String.valueOf(durationInMs));
if(adapter == null){
Log.v(TAG, "adapter is null");
}else if(adapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
Log.v(TAG, "Launching Activity to request Discoverable Permission");
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, durationInMs);
callingActivity.startActivityForResult(discoverableIntent, ENABLE_DISCOVER_INTENT);
}else{
Log.v(TAG, "adapter is already in SCAN MODE");
}
}
public void awaitConnectionFromDevice(){
awaitConnectionFromDevice(DEFAULT_UUID, BluetoothAdapter.getDefaultAdapter());
}
public void awaitConnectionFromDevice(UUID commonKey){
awaitConnectionFromDevice(commonKey, BluetoothAdapter.getDefaultAdapter());
}
public void awaitConnectionFromDevice(BluetoothAdapter adapter){
awaitConnectionFromDevice(DEFAULT_UUID, adapter);
}
public void awaitConnectionFromDevice(UUID commonKey, BluetoothAdapter adapter){
Log.v(TAG, "awaitConnectionFromDevice for UUID: " + String.valueOf(commonKey) + ", BluetoothAdapter=" + (adapter == null ? "null" : adapter.getName()));
cancelDiscovery();
synchronized (ServerSocketLock){
if(mServerSocketThread != null){
Log.v(TAG, "Server Socket Thread was not null so canceling current Thread");
mServerSocketThread.cancel();
}
Log.v(TAG, "Attempting to Start new ServerThread");
mServerSocketThread = new ServerSocketThread(commonKey, adapter);
mServerSocketThread.start();
}
}
public void cancelAwaitingConnectionFromDevice(){
Log.v(TAG, "cancelAwaitingConnectionFromDevice");
synchronized (ServerSocketLock){
if(mServerSocketThread != null){
mServerSocketThread.cancel();
mServerSocketThread = null;
Log.v(TAG, "canceling Server Socket Thread");
}else{
Log.v(TAG, "Server Socket null, so not canceling");
}
}
}
public void startDiscovery() {
startDiscovery(BluetoothAdapter.getDefaultAdapter());
}
public void startDiscovery(BluetoothAdapter adapter){
Log.v(TAG, "startDiscovery to find list of devices in range");
adapter.startDiscovery();
}
public void cancelDiscovery() {
cancelDiscovery(BluetoothAdapter.getDefaultAdapter());
}
public void cancelDiscovery(BluetoothAdapter adapter){
Log.v(TAG, "cancelDiscovery");
adapter.cancelDiscovery();
}
public void connectToDevice(BluetoothDevice device){
connectToDevice(device, DEFAULT_UUID);
}
public void connectToDevice(BluetoothDevice device, UUID commonKey){
Log.v(TAG, "connectToDevice(BluetoothDevice=" + (device == null ? "null" : device.getName()) + ", UUID=" + String.valueOf(commonKey));
synchronized (ClientSocketLock){
if(mClientSocketThread != null){
Log.v(TAG, "Client Socket Thread was not null so canceling current Thread");
mClientSocketThread.cancel();
}else{
Log.v(TAG, "Client Socket Thread is NULL so not canceling");
}
Log.v(TAG, "ClientSocketThread Starting");
mClientSocketThread = new ClientSocketThread(device, commonKey);
mClientSocketThread.start();
}
}
public BluetoothDevice getBluetoothDeviceByMac(String mac){
Log.v(TAG, "getBluetoothDeviceByMac(mac=" + mac);
return getBluetoothDeviceByMac(mac, BluetoothAdapter.getDefaultAdapter());
}
public BluetoothDevice getBluetoothDeviceByMac(String mac, BluetoothAdapter adapter) {
Log.v(TAG, "getBluetoothDeviceByMac(mac=" + mac + ", BluetoothAdapter=" + (adapter == null ? "null" : adapter.getName()));
return adapter.getRemoteDevice(mac);
}
public ArrayList<KeyValueModel> getPairedDevices(){
return getPairedDevices(BluetoothAdapter.getDefaultAdapter());
}
public ArrayList<KeyValueModel> getPairedDevices(BluetoothAdapter adapter){
ArrayList<KeyValueModel> bondedDevices = new ArrayList<KeyValueModel>();
Set<BluetoothDevice> pairedDevices = adapter.getBondedDevices();
Log.v(TAG, "getPairedDevices Found " + pairedDevices.size() + " number of paired devices");
// If there are paired devices
if (pairedDevices.size() > 0) {
// Loop through paired devices
for (BluetoothDevice device : pairedDevices) {
// Add the name and address to an array adapter to show in a ListView
bondedDevices.add(new KeyValueModel(device.getAddress(), device.getName()));
}
}
return bondedDevices;
}
public static void unpairDevice(BluetoothDevice device){
Log.v(TAG, "unpairDevice");
try{
Method method = device.getClass().getMethod("removeBond", (Class[]) null);
method.invoke(device, (Object[]) null);
}catch (Exception ex){
Log.e(TAG, "Error Unpairing Device: " + ex.getMessage());
}
}
public boolean sendDataToConnectedDevice(byte[] data){
Log.v(TAG, "sendDataToConnectedDevice");
synchronized (ManageConnectionLock){
mManageConnectionThread.write(data);
return true;
}
}
public void setBluetoothDataListener(IBluetoothDataListener listener){
mBluetoothDataListener = listener;
}
public boolean getIsConnected(){
synchronized (ManageConnectionLock) {
return mManageConnectionThread != null && mManageConnectionThread.isAlive();
}
}
private void startManageConnectionThread(BluetoothSocket socket){
Log.v(TAG, "startManageConnectionThread for Socket: " + (socket == null ? "null" : socket.getClass().getSimpleName()));
synchronized (ManageConnectionLock) {
mManageConnectionThread = new ManageConnectionThread(socket);
mManageConnectionThread.start();
}
}
private void handleDataReceivedFromConnectedDevice(final byte[] bytes){
Log.v(TAG, "handleDataReceivedFromConnectedDevice");
Log.v(TAG, "bytes to Listener: " + new String(bytes));
if(mUIHandler != null && mBluetoothDataListener != null){
mUIHandler.post(new Runnable() {
@Override
public void run() {
if (mBluetoothDataListener != null) {
mBluetoothDataListener.onReceivedPayloadFromConnectedDevice(bytes);
}
}
});
}else{
Log.v(TAG, "UIHandler was null so skipped sending payload to listener");
}
}
private void handleConnected(){
Log.e(TAG, "handleConnected");
if(mUIHandler != null && mBluetoothDataListener != null){
mUIHandler.post(new Runnable() {
@Override
public void run() {
if(mBluetoothDataListener != null){
mBluetoothDataListener.onConnectedToTargetDevice();
}
}
});
}else{
Log.v(TAG, "UIHandler was null so skipped sending payload to listener");
}
}
private void handleDisconnected(){
Log.e(TAG, "handleDisconnected");
if(mUIHandler != null && mBluetoothDataListener != null){
mUIHandler.post(new Runnable() {
@Override
public void run() {
if(mBluetoothDataListener != null){
mBluetoothDataListener.onDisconnectedFromTargetDevice();
}
}
});
}else{
Log.v(TAG, "UIHandler or Listener was null so skipped sending payload to listener");
}
}
private void handleFailedToConnectAsServer(final Exception ex){
Log.e(TAG, "handleFailedToConnectAsServer ex: " + ex.getMessage());
if(mUIHandler != null && mBluetoothDataListener != null){
mUIHandler.post(new Runnable() {
@Override
public void run() {
if(mBluetoothDataListener != null){
mBluetoothDataListener.onFailedToReceiveConnectionFromTargetDevice(ex);
}
}
});
}else{
Log.v(TAG, "UIHandler or Listener was null so skipped sending payload to listener");
}
}
private void handleFailedToConnectAsClient(final Exception ex){
Log.e(TAG, "handleFailedToConnectAsClient ex: " + ex.getMessage());
if(mUIHandler != null && mBluetoothDataListener != null){
mUIHandler.post(new Runnable() {
@Override
public void run() {
if(mBluetoothDataListener != null){
mBluetoothDataListener.onFailedToConnectToTargetDevice(ex);
}
}
});
}else{
Log.v(TAG, "UIHandler or Listener was null so skipped sending payload to listener");
}
}
private void handleErrorInRetrievingData(final Exception ex){
Log.e(TAG, "handleErrorInRetrievingData ex: " + ex.getMessage());
if(mUIHandler != null && mBluetoothDataListener != null){
mUIHandler.post(new Runnable() {
@Override
public void run() {
if(mBluetoothDataListener != null){
mBluetoothDataListener.onErrorReceivingPayloadFromConnectedDevice(ex);
}
}
});
}else{
Log.v(TAG, "UIHandler or Listener was null so skipped sending payload to listener");
}
}
private void handleFailedToSendDataToConnectedDevice(final Exception ex){
Log.e(TAG, "handleFailedToSendDataToConnectedDevice ex: " + ex.getMessage());
if(mUIHandler != null && mBluetoothDataListener != null){
mUIHandler.post(new Runnable() {
@Override
public void run() {
if(mBluetoothDataListener != null){
mBluetoothDataListener.onFailedToSendDataToConnectedDevice(ex);
}
}
});
}else{
Log.v(TAG, "UIHandler or Listener was null so skipped sending payload to listener");
}
}
private void toastMessage(final String value){
if(!mIsToastEnabled || mUIHandler == null) {
return;
}
mUIHandler.post(new Runnable() {
@Override
public void run() {
try{
Toast.makeText(mContext, value, Toast.LENGTH_SHORT).show();
}catch(Exception ex){
Log.v(TAG, "Error Toasting, possibly bad handler, or context: " + ex.getMessage());
}
}
});
}
private class ServerSocketThread extends Thread{
private final String TAG = Globals.SEARCH_STRING + ServerSocketThread.class.getSimpleName();
private final BluetoothServerSocket mServerSocket;
public ServerSocketThread(UUID commonKey, BluetoothAdapter adapter) {
Log.v(TAG, "ServerSocketThread Constructor");
BluetoothServerSocket tmp = null;
try {
Log.v(TAG, "listening for RFComas Server: " + DEFAULT_SERVER_NAME_FOR_APP + ", and commonKey: " + String.valueOf(commonKey));
// MY_UUID is the app's UUID string, also used by the client code
tmp = adapter.listenUsingRfcommWithServiceRecord(DEFAULT_SERVER_NAME_FOR_APP, commonKey);
toastMessage("Listening for RFComm As Server on UUID: " + String.valueOf(commonKey));
} catch (IOException e) {
Log.e(TAG, "Error creating ServerSocket: " + e.getMessage());
toastMessage("Error Creating ServerSocket: " + e.getMessage());
}
mServerSocket = tmp;
}
public void run() {
Log.v(TAG, "ServerSocket run");
BluetoothSocket socket = null;
// Keep listening until exception occurs or a socket is returned
while (mServerSocket != null) {
try {
Log.v(TAG, "ServerSocket.accept()");
toastMessage("ServerSocket.accept()");
//Waits for Client Connection to pass Socket, then we close down
socket = mServerSocket.accept();
} catch (IOException e) {
Log.e(TAG, "ServerSocket.accept() Error: " + e.getMessage());
toastMessage("ServerSocket.accept() Error: " + e.getMessage());
handleFailedToConnectAsServer(e);
break;
}
// If a connection was accepted we don't need to keep server listening, so close unless multiple client/server connections is desired
if (socket != null) {
try{
Log.v(TAG, "ServerSocket Accepted Client Socket, Begin Listening Connect Thread");
toastMessage("ServerSocket Accepted Client Socket, Begin Listening Connect Thread");
// Do work to manage the connection (in a separate thread)
startManageConnectionThread(socket);
//mServerSocket.close();
}catch(Exception ex){
Log.e(TAG, "Exception closing Server Socket");
}
//break; //Add in Break if you want to shut down listening for connections
}else{
Log.v(TAG, "Socket wasn't accepted");
toastMessage("Socket wasn't accepted");
handleFailedToConnectAsServer(new Exception("Socket is Null"));
}
}
Log.v(TAG, "Exiting Server Accept Thread");
}
public void cancel() {
try {
Log.v(TAG, "ServerSocketThread Canceled");
mServerSocket.close();
} catch (IOException e) {
Log.e(TAG, "ServerSocketThread Error: " + e.getMessage());
}
}
}
private class ClientSocketThread extends Thread{
private BluetoothSocket mSocket;
private final BluetoothDevice mDevice;
public ClientSocketThread(BluetoothDevice device, UUID commonKey) {
Log.v(TAG, "ClientSocketThread Constructor");
// Use a temporary object that is later assigned to mmSocket,
// because mmSocket is final
BluetoothSocket tmp = null;
mDevice = device;
// Get a BluetoothSocket to connect with the given BluetoothDevice
try {
Log.v(TAG, "Client creating RFComm Socket to Server with UUID: " + String.valueOf(commonKey));
toastMessage("Client creating RFComm Socket to Server with UUID: " + String.valueOf(commonKey));
// MY_UUID is the app's UUID string, also used by the server code
tmp = device.createRfcommSocketToServiceRecord(commonKey);
} catch (IOException e) {
Log.e(TAG, "Error creating Client Socket: " + e.getMessage());
toastMessage("Creating Socket Exception: " + e.getMessage());
handleFailedToConnectAsClient(e);
}
mSocket = tmp;
}
public void run() {
try {
if(mSocket == null){
Log.e(TAG, "Error Client Socket is Null, Canceling Client Thread");
return;
}
Log.v(TAG, "Client Connecting");
// Connect to the server, or timeout eventually
toastMessage("Client Connecting");
mSocket.connect();
} catch (IOException connectException) {
// Unable to connect; close the socket and try the fallback method of reflection with port to connect
try {
Log.e("", "trying fallback...");
toastMessage("Client Connection Failed Exception: " + connectException.getMessage());
mSocket = (BluetoothSocket) mDevice.getClass().getMethod("createRfcommSocket", new Class[]{int.class}).invoke(mDevice, 1);
toastMessage("Client Connect Again Attempt 2, but with fall back Reflection and port");
Log.v(TAG, "Client Connect Again Attempt 2, but with fall back Reflection and port");
mSocket.connect();
Log.e("", "Connected");
toastMessage("Client Connected");
} catch (Exception ex) {
Log.e("", "Couldn't establish Bluetooth connection!");
toastMessage("Client Couldn't Establish Connection to Server: " + ex.getMessage());
handleFailedToConnectAsClient(ex);
return;
}
}
// Do work to manage the connection (in a separate thread)
startManageConnectionThread(mSocket);
}
public void cancel() {
try {
Log.v(TAG, "Client Socket cancel");
mSocket.close();
} catch (IOException e) {
Log.e(TAG, "Error Closing Socket");
}
}
}
private class ManageConnectionThread extends Thread {
/////////////
// MEMBERS //
/////////////
private final String TAG = Globals.SEARCH_STRING + ManageConnectionThread.class.getSimpleName();
private final BluetoothSocket mSocket;
private final InputStream mInStream;
private final OutputStream mOutStream;
//////////////////
// CONSTRUCTOR //
//////////////////
public ManageConnectionThread(BluetoothSocket socket) {
mSocket = socket;
handleConnected();
InputStream tmpIn = null;
OutputStream tmpOut = null;
// Get the input and output streams, using temp objects because
try {
Log.v(TAG, "ManageConnectionThread Constructor");
Log.v(TAG, "Connected to Socket = " + String.valueOf(socket.isConnected()));
toastMessage("Listening for input or output Stream");
Log.v(TAG, "Get InputStream");
tmpIn = socket.getInputStream();
Log.v(TAG, "Get OutputStream");
tmpOut = socket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "Error getting Socket Streams: " + e.getMessage());
toastMessage("Connect Thread: Error: " + e.getMessage());
handleErrorInRetrievingData(e);
}
mInStream = tmpIn;
mOutStream = tmpOut;
}
///////////////
// OVERRIDES //
///////////////
public void run() {
// Keep listening to the InputStream until an exception occurs
while (true) {
try {
// Read from the InputStream
byte[] data = new byte[16384];
int nRead;
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
while ((nRead = mInStream.read(data, 0, data.length)) != -1) {
//Log.v(TAG, "bytes Read: " + String.valueOf(nRead));
buffer.write(data, 0, nRead);
//TODO Find better way to find End Of Message rather than looking for }
String temp = new String(buffer.toByteArray());
//Log.v(TAG, "current Data: " + temp);
if(temp.contains("}")){
Log.v(TAG, "bytes reading complete");
handleDataReceivedFromConnectedDevice(buffer.toByteArray());
buffer.flush();
buffer = new ByteArrayOutputStream();
}else{
Log.v(TAG, "More bytes Available");
}
}
} catch (IOException e) {
Log.e(TAG, "Error reading inputStream");
handleErrorInRetrievingData(e);
break;
}
}
Log.v(TAG, "Exiting Managed Connection Thread");
handleDisconnected();
}
/////////////
// METHODS //
/////////////
public void write(byte[] bytes) {
try {
Log.v(TAG, "ManageConnectionThread write(bytes)");
mOutStream.write(bytes);
} catch (IOException e) {
Log.e(TAG, "Error Writing Stream: " + e.getMessage());
handleFailedToSendDataToConnectedDevice(e);
}
}
public void cancel() {
try {
Log.v(TAG, "ManageConnectionThread cancel");
handleDisconnected();
mSocket.close();
} catch (IOException e) {
Log.e(TAG, "Error Closing BluetoothSocket: " + e.getMessage());
}
}
}
public interface IBluetoothDataListener{
//////////////////////
// OVERRIDE METHODS //
//////////////////////
void onReceivedPayloadFromConnectedDevice(byte[] payload);
void onErrorReceivingPayloadFromConnectedDevice(Exception ex);
void onFailedToConnectToTargetDevice(Exception ex);
void onFailedToReceiveConnectionFromTargetDevice(Exception ex);
void onFailedToSendDataToConnectedDevice(Exception ex);
void onConnectedToTargetDevice();
void onDisconnectedFromTargetDevice();
}
}
Then of course you will want to make sure you have your broadcast receivers setup:
<receiver
android:name=".receivers.BluetoothChangedReceiver"
android:enabled="true" >
<intent-filter>
<action android:name="android.bluetooth.adapter.action.STATE_CHANGED" />
<action android:name="android.bluetooth.adapter.action.SCAN_MODE_CHANGED" />
<action android:name="android.bluetooth.adapter.action.DISCOVERY_STARTED" />
<action android:name="android.bluetooth.adapter.action.DISCOVERY_FINISHED" />
<action android:name="android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED" />
<action android:name="android.bluetooth.device.action.FOUND" />
<action android:name="android.bluetooth.device.action.DISAPPEARED" />
</intent-filter>
</receiver>
<receiver
android:name=".receivers.BluetoothDeviceReceiver"
android:enabled="true" >
<intent-filter>
<action android:name="android.bluetooth.device.action.FOUND" />
<action android:name="android.bluetooth.device.action.DISAPPEARED" />
<action android:name="android.bluetooth.device.action.ACL_CONNECTED" />
<action android:name="android.bluetooth.device.action.ACL_DISCONNECTED" />
<action android:name="android.bluetooth.device.action.ACTION_ACL_DISCONNECT_REQUESTED" />
<action android:name="android.bluetooth.device.action.BOND_STATE_CHANGED" />
<action android:name="android.bluetooth.device.action.UUID" />
</intent-filter>
</receiver>