BT Classic/BTBR: Fix concurrency issues on connection

This commit is contained in:
José Rebelo
2025-07-03 19:59:00 +02:00
committed by José Rebelo
parent c34885974a
commit 8916555044
18 changed files with 103 additions and 133 deletions
@@ -25,7 +25,6 @@ import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
public abstract class AbstractHeadphoneDeviceSupport extends AbstractSerialDeviceSupport implements HeadphoneHelper.Callback {
private HeadphoneHelper headphoneHelper;
@Override
@@ -44,12 +43,6 @@ public abstract class AbstractHeadphoneDeviceSupport extends AbstractSerialDevic
headphoneHelper = new HeadphoneHelper(getContext(), getDevice(), this);
}
@Override
public boolean connect() {
getDeviceIOThread().start();
return true;
}
@Override
public void onSendConfiguration(String config) {
if (!headphoneHelper.onSendConfiguration(config))
@@ -58,8 +51,10 @@ public abstract class AbstractHeadphoneDeviceSupport extends AbstractSerialDevic
@Override
public void dispose() {
if (headphoneHelper != null)
headphoneHelper.dispose();
super.dispose();
synchronized (ConnectionMonitor) {
if (headphoneHelper != null)
headphoneHelper.dispose();
super.dispose();
}
}
}
@@ -36,6 +36,10 @@ import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
* @see nodomain.freeyourgadget.gadgetbridge.service.btclassic.BtClassicIoThread
*/
public abstract class AbstractBTBRDeviceSupport extends AbstractDeviceSupport implements SocketCallback {
/// used to guard {@link #connect()}, {@link #disconnect()} and {@link #dispose()}
protected final Object ConnectionMonitor = new Object();
private BtBRQueue mQueue;
private UUID mSupportedService = null;
private int mBufferSize = 1024;
@@ -50,20 +54,24 @@ public abstract class AbstractBTBRDeviceSupport extends AbstractDeviceSupport im
@Override
public boolean connect() {
final UUID supportedService = getSupportedService();
if (supportedService == null) {
throw new NullPointerException("No supported service UUID specified");
}
synchronized (ConnectionMonitor) {
final UUID supportedService = getSupportedService();
if (supportedService == null) {
throw new NullPointerException("No supported service UUID specified");
}
if (mQueue == null) {
mQueue = new BtBRQueue(getBluetoothAdapter(), getDevice(), getContext(), this, supportedService, getBufferSize());
if (mQueue == null) {
mQueue = new BtBRQueue(getBluetoothAdapter(), getDevice(), getContext(), this, supportedService, getBufferSize());
}
return mQueue.connect();
}
return mQueue.connect();
}
public void disconnect() {
if (mQueue != null) {
mQueue.disconnect();
synchronized (ConnectionMonitor) {
if (mQueue != null) {
mQueue.disconnect();
}
}
}
@@ -80,9 +88,11 @@ public abstract class AbstractBTBRDeviceSupport extends AbstractDeviceSupport im
@Override
public void dispose() {
if (mQueue != null) {
mQueue.dispose();
mQueue = null;
synchronized (ConnectionMonitor) {
if (mQueue != null) {
mQueue.dispose();
mQueue = null;
}
}
}
@@ -91,7 +101,7 @@ public abstract class AbstractBTBRDeviceSupport extends AbstractDeviceSupport im
}
@Override
public boolean isConnected(){
public boolean isConnected() {
// in a multi-threaded environment the queue knows
// best about the up-to-date connection status
return (mQueue != null) && mQueue.isConnected();
@@ -33,7 +33,6 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Arrays;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
@@ -171,13 +170,11 @@ public final class BtBRQueue {
return;
}
if (!(msg.obj instanceof Transaction)) {
if (!(msg.obj instanceof Transaction 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);
@@ -212,8 +209,15 @@ public final class BtBRQueue {
*/
@SuppressLint("MissingPermission")
public boolean connect() {
if (isConnected()) {
LOG.warn("Ignoring connect() because already connected.");
final GBDevice.State state = mGbDevice.getState();
if (state.equalsOrHigherThan(GBDevice.State.CONNECTING)) {
LOG.warn("connect - ignored, state is {}", state);
return false;
} else if (mBtSocket != null) {
LOG.warn("connect - ignored, mBtSocket isn't null");
return false;
} else if (mDisposed.get()) {
LOG.error("connect - ignored, this BtBRQueue has already been disposed");
return false;
}
@@ -304,6 +308,7 @@ public final class BtBRQueue {
public void dispose() {
if (mDisposed.getAndSet(true)) {
LOG.warn("dispose() was called repeatedly");
return;
}
@@ -101,13 +101,15 @@ public class AAWirelessSupport extends AbstractBTBRDeviceSupport {
@Override
public void dispose() {
try {
getContext().unregisterReceiver(commandReceiver);
} catch (final Exception e) {
LOG.warn("Failed to unregister receiver", e);
}
synchronized (ConnectionMonitor) {
try {
getContext().unregisterReceiver(commandReceiver);
} catch (final Exception e) {
LOG.warn("Failed to unregister receiver", e);
}
super.dispose();
super.dispose();
}
}
@Override
@@ -19,28 +19,16 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.divoom;
import android.net.Uri;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
public class PixooSupport extends AbstractSerialDeviceSupport {
private static final Logger LOG = LoggerFactory.getLogger(PixooSupport.class);
@Override
public void onSendConfiguration(String config) {
super.onSendConfiguration(config);
}
@Override
public boolean connect() {
getDeviceIOThread().start();
return true;
}
@Override
public synchronized PixooIOThread getDeviceIOThread() {
return (PixooIOThread) super.getDeviceIOThread();
@@ -117,9 +117,11 @@ public class ZeppOsBtbrSupport extends AbstractBTBRDeviceSupport implements Zepp
@Override
public void dispose() {
zeppOsSupport.dispose();
pingHandler.removeCallbacksAndMessages(null);
super.dispose();
synchronized (ConnectionMonitor) {
zeppOsSupport.dispose();
pingHandler.removeCallbacksAndMessages(null);
super.dispose();
}
}
@Override
@@ -199,8 +199,10 @@ public class HuaweiBRSupport extends AbstractBTBRDeviceSupport {
@Override
public void dispose() {
supportProvider.dispose();
super.dispose();
synchronized (ConnectionMonitor) {
supportProvider.dispose();
super.dispose();
}
}
@Override
@@ -108,9 +108,11 @@ public class HuaweiFreebudsSupport extends HuaweiBRSupport implements HeadphoneH
@Override
public void dispose() {
if (headphoneHelper != null)
headphoneHelper.dispose();
super.dispose();
synchronized (ConnectionMonitor) {
if (headphoneHelper != null)
headphoneHelper.dispose();
super.dispose();
}
}
@Override
@@ -17,29 +17,11 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.liveview;
import android.net.Uri;
import java.util.ArrayList;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
public class LiveviewSupport extends AbstractSerialDeviceSupport {
@Override
public boolean connect() {
getDeviceIOThread().start();
return true;
}
@Override
protected GBDeviceProtocol createDeviceProtocol() {
return new LiveviewProtocol(getDevice());
@@ -59,9 +41,4 @@ public class LiveviewSupport extends AbstractSerialDeviceSupport {
public synchronized LiveviewIoThread getDeviceIOThread() {
return (LiveviewIoThread) super.getDeviceIOThread();
}
@Override
public void onNotification(NotificationSpec notificationSpec) {
super.onNotification(notificationSpec);
}
}
@@ -55,8 +55,10 @@ public class PebbleSupport extends AbstractSerialDeviceSupport {
@Override
public void dispose() {
super.dispose();
unregisterSunriseSunsetAlarmReceiver();
synchronized (ConnectionMonitor) {
super.dispose();
unregisterSunriseSunsetAlarmReceiver();
}
}
private void registerSunriseSunsetAlarmReceiver() {
@@ -79,8 +81,13 @@ public class PebbleSupport extends AbstractSerialDeviceSupport {
@Override
public boolean connect() {
getDeviceIOThread().start();
registerSunriseSunsetAlarmReceiver();
synchronized (ConnectionMonitor) {
final PebbleIoThread deviceIOThread = getDeviceIOThread();
if (!deviceIOThread.isAlive()) {
deviceIOThread.start();
}
registerSunriseSunsetAlarmReceiver();
}
return true;
}
@@ -5,13 +5,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
public class PixelBudsADeviceSupport extends AbstractSerialDeviceSupport {
@Override
public boolean connect() {
getDeviceIOThread().start();
return true;
}
@Override
public boolean useAutoConnect() {
return false;
@@ -21,14 +21,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
public class QC35BaseSupport extends AbstractSerialDeviceSupport {
@Override
public boolean connect() {
getDeviceProtocol();
getDeviceIOThread().start();
return true;
}
@Override
public boolean useAutoConnect() {
return false;
@@ -33,12 +33,6 @@ public class RedmiBudsDeviceSupport extends AbstractSerialDeviceSupport {
RedmiBudsDeviceSupport.this, getBluetoothAdapter());
}
@Override
public boolean connect() {
getDeviceIOThread().start();
return true;
}
@Override
public boolean useAutoConnect() {
return false;
@@ -79,11 +79,14 @@ public class RoidmiSupport extends AbstractSerialDeviceSupport {
@Override
public boolean connect() {
getDeviceIOThread().start();
requestDeviceInfos(1500);
return true;
synchronized (ConnectionMonitor) {
final RoidmiIoThread deviceIOThread = getDeviceIOThread();
if (!deviceIOThread.isAlive()) {
deviceIOThread.start();
requestDeviceInfos(1500);
}
return true;
}
}
@Override
@@ -104,7 +107,7 @@ public class RoidmiSupport extends AbstractSerialDeviceSupport {
@Override
public void onSendConfiguration(final String config) {
LOG.debug("onSendConfiguration " + config);
LOG.debug("onSendConfiguration {}", config);
final RoidmiIoThread roidmiIoThread = getDeviceIOThread();
final RoidmiProtocol roidmiProtocol = (RoidmiProtocol) getDeviceProtocol();
@@ -120,7 +123,7 @@ public class RoidmiSupport extends AbstractSerialDeviceSupport {
roidmiIoThread.write(roidmiProtocol.encodeGetVoltage());
break;
default:
LOG.error("Invalid Roidmi configuration " + config);
LOG.error("Invalid Roidmi configuration {}", config);
break;
}
}
@@ -9,12 +9,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
public class SoundcoreLiberty3ProDeviceSupport extends AbstractSerialDeviceSupport {
public static final UUID UUID_DEVICE_CTRL = UUID.fromString("0cf12d31-fac3-4553-bd80-d6832e7b3952");
@Override
public boolean connect() {
getDeviceIOThread().start();
return true;
}
@Override
public boolean useAutoConnect() {
return false;
@@ -9,12 +9,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
public class SoundcoreLiberty4NCDeviceSupport extends AbstractSerialDeviceSupport {
public static final UUID UUID_DEVICE_CTRL = UUID.fromString("0cf12d31-fac3-4553-bd80-d6832e7b3947");
@Override
public boolean connect() {
getDeviceIOThread().start();
return true;
}
@Override
public boolean useAutoConnect() {
return false;
@@ -16,12 +16,6 @@ public class SoundcoreQ30DeviceSupport extends AbstractSerialDeviceSupport {
return new SoundcoreQ30IOThread(getDevice(), getContext(), (SoundcoreQ30Protocol) getDeviceProtocol(),this, getBluetoothAdapter());
}
@Override
public boolean connect() {
getDeviceIOThread().start();
return true;
}
@Override
public boolean useAutoConnect() {
return false;
@@ -56,6 +56,9 @@ import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport {
private static final Logger LOG = LoggerFactory.getLogger(AbstractSerialDeviceSupport.class);
/// used to guard {@link #connect()} and {@link #dispose()}
protected final Object ConnectionMonitor = new Object();
private GBDeviceProtocol gbDeviceProtocol;
protected GBDeviceIoThread gbDeviceIOThread;
@@ -69,13 +72,26 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport
*/
protected abstract GBDeviceIoThread createDeviceIOThread();
@Override
public boolean connect() {
synchronized (ConnectionMonitor) {
final GBDeviceIoThread deviceIOThread = getDeviceIOThread();
if (!deviceIOThread.isAlive()) {
deviceIOThread.start();
}
return true;
}
}
@Override
public void dispose() {
// currently only one thread allowed
if (gbDeviceIOThread != null) {
gbDeviceIOThread.quit();
gbDeviceIOThread.interrupt();
gbDeviceIOThread = null;
synchronized (ConnectionMonitor) {
// currently only one thread allowed
if (gbDeviceIOThread != null) {
gbDeviceIOThread.quit();
gbDeviceIOThread.interrupt();
gbDeviceIOThread = null;
}
}
}