casio: introduce BLE legacy writes to fake not-yet-written characteristic values (#5008)

This commit is contained in:
Thomas Kuehne
2025-07-04 19:55:10 +02:00
committed by José Rebelo
parent ed5de171e8
commit 2709446597
8 changed files with 44 additions and 17 deletions
@@ -18,6 +18,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.btle;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.Context;
import android.os.Build;
@@ -64,6 +65,23 @@ public class TransactionBuilder {
return add(action);
}
/// Use this only if <strong>ALL</strong> conditions are true:
/// <ol>
/// <li>characteristic has write type {@link BluetoothGattCharacteristic#WRITE_TYPE_NO_RESPONSE},</li>
/// <li>custom {@link GattCallback#onCharacteristicWrite(BluetoothGatt, BluetoothGattCharacteristic, int)}
/// uses {@link BluetoothGattCharacteristic#getValue()} and</li>
/// <li>no {@link BluetoothGatt#beginReliableWrite()} was used.</li>
/// </ol>
/// @see WriteAction
public TransactionBuilder writeLegacy(BluetoothGattCharacteristic characteristic, byte[] data) {
if (characteristic == null) {
LOG.warn("Unable to write characteristic: null");
return this;
}
WriteAction action = new WriteAction(characteristic, data, true);
return add(action);
}
/// @see WriteAction
public TransactionBuilder write(BluetoothGattCharacteristic characteristic, byte[] data) {
if (characteristic == null) {
@@ -40,10 +40,16 @@ public class WriteAction extends BtLEAction {
private static final Logger LOG = LoggerFactory.getLogger(WriteAction.class);
private final byte[] value;
private final boolean legacyCompat;
public WriteAction(BluetoothGattCharacteristic characteristic, byte[] value) {
this(characteristic, value, false);
}
public WriteAction(BluetoothGattCharacteristic characteristic, byte[] value, boolean legacyCompat) {
super(characteristic);
this.value = value;
this.legacyCompat = legacyCompat;
}
@Override
@@ -52,7 +58,7 @@ public class WriteAction extends BtLEAction {
int properties = characteristic.getProperties();
//TODO: expectsResult should return false if PROPERTY_WRITE_NO_RESPONSE is true, but this leads to timing issues
if ((properties & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0 || ((properties & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0)) {
return writeCharacteristicImp(gatt, characteristic, getValue());
return writeCharacteristicImp(gatt, characteristic, getValue(), legacyCompat);
}
LOG.error("WriteAction for non-writeable characteristic {}", characteristic.getUuid());
@@ -64,14 +70,17 @@ public class WriteAction extends BtLEAction {
if (LOG.isDebugEnabled()) {
LOG.debug("writing to characteristic: {} - {}", characteristic.getUuid(), Logging.formatBytes(value));
}
return writeCharacteristicImp(gatt, characteristic, value);
return writeCharacteristicImp(gatt, characteristic, value, false);
}
@SuppressLint("MissingPermission")
private static boolean writeCharacteristicImp(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, byte[] value) {
private static boolean writeCharacteristicImp(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, byte[] value, boolean legacyCompat) {
if (GBApplication.isRunningTiramisuOrLater()) {
// use API introduced in SDK level 33 to catch exceptions and more specific errors
try {
if (legacyCompat) {
characteristic.setValue(value);
}
final int status = gatt.writeCharacteristic(characteristic, value, characteristic.getWriteType());
if (status == BluetoothStatusCodes.SUCCESS) {
return true;
@@ -131,11 +131,11 @@ public abstract class Casio2C2DSupport extends CasioSupport {
if (!requests.isEmpty()) {
LOG.warn("writing while waiting for a response may lead to incorrect received responses");
}
builder.write(getCharacteristic(CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID), arr);
builder.writeLegacy(getCharacteristic(CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID), arr);
}
public void writeAllFeaturesRequest(TransactionBuilder builder, byte[] arr) {
builder.write(getCharacteristic(CasioConstants.CASIO_READ_REQUEST_FOR_ALL_FEATURES_CHARACTERISTIC_UUID), arr);
builder.writeLegacy(getCharacteristic(CasioConstants.CASIO_READ_REQUEST_FOR_ALL_FEATURES_CHARACTERISTIC_UUID), arr);
}
public interface ResponseHandler {
@@ -208,7 +208,7 @@ public class CasioGB6900DeviceSupport extends CasioSupport {
BluetoothGattService llService = mBtGatt.getService(CasioConstants.LINK_LOSS_SERVICE);
BluetoothGattCharacteristic charact = llService.getCharacteristic(CasioConstants.ALERT_LEVEL_CHARACTERISTIC_UUID);
builder.write(charact, value);
builder.writeLegacy(charact, value);
builder.wait(mCasioSleepTime);
}
@@ -244,7 +244,7 @@ public class CasioGB6900DeviceSupport extends CasioSupport {
BluetoothGattCharacteristic charact = getCharacteristic(CasioConstants.CURRENT_TIME_CHARACTERISTIC_UUID);
if(charact != null) {
charact.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
builder.write(charact, arr);
builder.writeLegacy(charact, arr);
}
else {
LOG.warn("Characteristic not found: CURRENT_TIME_CHARACTERISTIC_UUID");
@@ -259,7 +259,7 @@ public class CasioGB6900DeviceSupport extends CasioSupport {
byte byte1 = (byte)(dstOffset / 15);
BluetoothGattCharacteristic charact = getCharacteristic(CasioConstants.LOCAL_TIME_CHARACTERISTIC_UUID);
if(charact != null) {
builder.write(charact, new byte[]{byte0, byte1});
builder.writeLegacy(charact, new byte[]{byte0, byte1});
}
else {
LOG.warn("Characteristic not found: LOCAL_TIME_CHARACTERISTIC_UUID");
@@ -276,7 +276,7 @@ public class CasioGB6900DeviceSupport extends CasioSupport {
BluetoothGattCharacteristic charact = getCharacteristic(CasioConstants.CASIO_VIRTUAL_SERVER_FEATURES);
if(charact != null) {
builder.write(charact, new byte[]{byte0, 0x00});
builder.writeLegacy(charact, new byte[]{byte0, 0x00});
}
else {
LOG.warn("Characteristic not found: CASIO_VIRTUAL_SERVER_FEATURES");
@@ -462,7 +462,7 @@ public class CasioGB6900DeviceSupport extends CasioSupport {
msg[1] = 1;
System.arraycopy(titleBytes, 0, msg, 2, len);
builder.write(getCharacteristic(CasioConstants.ALERT_CHARACTERISTIC_UUID), msg);
builder.writeLegacy(getCharacteristic(CasioConstants.ALERT_CHARACTERISTIC_UUID), msg);
LOG.info("Showing notification, title: " + title + " message (not sent): " + message);
builder.queue(getQueue());
} catch (IOException e) {
@@ -563,7 +563,7 @@ public class CasioGB6900DeviceSupport extends CasioSupport {
arr[1] = 10;
arr[2] = 1;
System.arraycopy(bInfo, 0, arr, 3, len);
builder.write(getCharacteristic(CasioConstants.MORE_ALERT_FOR_LONG_UUID), arr);
builder.writeLegacy(getCharacteristic(CasioConstants.MORE_ALERT_FOR_LONG_UUID), arr);
builder.queue(getQueue());
} catch (IOException e) {
LOG.warn("sendMusicInfo failed: " + e.getMessage());
@@ -596,7 +596,7 @@ public class CasioGB6900DeviceSupport extends CasioSupport {
BluetoothGattService service = mBtGatt.getService(CasioConstants.IMMEDIATE_ALERT_SERVICE_UUID);
BluetoothGattCharacteristic charact = service.getCharacteristic(CasioConstants.ALERT_LEVEL_CHARACTERISTIC_UUID);
builder.write(charact, value);
builder.writeLegacy(charact, value);
LOG.info("onFindDevice sent");
builder.queue(getQueue());
} catch (IOException e) {
@@ -84,7 +84,7 @@ public class InitOperation extends AbstractBTLEOperation<CasioGB6900DeviceSuppor
try {
TransactionBuilder builder = getSupport().createTransactionBuilder("writeBleInit");
builder.setCallback(this);
builder.write(getCharacteristic(CasioConstants.CASIO_SETTING_FOR_BLE_CHARACTERISTIC_UUID), mBleSettings);
builder.writeLegacy(getCharacteristic(CasioConstants.CASIO_SETTING_FOR_BLE_CHARACTERISTIC_UUID), mBleSettings);
getSupport().performImmediately(builder);
} catch(IOException e) {
LOG.error("Error writing BLE settings: " + e.getMessage());
@@ -118,7 +118,7 @@ public class SetAlarmOperation extends AbstractBTLEOperation<CasioGB6900DeviceSu
try {
TransactionBuilder builder = performInitialized("setAlarm");
builder.write(getCharacteristic(CasioConstants.CASIO_SETTING_FOR_ALM_CHARACTERISTIC_UUID), data);
builder.writeLegacy(getCharacteristic(CasioConstants.CASIO_SETTING_FOR_ALM_CHARACTERISTIC_UUID), data);
builder.queue(getQueue());
} catch(IOException e) {
LOG.error("Error setting alarm: " + e.getMessage());
@@ -402,7 +402,7 @@ public class CasioGBX100DeviceSupport extends Casio2C2DSupport implements Shared
try {
TransactionBuilder builder = performInitialized("showNotification");
builder.write(getCharacteristic(CasioConstants.CASIO_NOTIFICATION_CHARACTERISTIC_UUID), copy);
builder.writeLegacy(getCharacteristic(CasioConstants.CASIO_NOTIFICATION_CHARACTERISTIC_UUID), copy);
LOG.info("Showing notification, title: {} message: {}", title, message);
builder.queue(getQueue());
} catch (IOException e) {
@@ -65,7 +65,7 @@ public class FetchStepCountDataOperation extends AbstractBTLEOperation<CasioGBX
try {
TransactionBuilder builder = performInitialized("requestStepCountDate");
builder.setCallback(this);
builder.write(getCharacteristic(CasioConstants.CASIO_DATA_REQUEST_SP_CHARACTERISTIC_UUID), command);
builder.writeLegacy(getCharacteristic(CasioConstants.CASIO_DATA_REQUEST_SP_CHARACTERISTIC_UUID), command);
builder.queue(getQueue());
} catch(IOException e) {
LOG.error("Error requesting step count data", e);
@@ -78,7 +78,7 @@ public class FetchStepCountDataOperation extends AbstractBTLEOperation<CasioGBX
try {
TransactionBuilder builder = performInitialized("writeStepCountAck");
builder.setCallback(this);
builder.write(getCharacteristic(CasioConstants.CASIO_DATA_REQUEST_SP_CHARACTERISTIC_UUID), command);
builder.writeLegacy(getCharacteristic(CasioConstants.CASIO_DATA_REQUEST_SP_CHARACTERISTIC_UUID), command);
builder.queue(getQueue());
} catch(IOException e) {
LOG.error("Error writing step count ack", e);