BtLEAction: code review

This commit is contained in:
Thomas Kuehne
2026-06-14 10:43:39 +00:00
parent 0fd997ccaf
commit 2a7ccef529
18 changed files with 116 additions and 63 deletions
@@ -1,4 +1,4 @@
/* Copyright (C) 2015-2024 Carsten Pfeiffer, Uwe Hermann /* Copyright (C) 2015-2026 Carsten Pfeiffer, Uwe Hermann, Thomas Kuehne
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@@ -19,15 +19,19 @@ package nodomain.freeyourgadget.gadgetbridge.service.btle;
import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattCharacteristic;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
/** /**
* The Bluedroid implementation only allows performing one GATT request at a time. * The Bluedroid implementation only allows performing one GATT request at a time.
* As they are asynchronous anyway, we encapsulate every GATT request (read and write) * As they are asynchronous anyway, we encapsulate every GATT request (read and write)
* inside a runnable action. * inside a runnable action.
* <p/> * <p>
* These actions are then executed one after another, ensuring that every action's result * These actions are then executed one after another, ensuring that every action's result
* has been posted before invoking the next action. * has been posted before invoking the next action.
* </p>
*/ */
public abstract class BtLEAction { public abstract class BtLEAction {
private final BluetoothGattCharacteristic characteristic; private final BluetoothGattCharacteristic characteristic;
@@ -41,33 +45,36 @@ public abstract class BtLEAction {
/** /**
* Returns true if this action expects an (async) result which must * Returns true if this action expects an (async) result which must
* be waited for, before continuing with other actions. * be waited for, before continuing with other actions.
* <p/> * <p>
* This is needed because the current Bluedroid stack can only deal * This is needed because the current Bluedroid stack can only deal
* with one single bluetooth operation at a time. * with one single bluetooth operation at a time.
* </p>
*/ */
public abstract boolean expectsResult(); public abstract boolean expectsResult();
/** /**
* Executes this action, e.g. reads or write a GATT characteristic. * Executes this action, e.g. reads or write a {@link GattCharacteristic}.
* *
* @param gatt the characteristic to manipulate, or null if none. * @return {@code true} if the action was successful, {@code false} otherwise
* @return true if the action was successful, false otherwise
*/ */
public abstract boolean run(BluetoothGatt gatt); public abstract boolean run(@NonNull BluetoothGatt gatt);
/** /**
* Returns the GATT characteristic being read/written/... * Returns the GATT characteristic being read/written/...
* *
* @return the GATT characteristic, or <code>null</code> * @return the GATT characteristic, or {@code null}
*/ */
@Nullable
public BluetoothGattCharacteristic getCharacteristic() { public BluetoothGattCharacteristic getCharacteristic() {
return characteristic; return characteristic;
} }
@NonNull
protected String getCreationTime() { protected String getCreationTime() {
return DateTimeUtils.formatLocalTime(creationTimestamp); return DateTimeUtils.formatLocalTime(creationTimestamp);
} }
@NonNull
public String toString() { public String toString() {
BluetoothGattCharacteristic characteristic = getCharacteristic(); BluetoothGattCharacteristic characteristic = getCharacteristic();
String uuid = characteristic == null ? "(null)" : characteristic.getUuid().toString(); String uuid = characteristic == null ? "(null)" : characteristic.getUuid().toString();
@@ -1,4 +1,4 @@
/* Copyright (C) 2015-2024 Carsten Pfeiffer /* Copyright (C) 2015-2026 Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@@ -18,6 +18,8 @@ package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGatt;
import androidx.annotation.NonNull;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -29,7 +31,7 @@ public abstract class AbortTransactionAction extends PlainAction {
private static final Logger LOG = LoggerFactory.getLogger(AbortTransactionAction.class); private static final Logger LOG = LoggerFactory.getLogger(AbortTransactionAction.class);
@Override @Override
public boolean run(BluetoothGatt gatt) { public boolean run(@NonNull BluetoothGatt gatt) {
if (shouldAbort()) { if (shouldAbort()) {
LOG.info("Aborting transaction because abort criteria met."); LOG.info("Aborting transaction because abort criteria met.");
return false; return false;
@@ -39,6 +41,7 @@ public abstract class AbortTransactionAction extends PlainAction {
protected abstract boolean shouldAbort(); protected abstract boolean shouldAbort();
@NonNull
@Override @Override
public String toString() { public String toString() {
return getCreationTime() + " " + getClass().getSimpleName() + " abort=" + shouldAbort(); return getCreationTime() + " " + getClass().getSimpleName() + " abort=" + shouldAbort();
@@ -1,4 +1,4 @@
/* Copyright (C) 2023-2025 Andreas Böhler, Thomas Kuehne /* Copyright (C) 2023-2026 Andreas Böhler, Thomas Kuehne
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@@ -22,6 +22,7 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.IntentFilter; import android.content.IntentFilter;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.localbroadcastmanager.content.LocalBroadcastManager;
@@ -70,7 +71,7 @@ public class BondAction extends PlainAction implements BondingInterface {
} }
@Override @Override
public boolean run(BluetoothGatt gatt) { public boolean run(@NonNull BluetoothGatt gatt) {
BluetoothDevice device = gatt.getDevice(); BluetoothDevice device = gatt.getDevice();
mCandidate = new GBDeviceCandidate(device, GBDevice.RSSI_UNKNOWN, null, null); mCandidate = new GBDeviceCandidate(device, GBDevice.RSSI_UNKNOWN, null, null);
BondingUtil.tryBondThenComplete(this, device); BondingUtil.tryBondThenComplete(this, device);
@@ -1,4 +1,4 @@
/* Copyright (C) 2016-2024 Carsten Pfeiffer /* Copyright (C) 2016-2026 Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@@ -16,6 +16,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */ along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.btle.actions; package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
import androidx.annotation.NonNull;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -29,9 +31,10 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class CheckInitializedAction extends AbortTransactionAction { public class CheckInitializedAction extends AbortTransactionAction {
private static final Logger LOG = LoggerFactory.getLogger(CheckInitializedAction.class); private static final Logger LOG = LoggerFactory.getLogger(CheckInitializedAction.class);
@NonNull
private final GBDevice device; private final GBDevice device;
public CheckInitializedAction(GBDevice gbDevice) { public CheckInitializedAction(@NonNull GBDevice gbDevice) {
device = gbDevice; device = gbDevice;
} }
@@ -39,7 +42,7 @@ public class CheckInitializedAction extends AbortTransactionAction {
protected boolean shouldAbort() { protected boolean shouldAbort() {
boolean abort = device.isInitialized(); boolean abort = device.isInitialized();
if (abort) { if (abort) {
LOG.info("Aborting device initialization, because already initialized: " + device); LOG.info("Aborting device initialization, because already initialized: {}", device);
} }
return abort; return abort;
} }
@@ -1,4 +1,4 @@
/* Copyright (C) 2016-2025 Andreas Shimokawa, Carsten Pfeiffer, Thomas Kuehne /* Copyright (C) 2016-2026 Andreas Shimokawa, Carsten Pfeiffer, Thomas Kuehne
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@@ -19,16 +19,19 @@ package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattCharacteristic;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public abstract class ConditionalWriteAction extends WriteAction { public abstract class ConditionalWriteAction extends WriteAction {
private boolean actualGenerated; private boolean actualGenerated;
private byte[] actual; private byte[] actual;
public ConditionalWriteAction(BluetoothGattCharacteristic characteristic) { public ConditionalWriteAction(@NonNull BluetoothGattCharacteristic characteristic) {
super(characteristic, null); super(characteristic, null);
} }
@Override @Override
public boolean run(BluetoothGatt gatt) { public boolean run(@NonNull BluetoothGatt gatt) {
byte[] value = getValue(); byte[] value = getValue();
if (value != null) { if (value != null) {
return super.run(gatt); return super.run(gatt);
@@ -47,12 +50,14 @@ public abstract class ConditionalWriteAction extends WriteAction {
/** /**
* Checks the condition whether the write shall happen or not. * Checks the condition whether the write shall happen or not.
* Returns the actual value to be written or null in case nothing shall be written. * Returns the actual value to be written or {@code null} in case nothing shall be written.
* <p/> * <p>
* Note that returning null will not cause {@link #run()} to return false, in other words, * Note that returning {@code null} will not cause {@link #run(BluetoothGatt)} to return {@code false}, in other words,
* the rest of the queue will still be executed. * the rest of the queue will still be executed.
* </p>
* *
* @return the value to be written or null to not write anything * @return the value to be written or {@code null} to not write anything
*/ */
@Nullable
protected abstract byte[] checkCondition(); protected abstract byte[] checkCondition();
} }
@@ -1,4 +1,4 @@
/* Copyright (C) 2023-2025 Johannes Krude, Thomas Kuehne /* Copyright (C) 2023-2026 Johannes Krude, Thomas Kuehne
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@@ -50,7 +50,7 @@ public class FunctionAction extends BtLEAction {
} }
@Override @Override
public boolean run(BluetoothGatt gatt) { public boolean run(@NonNull BluetoothGatt gatt) {
try { try {
final boolean success; final boolean success;
if (mRunnable != null) { if (mRunnable != null) {
@@ -1,4 +1,4 @@
/* Copyright (C) 2015-2024 Alicia Hormann, Carsten Pfeiffer, Daniele Gobbetti /* Copyright (C) 2015-2026 Alicia Hormann, Carsten Pfeiffer, Daniele Gobbetti
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@@ -32,6 +32,8 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCallback;
import static nodomain.freeyourgadget.gadgetbridge.service.btle.GattDescriptor.UUID_DESCRIPTOR_GATT_CLIENT_CHARACTERISTIC_CONFIGURATION; import static nodomain.freeyourgadget.gadgetbridge.service.btle.GattDescriptor.UUID_DESCRIPTOR_GATT_CLIENT_CHARACTERISTIC_CONFIGURATION;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresPermission; import androidx.annotation.RequiresPermission;
/** /**
@@ -43,16 +45,19 @@ public class NotifyAction extends BtLEAction {
private static final Logger LOG = LoggerFactory.getLogger(NotifyAction.class); private static final Logger LOG = LoggerFactory.getLogger(NotifyAction.class);
private final boolean enableFlag; private final boolean enableFlag;
private boolean hasWrittenDescriptor = false; private boolean hasWrittenDescriptor;
public NotifyAction(BluetoothGattCharacteristic characteristic, boolean enable) { public NotifyAction(@NonNull BluetoothGattCharacteristic characteristic, boolean enable) {
super(characteristic); super(characteristic);
enableFlag = enable; enableFlag = enable;
hasWrittenDescriptor = false;
} }
/// shared write implementation that can be used without a BtLEAction /// shared write implementation that can be used without a BtLEAction
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
public static boolean writeDescriptor(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final byte[] value) { public static boolean writeDescriptor(@Nullable final BluetoothGatt gatt,
@Nullable final BluetoothGattDescriptor descriptor,
@NonNull final byte[] value) {
if (gatt == null) { if (gatt == null) {
LOG.error("gatt == null"); LOG.error("gatt == null");
return false; return false;
@@ -96,7 +101,7 @@ public class NotifyAction extends BtLEAction {
@Override @Override
@RequiresPermission("android.permission.BLUETOOTH_CONNECT") @RequiresPermission("android.permission.BLUETOOTH_CONNECT")
public boolean run(BluetoothGatt gatt) { public boolean run(@NonNull BluetoothGatt gatt) {
// register gatt's callback to receive notifications // register gatt's callback to receive notifications
boolean result = gatt.setCharacteristicNotification(getCharacteristic(), enableFlag); boolean result = gatt.setCharacteristicNotification(getCharacteristic(), enableFlag);
@@ -137,6 +142,7 @@ public class NotifyAction extends BtLEAction {
return hasWrittenDescriptor; return hasWrittenDescriptor;
} }
@NonNull
@Override @Override
public String toString() { public String toString() {
BluetoothGattCharacteristic characteristic = getCharacteristic(); BluetoothGattCharacteristic characteristic = getCharacteristic();
@@ -1,4 +1,4 @@
/* Copyright (C) 2015-2024 Carsten Pfeiffer /* Copyright (C) 2015-2026 Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@@ -16,6 +16,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */ along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.btle.actions; package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
import androidx.annotation.NonNull;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction; import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic; import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
@@ -34,6 +36,7 @@ public abstract class PlainAction extends BtLEAction {
return false; return false;
} }
@NonNull
@Override @Override
public String toString() { public String toString() {
return getCreationTime() + " " + getClass().getSimpleName(); return getCreationTime() + " " + getClass().getSimpleName();
@@ -1,4 +1,4 @@
/* Copyright (C) 2015-2024 Carsten Pfeiffer, Daniele Gobbetti /* Copyright (C) 2015-2026 Carsten Pfeiffer, Daniele Gobbetti
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@@ -20,6 +20,8 @@ import android.annotation.SuppressLint;
import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattCharacteristic;
import androidx.annotation.NonNull;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -34,13 +36,13 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCallback;
public class ReadAction extends BtLEAction { public class ReadAction extends BtLEAction {
private static final Logger LOG = LoggerFactory.getLogger(ReadAction.class); private static final Logger LOG = LoggerFactory.getLogger(ReadAction.class);
public ReadAction(BluetoothGattCharacteristic characteristic) { public ReadAction(@NonNull BluetoothGattCharacteristic characteristic) {
super(characteristic); super(characteristic);
} }
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
@Override @Override
public boolean run(BluetoothGatt gatt) { public boolean run(@NonNull BluetoothGatt gatt) {
int properties = getCharacteristic().getProperties(); int properties = getCharacteristic().getProperties();
if ((properties & BluetoothGattCharacteristic.PROPERTY_READ) > 0) { if ((properties & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
return gatt.readCharacteristic(getCharacteristic()); return gatt.readCharacteristic(getCharacteristic());
@@ -54,6 +56,7 @@ public class ReadAction extends BtLEAction {
return true; return true;
} }
@NonNull
@Override @Override
public String toString() { public String toString() {
BluetoothGattCharacteristic characteristic = getCharacteristic(); BluetoothGattCharacteristic characteristic = getCharacteristic();
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Thomas Kuehne /* Copyright (C) 2025-2026 Thomas Kuehne
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@@ -21,6 +21,7 @@ import android.annotation.SuppressLint;
import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGatt;
import android.os.Build; import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -47,7 +48,7 @@ public class ReadPhyAction extends BtLEAction {
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
@Override @Override
public boolean run(BluetoothGatt gatt) { public boolean run(@NonNull BluetoothGatt gatt) {
try { try {
gatt.readPhy(); gatt.readPhy();
return true; return true;
@@ -57,6 +58,7 @@ public class ReadPhyAction extends BtLEAction {
} }
} }
@NonNull
@Override @Override
public String toString() { public String toString() {
return getCreationTime() + " " + getClass().getSimpleName(); return getCreationTime() + " " + getClass().getSimpleName();
@@ -1,4 +1,4 @@
/* Copyright (C) 2023-2024 José Rebelo /* Copyright (C) 2023-2026 José Rebelo
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@@ -19,6 +19,8 @@ package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGatt;
import androidx.annotation.NonNull;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BleNamesResolver; import nodomain.freeyourgadget.gadgetbridge.service.btle.BleNamesResolver;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction; import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
@@ -38,10 +40,11 @@ public class RequestConnectionPriorityAction extends BtLEAction {
@Override @Override
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
public boolean run(final BluetoothGatt gatt) { public boolean run(@NonNull final BluetoothGatt gatt) {
return gatt.requestConnectionPriority(priority); return gatt.requestConnectionPriority(priority);
} }
@NonNull
@Override @Override
public String toString() { public String toString() {
return getCreationTime() + " " + getClass().getSimpleName() + " " + return getCreationTime() + " " + getClass().getSimpleName() + " " +
@@ -1,4 +1,4 @@
/* Copyright (C) 2019-2024 Andreas Shimokawa, Daniel Dakhno /* Copyright (C) 2019-2026 Andreas Shimokawa, Daniel Dakhno
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@@ -19,6 +19,9 @@ package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGatt;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction; import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCallback; import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCallback;
@@ -27,7 +30,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCallback;
public class RequestMtuAction extends BtLEAction { public class RequestMtuAction extends BtLEAction {
private final int mtu; private final int mtu;
public RequestMtuAction(int mtu) { public RequestMtuAction(@IntRange(from = 23L, to = 517L) final int mtu) {
super(null); super(null);
this.mtu = mtu; this.mtu = mtu;
} }
@@ -40,10 +43,11 @@ public class RequestMtuAction extends BtLEAction {
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
@Override @Override
public boolean run(BluetoothGatt gatt) { public boolean run(@NonNull final BluetoothGatt gatt) {
return gatt.requestMtu(this.mtu); return gatt.requestMtu(mtu);
} }
@NonNull
@Override @Override
public String toString() { public String toString() {
return getCreationTime() + " " + getClass().getSimpleName() + " mtu=" + mtu; return getCreationTime() + " " + getClass().getSimpleName() + " mtu=" + mtu;
@@ -1,4 +1,4 @@
/* Copyright (C) 2015-2025 Carsten Pfeiffer, Thomas Kuehne /* Copyright (C) 2015-2026 Carsten Pfeiffer, Thomas Kuehne
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@@ -45,7 +45,7 @@ public class SetDeviceBusyAction extends PlainAction {
} }
@Override @Override
public boolean run(BluetoothGatt gatt) { public boolean run(@NonNull BluetoothGatt gatt) {
if (busyTask == 0) { if (busyTask == 0) {
device.unsetBusyTask(); device.unsetBusyTask();
} else { } else {
@@ -56,6 +56,7 @@ public class SetDeviceBusyAction extends PlainAction {
return true; return true;
} }
@NonNull
@Override @Override
public String toString() { public String toString() {
return getCreationTime() + " " + getClass().getName() + " " return getCreationTime() + " " + getClass().getName() + " "
@@ -1,4 +1,4 @@
/* Copyright (C) 2015-2025 Andreas Shimokawa, Carsten Pfeiffer, Daniel Dakhno, Thomas Kuehne /* Copyright (C) 2015-2026 Andreas Shimokawa, Carsten Pfeiffer, Daniel Dakhno, Thomas Kuehne
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@@ -28,18 +28,19 @@ public class SetDeviceStateAction extends PlainAction {
private final GBDevice.State deviceState; private final GBDevice.State deviceState;
private final Context context; private final Context context;
public SetDeviceStateAction(GBDevice device, GBDevice.State deviceState, Context context) { public SetDeviceStateAction(@NonNull GBDevice device, @NonNull GBDevice.State deviceState, @NonNull Context context) {
this.device = device; this.device = device;
this.deviceState = deviceState; this.deviceState = deviceState;
this.context = context; this.context = context;
} }
@Override @Override
public boolean run(BluetoothGatt gatt) { public boolean run(@NonNull BluetoothGatt gatt) {
device.setUpdateState(deviceState, context); device.setUpdateState(deviceState, context);
return true; return true;
} }
@NonNull
public Context getContext() { public Context getContext() {
return context; return context;
} }
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Thomas Kuehne /* Copyright (C) 2025-2026 Thomas Kuehne
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@@ -21,6 +21,7 @@ import android.annotation.SuppressLint;
import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGatt;
import android.os.Build; import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -41,7 +42,7 @@ public class SetPreferredPhyAction extends BtLEAction {
private final int mRxPhy; private final int mRxPhy;
private final int mPhyOptions; private final int mPhyOptions;
public SetPreferredPhyAction(int txPhy, int rxPhy, int phyOptions) { public SetPreferredPhyAction(final int txPhy, final int rxPhy, final int phyOptions) {
super(null); super(null);
mTxPhy = txPhy; mTxPhy = txPhy;
mRxPhy = rxPhy; mRxPhy = rxPhy;
@@ -57,7 +58,7 @@ public class SetPreferredPhyAction extends BtLEAction {
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
@Override @Override
public boolean run(BluetoothGatt gatt) { public boolean run(@NonNull final BluetoothGatt gatt) {
try { try {
gatt.setPreferredPhy(mTxPhy, mRxPhy, mPhyOptions); gatt.setPreferredPhy(mTxPhy, mRxPhy, mPhyOptions);
return true; return true;
@@ -68,6 +69,7 @@ public class SetPreferredPhyAction extends BtLEAction {
} }
} }
@NonNull
@Override @Override
public String toString() { public String toString() {
return getCreationTime() + " " + getClass().getSimpleName() + " tx=" + mTxPhy return getCreationTime() + " " + getClass().getSimpleName() + " tx=" + mTxPhy
@@ -1,4 +1,4 @@
/* Copyright (C) 2015-2025 Andreas Shimokawa, Carsten Pfeiffer, José Rebelo, Thomas Kuehne /* Copyright (C) 2015-2026 Andreas Shimokawa, Carsten Pfeiffer, José Rebelo, Thomas Kuehne
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@@ -20,6 +20,7 @@ import android.bluetooth.BluetoothGatt;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.localbroadcastmanager.content.LocalBroadcastManager;
@@ -40,15 +41,15 @@ public class SetProgressAction extends PlainAction {
* @param percentage Current percentage indicating how far along the action has progressed * @param percentage Current percentage indicating how far along the action has progressed
* @param context Context in which to create the notification * @param context Context in which to create the notification
*/ */
public SetProgressAction(@StringRes int textRes, boolean ongoing, int percentage, Context context) { public SetProgressAction(@StringRes int textRes, boolean ongoing, int percentage, @NonNull Context context) {
this.text = context.getString(textRes);; this.text = context.getString(textRes);
this.ongoing = ongoing; this.ongoing = ongoing;
this.percentage = percentage; this.percentage = percentage;
this.context = context; this.context = context;
} }
@Override @Override
public boolean run(BluetoothGatt gatt) { public boolean run(@NonNull BluetoothGatt gatt) {
GB.updateInstallNotification(this.text, this.ongoing, this.percentage, this.context); GB.updateInstallNotification(this.text, this.ongoing, this.percentage, this.context);
final LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(context); final LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(context);
@@ -63,6 +64,7 @@ public class SetProgressAction extends PlainAction {
return true; return true;
} }
@NonNull
@Override @Override
public String toString() { public String toString() {
return getCreationTime() + " " + getClass().getSimpleName() + ": " + text + "; " + percentage + "%"; return getCreationTime() + " " + getClass().getSimpleName() + ": " + text + "; " + percentage + "%";
@@ -1,4 +1,4 @@
/* Copyright (C) 2015-2024 Carsten Pfeiffer /* Copyright (C) 2015-2026 Carsten Pfeiffer
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@@ -18,6 +18,9 @@ package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGatt;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
/** /**
* An action that will cause the queue to {@link Thread#sleep(long) sleep} for the specified time. * An action that will cause the queue to {@link Thread#sleep(long) sleep} for the specified time.
* Note that this is usually a bad idea, since it will not be able to process messages * Note that this is usually a bad idea, since it will not be able to process messages
@@ -25,22 +28,23 @@ import android.bluetooth.BluetoothGatt;
*/ */
public class SleepAction extends PlainAction { public class SleepAction extends PlainAction {
private final int mMillis; private final long mMillis;
public SleepAction(int millis) { public SleepAction(@IntRange(from = 0L) final long millis) {
mMillis = millis; mMillis = millis;
} }
@Override @Override
public boolean run(BluetoothGatt gatt) { public boolean run(@NonNull final BluetoothGatt gatt) {
try { try {
Thread.sleep(mMillis); Thread.sleep(mMillis);
return true; return true;
} catch (InterruptedException e) { } catch (final InterruptedException e) {
return false; return false;
} }
} }
@NonNull
@Override @Override
public String toString() { public String toString() {
return getCreationTime() + " " + getClass().getSimpleName() + " " + mMillis + " ms"; return getCreationTime() + " " + getClass().getSimpleName() + " " + mMillis + " ms";
@@ -1,4 +1,4 @@
/* Copyright (C) 2015-2025 Andreas Shimokawa, Carsten Pfeiffer, Daniele /* Copyright (C) 2015-2026 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, José Rebelo, Uwe Hermann, Thomas Kuehne Gobbetti, José Rebelo, Uwe Hermann, Thomas Kuehne
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@@ -22,6 +22,8 @@ import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothStatusCodes; import android.bluetooth.BluetoothStatusCodes;
import androidx.annotation.NonNull;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -42,18 +44,18 @@ public class WriteAction extends BtLEAction {
private final byte[] value; private final byte[] value;
private final boolean legacyCompat; private final boolean legacyCompat;
public WriteAction(BluetoothGattCharacteristic characteristic, byte[] value) { public WriteAction(@NonNull BluetoothGattCharacteristic characteristic, @NonNull byte[] value) {
this(characteristic, value, false); this(characteristic, value, false);
} }
public WriteAction(BluetoothGattCharacteristic characteristic, byte[] value, boolean legacyCompat) { public WriteAction(@NonNull BluetoothGattCharacteristic characteristic, @NonNull byte[] value, boolean legacyCompat) {
super(characteristic); super(characteristic);
this.value = value; this.value = value;
this.legacyCompat = legacyCompat; this.legacyCompat = legacyCompat;
} }
@Override @Override
public boolean run(BluetoothGatt gatt) { public boolean run(@NonNull BluetoothGatt gatt) {
BluetoothGattCharacteristic characteristic = getCharacteristic(); BluetoothGattCharacteristic characteristic = getCharacteristic();
int properties = characteristic.getProperties(); int properties = characteristic.getProperties();
//TODO: expectsResult should return false if PROPERTY_WRITE_NO_RESPONSE is true, but this leads to timing issues //TODO: expectsResult should return false if PROPERTY_WRITE_NO_RESPONSE is true, but this leads to timing issues
@@ -74,7 +76,7 @@ public class WriteAction extends BtLEAction {
} }
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
private static boolean writeCharacteristicImp(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, byte[] value, boolean legacyCompat) { private static boolean writeCharacteristicImp(@NonNull BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic, @NonNull byte[] value, boolean legacyCompat) {
if (GBApplication.isRunningTiramisuOrLater() && !legacyCompat) { if (GBApplication.isRunningTiramisuOrLater() && !legacyCompat) {
// use API introduced in SDK level 33 to catch exceptions and more specific errors // use API introduced in SDK level 33 to catch exceptions and more specific errors
try { try {
@@ -109,6 +111,7 @@ public class WriteAction extends BtLEAction {
return true; return true;
} }
@NonNull
@Override @Override
public String toString() { public String toString() {
BluetoothGattCharacteristic characteristic = getCharacteristic(); BluetoothGattCharacteristic characteristic = getCharacteristic();