1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-28 21:06:50 +01:00

BtBRQueue: use Handler(Thread) for sending messages and connecting socket

This commit is contained in:
MrYoranimo 2024-01-15 15:11:56 +01:00 committed by José Rebelo
parent ae97e961b9
commit 1185699c56

View File

@ -21,6 +21,12 @@ import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket; import android.bluetooth.BluetoothSocket;
import android.content.Context; import android.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.Process;
import androidx.annotation.NonNull;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -29,16 +35,16 @@ import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Locale; import java.util.Locale;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
public final class BtBRQueue { public final class BtBRQueue {
private static final Logger LOG = LoggerFactory.getLogger(BtBRQueue.class); private static final Logger LOG = LoggerFactory.getLogger(BtBRQueue.class);
public static final int HANDLER_SUBJECT_CONNECT = 0;
public static final int HANDLER_SUBJECT_PERFORM_TRANSACTION = 1;
private BluetoothAdapter mBtAdapter = null; private BluetoothAdapter mBtAdapter = null;
private BluetoothSocket mBtSocket = null; private BluetoothSocket mBtSocket = null;
@ -46,57 +52,83 @@ public final class BtBRQueue {
private final SocketCallback mCallback; private final SocketCallback mCallback;
private final UUID mService; private final UUID mService;
private final BlockingQueue<AbstractTransaction> mTransactions = new LinkedBlockingQueue<>();
private volatile boolean mDisposed; private volatile boolean mDisposed;
private volatile boolean mCrashed;
private final Context mContext; private final Context mContext;
private CountDownLatch mConnectionLatch;
private CountDownLatch mAvailableData;
private final int mBufferSize; private final int mBufferSize;
private Thread writeThread = new Thread("Write Thread") { private Handler mWriteHandler;
private final HandlerThread mWriteHandlerThread = new HandlerThread("Write Thread", Process.THREAD_PRIORITY_BACKGROUND) {
@Override @Override
public void run() { protected void onLooperPrepared() {
LOG.debug("Started write thread for {} (address {})", mGbDevice.getName(), mGbDevice.getAddress()); LOG.debug("Write handler thread's looper prepared, creating write handler");
mWriteHandler = new Handler(mWriteHandlerThread.getLooper()) {
while (!mDisposed && !mCrashed) { @SuppressLint("MissingPermission")
try { @Override
AbstractTransaction qTransaction = mTransactions.take(); public void handleMessage(@NonNull Message msg) {
if (!isConnected()) { switch (msg.what) {
LOG.debug("Not connected, waiting for connection..."); case HANDLER_SUBJECT_CONNECT: {
setDeviceConnectionState(GBDevice.State.NOT_CONNECTED); try {
// wait until the connection succeeds before running the actions mBtSocket.connect();
// Note that no automatic connection is performed. This has to be triggered
// on the outside typically by the DeviceSupport. The reason is that LOG.info("Connected to RFCOMM socket for {}", mGbDevice.getName());
// devices have different kinds of initializations and this class has no setDeviceConnectionState(GBDevice.State.CONNECTED);
// idea about them.
mConnectionLatch = new CountDownLatch(1); // update thread names to show device names in logs
mConnectionLatch.await(); readThread.setName(String.format(Locale.ENGLISH,
mConnectionLatch = null; "Read Thread for %s", mGbDevice.getName()));
} mWriteHandlerThread.setName(String.format(Locale.ENGLISH,
LOG.info("Ready for a new message exchange."); "Write Thread for %s", mGbDevice.getName()));
Transaction transaction = (Transaction)qTransaction;
for (BtBRAction action : transaction.getActions()) { // now that connect has been created, start the threads
if (LOG.isDebugEnabled()) { readThread.start();
LOG.debug("About to run action: " + action); onConnectionEstablished();
} catch (IOException e) {
LOG.error("IO exception while establishing socket connection: ", e);
setDeviceConnectionState(GBDevice.State.NOT_CONNECTED);
}
return;
} }
if (action.run(mBtSocket)) { case HANDLER_SUBJECT_PERFORM_TRANSACTION: {
LOG.debug("Action ok: " + action); try {
} else { if (!isConnected()) {
LOG.error("Action returned false: " + action); LOG.debug("Not connected, updating device state to WAITING_FOR_RECONNECT");
break; setDeviceConnectionState(GBDevice.State.WAITING_FOR_RECONNECT);
return;
}
if (!(msg.obj instanceof Transaction)) {
LOG.error("msg.obj is not an instance of Transaction");
return;
}
Transaction transaction = (Transaction) msg.obj;
for (BtBRAction action : transaction.getActions()) {
if (LOG.isDebugEnabled()) {
LOG.debug("About to run action: {}", action);
}
if (action.run(mBtSocket)) {
LOG.debug("Action ok: {}", action);
} else {
LOG.error("Action returned false, cancelling further actions in transaction: {}", action);
break;
}
}
} catch (Throwable ex) {
LOG.error("IO Write Thread died: " + ex.getMessage(), ex);
}
return;
} }
} }
} catch (InterruptedException ignored) {
mConnectionLatch = null; LOG.warn("Unhandled write handler message {}", msg.what);
LOG.debug("Thread interrupted");
} catch (Throwable ex) {
LOG.error("IO Write Thread died: " + ex.getMessage(), ex);
mCrashed = true;
mConnectionLatch = null;
} }
} };
} }
}; };
@ -108,44 +140,17 @@ public final class BtBRQueue {
LOG.debug("Read thread started, entering loop"); LOG.debug("Read thread started, entering loop");
while (!mDisposed && !mCrashed) { while (!mDisposed) {
try { try {
if (!isConnected()) {
LOG.debug("not connected, waiting for connection...");
// wait until the connection succeeds before running the actions
// Note that no automatic connection is performed. This has to be triggered
// on the outside typically by the DeviceSupport. The reason is that
// devices have different kinds of initializations and this class has no
// idea about them.
mConnectionLatch = new CountDownLatch(1);
mConnectionLatch.await();
mConnectionLatch = null;
}
if (mAvailableData != null) {
if (mBtSocket.getInputStream().available() == 0) {
mAvailableData.countDown();
}
}
nRead = mBtSocket.getInputStream().read(buffer); nRead = mBtSocket.getInputStream().read(buffer);
// safety measure // safety measure
if (nRead == -1) { if (nRead == -1) {
throw new IOException("End of stream"); throw new IOException("End of stream");
} }
} catch (InterruptedException ignored) { } catch (IOException ex) {
LOG.debug("Thread interrupted"); LOG.error("IO exception while reading message from socket, breaking out of read thread: ", ex);
mConnectionLatch = null; break;
continue;
} catch (Throwable ex) {
if (mAvailableData == null) {
LOG.error("IO read thread died: " + ex.getMessage(), ex);
mCrashed = true;
}
mConnectionLatch = null;
continue;
} }
LOG.debug("Received {} bytes: {}", nRead, GB.hexdump(buffer, 0, nRead)); LOG.debug("Received {} bytes: {}", nRead, GB.hexdump(buffer, 0, nRead));
@ -157,7 +162,8 @@ public final class BtBRQueue {
} }
} }
LOG.debug("Exited read thread loop"); LOG.debug("Exited read thread loop, calling disconnect()");
disconnect();
} }
}; };
@ -168,6 +174,8 @@ public final class BtBRQueue {
mCallback = socketCallback; mCallback = socketCallback;
mService = supportedService; mService = supportedService;
mBufferSize = bufferSize; mBufferSize = bufferSize;
mWriteHandlerThread.start();
} }
/** /**
@ -196,24 +204,6 @@ public final class BtBRQueue {
try { try {
BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(mGbDevice.getAddress()); BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(mGbDevice.getAddress());
mBtSocket = btDevice.createRfcommSocketToServiceRecord(mService); mBtSocket = btDevice.createRfcommSocketToServiceRecord(mService);
LOG.debug("RFCOMM socket created, connecting");
// TODO this call is blocking, which makes this method preferably called from a background thread
mBtSocket.connect();
LOG.info("Connected to RFCOMM socket for {}", mGbDevice.getName());
setDeviceConnectionState(GBDevice.State.CONNECTED);
// update thread names to show device names in logs
readThread.setName(String.format(Locale.ENGLISH,
"Read Thread for %s", mGbDevice.getName()));
writeThread.setName(String.format(Locale.ENGLISH,
"Write Thread for %s", mGbDevice.getName()));
// now that connect has been created, start the threads
readThread.start();
writeThread.start();
} catch (IOException e) { } catch (IOException e) {
LOG.error("Unable to connect to RFCOMM endpoint: ", e); LOG.error("Unable to connect to RFCOMM endpoint: ", e);
setDeviceConnectionState(originalState); setDeviceConnectionState(originalState);
@ -221,7 +211,8 @@ public final class BtBRQueue {
return false; return false;
} }
onConnectionEstablished(); LOG.debug("Socket created, connecting in handler");
mWriteHandler.sendMessageAtFrontOfQueue(mWriteHandler.obtainMessage(HANDLER_SUBJECT_CONNECT));
return true; return true;
} }
@ -230,18 +221,15 @@ public final class BtBRQueue {
} }
public void disconnect() { public void disconnect() {
if (mBtSocket != null) { if (mWriteHandlerThread.isAlive()) {
mWriteHandlerThread.quit();
}
if (mBtSocket != null && mBtSocket.isConnected()) {
try { try {
mAvailableData = new CountDownLatch(1);
if (!mAvailableData.await(1, TimeUnit.SECONDS)) {
LOG.warn("disconnect(): Latch timeout reached while waiting for incoming data");
}
mAvailableData = null;
mBtSocket.close(); mBtSocket.close();
} catch (IOException | InterruptedException e) { } catch (IOException e) {
LOG.error(e.getMessage()); LOG.error("IO exception while closing socket in disconnect(): ", e);
} }
} }
} }
@ -258,14 +246,15 @@ public final class BtBRQueue {
} }
/** /**
* Adds a transaction to the end of the queue. * Add a finalized {@link Transaction} to the write handler's queue
* *
* @param transaction * @param transaction The transaction to be run in the handler thread's looper
*/ */
public void add(Transaction transaction) { public void add(Transaction transaction) {
LOG.debug("about to add: " + transaction); LOG.debug("Adding transaction to looper message queue: {}", transaction);
if (!transaction.isEmpty()) { if (!transaction.isEmpty()) {
mTransactions.add(transaction); mWriteHandler.obtainMessage(HANDLER_SUBJECT_PERFORM_TRANSACTION, transaction).sendToTarget();
} }
} }
@ -282,10 +271,10 @@ public final class BtBRQueue {
mDisposed = true; mDisposed = true;
disconnect(); disconnect();
writeThread.interrupt();
writeThread = null; if (readThread != null && readThread.isAlive()) {
readThread.interrupt(); readThread.interrupt();
readThread = null; readThread = null;
mTransactions.clear(); }
} }
} }