mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
ble: improve exception handling and streamline logged information
This commit is contained in:
committed by
José Rebelo
parent
2709446597
commit
b30ede77c7
@@ -17,6 +17,7 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge;
|
||||
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -57,7 +58,14 @@ public abstract class Logging {
|
||||
} else {
|
||||
stopFileLogger();
|
||||
}
|
||||
getLogger().info("Gadgetbridge version: {}-{}", BuildConfig.VERSION_NAME, BuildConfig.GIT_HASH_SHORT);
|
||||
getLogger().info("Gadgetbridge version: {}-{} {} {}", BuildConfig.VERSION_NAME,
|
||||
BuildConfig.GIT_HASH_SHORT, BuildConfig.FLAVOR, BuildConfig.BUILD_TYPE);
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
getLogger().info("Android: SDK_INT={}", Build.VERSION.SDK_INT);
|
||||
} else {
|
||||
getLogger().info("Android: SDK_INT_FULL={} SECURITY_PATCH={}", Build.VERSION.SDK_INT_FULL, Build.VERSION.SECURITY_PATCH);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Log.e("GBApplication", "External files dir not available, cannot log to file", ex);
|
||||
stopFileLogger();
|
||||
|
||||
+4
-5
@@ -16,9 +16,7 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.btle;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
|
||||
public abstract class AbstractTransaction {
|
||||
private final String mName;
|
||||
@@ -33,14 +31,15 @@ public abstract class AbstractTransaction {
|
||||
}
|
||||
|
||||
protected String getCreationTime() {
|
||||
return DateFormat.getTimeInstance(DateFormat.MEDIUM).format(new Date(creationTimestamp));
|
||||
return DateTimeUtils.formatLocalTime(creationTimestamp);
|
||||
}
|
||||
|
||||
public abstract int getActionCount();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(Locale.US, "%s: Transaction task: %s with %d actions", getCreationTime(), getTaskName(), getActionCount());
|
||||
return getCreationTime() + " " + getClass().getSimpleName() + " with "
|
||||
+ getActionCount() + " actions for " + getTaskName();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+22
-5
@@ -25,11 +25,11 @@ import android.util.SparseArray;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class BleNamesResolver {
|
||||
private static HashMap<String, String> mServices = new HashMap<>();
|
||||
private static HashMap<String, String> mCharacteristics = new HashMap<>();
|
||||
private static SparseArray<String> mValueFormats = new SparseArray<>();
|
||||
private static SparseArray<String> mAppearance = new SparseArray<>();
|
||||
private static SparseArray<String> mHeartRateSensorLocation = new SparseArray<>();
|
||||
private static HashMap<String, String> mServices = new HashMap<>(100);
|
||||
private static HashMap<String, String> mCharacteristics = new HashMap<>(600);
|
||||
private static SparseArray<String> mValueFormats = new SparseArray<>(10);
|
||||
private static SparseArray<String> mAppearance = new SparseArray<>(20);
|
||||
private static SparseArray<String> mHeartRateSensorLocation = new SparseArray<>(10);
|
||||
|
||||
static public String resolveServiceName(final String uuid) {
|
||||
String result = mServices.get(uuid);
|
||||
@@ -363,6 +363,17 @@ public class BleNamesResolver {
|
||||
}
|
||||
}
|
||||
|
||||
/// lookup description for numeric {@link BluetoothGatt#requestConnectionPriority} priorities
|
||||
public static String getConnectionPriorityString(int priority){
|
||||
return switch (priority) {
|
||||
case BluetoothGatt.CONNECTION_PRIORITY_BALANCED -> "CONNECTION_PRIORITY_BALANCED";
|
||||
case BluetoothGatt.CONNECTION_PRIORITY_HIGH -> "CONNECTION_PRIORITY_HIGH";
|
||||
case BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER -> "CONNECTION_PRIORITY_LOW_POWER";
|
||||
case BluetoothGatt.CONNECTION_PRIORITY_DCK -> "CONNECTION_PRIORITY_DCK";
|
||||
default -> "priority_" + priority;
|
||||
};
|
||||
}
|
||||
|
||||
static public String getCharacteristicPropertyString(int property) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (BluetoothGattCharacteristic.PROPERTY_BROADCAST == (BluetoothGattCharacteristic.PROPERTY_BROADCAST & property)) {
|
||||
@@ -484,6 +495,8 @@ public class BleNamesResolver {
|
||||
mServices.put("86f65000-f706-58a0-95b2-1fb9261e4dc7", "(Propr: Ultrahuman Request)");
|
||||
mServices.put("86f66000-f706-58a0-95b2-1fb9261e4dc7", "(Propr: Ultrahuman Data)");
|
||||
mServices.put("8d53dc1d-1db7-4cd3-868b-8a527460aa84", "(Propr: SMP - Simple Management Protocol)");
|
||||
mServices.put("6e40fff0-b5a3-f393-e0a9-e50e24dcca9e", "(Propr: NUS - Nordic UART Service)");
|
||||
mServices.put("de5bf728-d711-4e47-af26-65e3012a5dc7", "(Propr: Yawell Serial)");
|
||||
|
||||
// source https://bitbucket.org/bluetooth-SIG/public/src/main/assigned_numbers/uuids/characteristic_uuids.yaml
|
||||
mCharacteristics.put("00002a00-0000-1000-8000-00805f9b34fb", "Device Name");
|
||||
@@ -1015,6 +1028,10 @@ public class BleNamesResolver {
|
||||
mCharacteristics.put("86f65002-f706-58a0-95b2-1fb9261e4dc7", "(Propr: Ultrahuman Response)");
|
||||
mCharacteristics.put("86f66001-f706-58a0-95b2-1fb9261e4dc7", "(Propr: Ultrahuman Data)");
|
||||
mCharacteristics.put("da2e7828-fbce-4e01-ae9e-261174997c48", "(Propr: SMP - Simple Management Protocol)");
|
||||
mCharacteristics.put("6e400002-b5a3-f393-e0a9-e50e24dcca9e", "(Propr: Nordic UART TX)");
|
||||
mCharacteristics.put("6e400003-b5a3-f393-e0a9-e50e24dcca9e", "(Propr: Nordic UART RX)");
|
||||
mCharacteristics.put("de5bf729-d711-4e47-af26-65e3012a5dc7", "(Propr: Yawell Notify)");
|
||||
mCharacteristics.put("de5bf72a-d711-4e47-af26-65e3012a5dc7", "(Propr: Yawell Write)");
|
||||
|
||||
mValueFormats.put(52, "32bit float");
|
||||
mValueFormats.put(50, "16bit float");
|
||||
|
||||
@@ -19,8 +19,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.btle;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
|
||||
/**
|
||||
@@ -67,12 +65,12 @@ public abstract class BtLEAction {
|
||||
}
|
||||
|
||||
protected String getCreationTime() {
|
||||
return DateTimeUtils.formatDateTime(new Date(creationTimestamp));
|
||||
return DateTimeUtils.formatLocalTime(creationTimestamp);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic();
|
||||
String uuid = characteristic == null ? "(null)" : characteristic.getUuid().toString();
|
||||
return getCreationTime() + ": " + getClass().getSimpleName() + " on characteristic: " + uuid;
|
||||
return getCreationTime() + " " + getClass().getSimpleName() + " on characteristic " + uuid;
|
||||
}
|
||||
}
|
||||
|
||||
+114
-38
@@ -53,17 +53,18 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.Logging;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBExceptionHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WriteAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
/**
|
||||
* One queue/thread per connectable device.
|
||||
*/
|
||||
@SuppressLint("MissingPermission") // if we're using this, we have bluetooth permissions
|
||||
public final class BtLEQueue {
|
||||
public final class BtLEQueue implements Thread.UncaughtExceptionHandler {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BtLEQueue.class);
|
||||
private static final byte[] EMPTY = new byte[0];
|
||||
private static final AtomicLong THREAD_COUNTER = new AtomicLong(0L);
|
||||
@@ -86,7 +87,7 @@ public final class BtLEQueue {
|
||||
private CountDownLatch mWaitForServerActionResultLatch;
|
||||
private CountDownLatch mConnectionLatch;
|
||||
private BluetoothGattCharacteristic mWaitCharacteristic;
|
||||
private final InternalGattCallback internalGattCallback;
|
||||
private final NoThrowBluetoothGattCallback<InternalGattCallback> internalGattCallback;
|
||||
private final InternalGattServerCallback internalGattServerCallback;
|
||||
private final AbstractBTLEDeviceSupport mDeviceSupport;
|
||||
private final boolean mImplicitGattCallbackModify;
|
||||
@@ -99,7 +100,7 @@ public final class BtLEQueue {
|
||||
private class DispatchRunnable implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
LOG.debug("Queue Dispatch Thread started.");
|
||||
LOG.debug("{} started", Thread.currentThread().getName());
|
||||
boolean crashed = false;
|
||||
|
||||
while (!mDisposed.get() && !crashed) {
|
||||
@@ -109,7 +110,7 @@ public final class BtLEQueue {
|
||||
if (!isConnected()) {
|
||||
LOG.debug("not connected, waiting for connection...");
|
||||
// TODO: request connection and initialization from the outside and wait until finished
|
||||
internalGattCallback.reset();
|
||||
internalGattCallback.Delegate.reset();
|
||||
|
||||
// wait until the connection succeeds before running the actions
|
||||
// Note that no automatic connection is performed. This has to be triggered
|
||||
@@ -131,7 +132,7 @@ public final class BtLEQueue {
|
||||
break;
|
||||
}
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("About to run server action: {}", action);
|
||||
LOG.debug("execute server: {}", action);
|
||||
}
|
||||
if (action.run(mBluetoothGattServer)) {
|
||||
// check again, maybe due to some condition, action did not need to write, so we can't wait
|
||||
@@ -153,7 +154,7 @@ public final class BtLEQueue {
|
||||
if (qTransaction instanceof final Transaction transaction) {
|
||||
LOG.trace("Changing gatt callback for {}? {}", transaction.getTaskName(), transaction.isModifyGattCallback());
|
||||
if (mImplicitGattCallbackModify || transaction.isModifyGattCallback()) {
|
||||
internalGattCallback.setTransactionGattCallback(transaction.getGattCallback());
|
||||
internalGattCallback.Delegate.setTransactionGattCallback(transaction.getGattCallback());
|
||||
}
|
||||
mAbortTransaction = false;
|
||||
// Run all actions of the transaction until one doesn't succeed
|
||||
@@ -174,12 +175,12 @@ public final class BtLEQueue {
|
||||
mWaitCharacteristic = action.getCharacteristic();
|
||||
mWaitForActionResultLatch = new CountDownLatch(1);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("About to run action: {}", action);
|
||||
LOG.debug("execute: {}", action);
|
||||
}
|
||||
if (action instanceof final GattListenerAction listenerAction) {
|
||||
// this special action overwrites the transaction gatt listener (if any), it must
|
||||
// always be the last action in the transaction
|
||||
internalGattCallback.setTransactionGattCallback(listenerAction.getGattCallback());
|
||||
internalGattCallback.Delegate.setTransactionGattCallback(listenerAction.getGattCallback());
|
||||
}
|
||||
if (action.run(mBluetoothGatt)) {
|
||||
// check again, maybe due to some condition, action did not need to write, so we can't wait
|
||||
@@ -209,7 +210,7 @@ public final class BtLEQueue {
|
||||
mWaitCharacteristic = null;
|
||||
}
|
||||
}
|
||||
LOG.info("Queue Dispatch Thread terminated.");
|
||||
LOG.debug("{} terminated", Thread.currentThread().getName());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -230,9 +231,10 @@ public final class BtLEQueue {
|
||||
mDisposed = new AtomicBoolean(false);
|
||||
mGattMonitor = new Object();
|
||||
mTransactions = new LinkedBlockingQueue<>();
|
||||
internalGattCallback = new InternalGattCallback(deviceSupport);
|
||||
internalGattCallback = new NoThrowBluetoothGattCallback<>(new InternalGattCallback(deviceSupport));
|
||||
internalGattServerCallback = new InternalGattServerCallback(deviceSupport);
|
||||
mDispatchThread = new Thread(new DispatchRunnable(), "BtLEQueue_" + threadIdx + "_out");
|
||||
mDispatchThread.setUncaughtExceptionHandler(this);
|
||||
|
||||
// 3) start the thread
|
||||
mDispatchThread.start();
|
||||
@@ -240,6 +242,7 @@ public final class BtLEQueue {
|
||||
// 4) handler thread ensure serial processing and informative thread name in the log
|
||||
if(GBApplication.isRunningOreoOrLater()){
|
||||
mReceiverThread = new HandlerThread("BtLEQueue_" + threadIdx + "_in");
|
||||
mReceiverThread.setUncaughtExceptionHandler(this);
|
||||
mReceiverThread.start();
|
||||
mReceiverHandler = new Handler(mReceiverThread.getLooper());
|
||||
} else {
|
||||
@@ -248,6 +251,14 @@ public final class BtLEQueue {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
|
||||
LOG.error("exception in {}", t.getName(), e);
|
||||
|
||||
// TODO implement actual exception handling for mDispatchThread and mReceiverThread
|
||||
new GBExceptionHandler(null, true).uncaughtException(t, e);
|
||||
}
|
||||
|
||||
boolean isConnected() {
|
||||
State state = mGbDevice.getState();
|
||||
boolean gatt = (mBluetoothGatt != null);
|
||||
@@ -361,7 +372,7 @@ public final class BtLEQueue {
|
||||
|
||||
private void handleDisconnected(int status) {
|
||||
LOG.debug("handleDisconnected: {}", BleNamesResolver.getStatusString(status));
|
||||
internalGattCallback.reset();
|
||||
internalGattCallback.Delegate.reset();
|
||||
mTransactions.clear();
|
||||
mPauseTransaction = false;
|
||||
mAbortTransaction = true;
|
||||
@@ -464,7 +475,7 @@ public final class BtLEQueue {
|
||||
* @param transaction
|
||||
*/
|
||||
void add(Transaction transaction) {
|
||||
LOG.debug("about to add: {}", transaction);
|
||||
LOG.debug("add: {}", transaction);
|
||||
if (!transaction.isEmpty()) {
|
||||
mTransactions.add(transaction);
|
||||
}
|
||||
@@ -485,7 +496,7 @@ public final class BtLEQueue {
|
||||
* Adds a serverTransaction to the end of the queue
|
||||
*/
|
||||
void add(ServerTransaction transaction) {
|
||||
LOG.debug("about to add server: {}", transaction);
|
||||
LOG.debug("add server: {}", transaction);
|
||||
if(!transaction.isEmpty()) {
|
||||
mTransactions.add(transaction);
|
||||
}
|
||||
@@ -559,7 +570,7 @@ public final class BtLEQueue {
|
||||
@Override
|
||||
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
|
||||
final int bondState = gatt.getDevice().getBondState();
|
||||
LOG.debug("connection state change, newState: {} {} {}",
|
||||
LOG.debug("connection state changed: {} {} {}",
|
||||
BleNamesResolver.getStateString(newState), BleNamesResolver.getStatusString(status),
|
||||
BleNamesResolver.getBondStateString(bondState));
|
||||
|
||||
@@ -583,7 +594,11 @@ public final class BtLEQueue {
|
||||
|
||||
final GattCallback callback = getCallbackToUse();
|
||||
if (callback != null) {
|
||||
callback.onConnectionStateChange(gatt, status, newState);
|
||||
try {
|
||||
callback.onConnectionStateChange(gatt, status, newState);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onConnectionStateChange failed", ex);
|
||||
}
|
||||
}
|
||||
|
||||
switch (newState) {
|
||||
@@ -636,7 +651,11 @@ public final class BtLEQueue {
|
||||
final GattCallback callback = getCallbackToUse();
|
||||
if (callback != null) {
|
||||
// only propagate the successful event
|
||||
callback.onServicesDiscovered(gatt);
|
||||
try {
|
||||
callback.onServicesDiscovered(gatt);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onServicesDiscovered failed", ex);
|
||||
}
|
||||
}
|
||||
final CountDownLatch latch = mConnectionLatch;
|
||||
if (latch != null) {
|
||||
@@ -649,14 +668,18 @@ public final class BtLEQueue {
|
||||
|
||||
@Override
|
||||
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
||||
LOG.debug("characteristic write: {} {}", characteristic.getUuid(), BleNamesResolver.getStatusString(status));
|
||||
LOG.debug("characteristic written: {} {}", characteristic.getUuid(), BleNamesResolver.getStatusString(status));
|
||||
if (!checkCorrectGattInstance(gatt, "characteristic write")) {
|
||||
return;
|
||||
}
|
||||
|
||||
final GattCallback callback = getCallbackToUse();
|
||||
if (callback != null) {
|
||||
callback.onCharacteristicWrite(gatt, characteristic, status);
|
||||
try {
|
||||
callback.onCharacteristicWrite(gatt, characteristic, status);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onCharacteristicWrite failed", ex);
|
||||
}
|
||||
}
|
||||
checkWaitingCharacteristic(characteristic, status);
|
||||
}
|
||||
@@ -671,7 +694,11 @@ public final class BtLEQueue {
|
||||
|
||||
final GattCallback callback = getCallbackToUse();
|
||||
if (callback != null) {
|
||||
callback.onMtuChanged(gatt, mtu, status);
|
||||
try {
|
||||
callback.onMtuChanged(gatt, mtu, status);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onMtuChanged failed", ex);
|
||||
}
|
||||
}
|
||||
|
||||
final CountDownLatch latch = mWaitForActionResultLatch;
|
||||
@@ -693,7 +720,7 @@ public final class BtLEQueue {
|
||||
BluetoothGattCharacteristic characteristic,
|
||||
@NonNull byte[] value, int status) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
String content = Logging.formatBytes(value);
|
||||
String content = GB.hexdump(value);
|
||||
LOG.debug(
|
||||
"characteristic read: {} {} - {}", characteristic.getUuid(),
|
||||
BleNamesResolver.getStatusString(status), content
|
||||
@@ -708,8 +735,8 @@ public final class BtLEQueue {
|
||||
if (callback != null) {
|
||||
try {
|
||||
callback.onCharacteristicRead(gatt, characteristic, value, status);
|
||||
} catch (Throwable ex) {
|
||||
LOG.error("onCharacteristicRead: {}", ex.getMessage(), ex);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onCharacteristicRead failed", ex);
|
||||
}
|
||||
}
|
||||
checkWaitingCharacteristic(characteristic, status);
|
||||
@@ -724,7 +751,7 @@ public final class BtLEQueue {
|
||||
@Override
|
||||
public void onDescriptorRead(@NonNull BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status, @NonNull byte[] value) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
String content = Logging.formatBytes(value);
|
||||
String content = GB.hexdump(value);
|
||||
LOG.debug("descriptor read: {} {} - {}", descriptor.getUuid(),
|
||||
BleNamesResolver.getStatusString(status), content);
|
||||
}
|
||||
@@ -737,7 +764,7 @@ public final class BtLEQueue {
|
||||
if (callback != null) {
|
||||
try {
|
||||
callback.onDescriptorRead(gatt, descriptor, status, value);
|
||||
} catch (Throwable ex) {
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onDescriptorRead failed", ex);
|
||||
}
|
||||
}
|
||||
@@ -746,7 +773,7 @@ public final class BtLEQueue {
|
||||
|
||||
@Override
|
||||
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
||||
LOG.debug("descriptor write: {} {}", descriptor.getUuid(), BleNamesResolver.getStatusString(status));
|
||||
LOG.debug("descriptor written: {} {}", descriptor.getUuid(), BleNamesResolver.getStatusString(status));
|
||||
if (!checkCorrectGattInstance(gatt, "descriptor write")) {
|
||||
return;
|
||||
}
|
||||
@@ -755,7 +782,7 @@ public final class BtLEQueue {
|
||||
if (callback != null) {
|
||||
try {
|
||||
callback.onDescriptorWrite(gatt, descriptor, status);
|
||||
} catch (Throwable ex) {
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onDescriptorWrite failed", ex);
|
||||
}
|
||||
}
|
||||
@@ -774,7 +801,7 @@ public final class BtLEQueue {
|
||||
@NonNull BluetoothGattCharacteristic characteristic,
|
||||
@NonNull byte[] value) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
String content = Logging.formatBytes(value);
|
||||
String content = GB.hexdump(value);
|
||||
LOG.debug("characteristic changed: {} - {}", characteristic.getUuid(), content);
|
||||
}
|
||||
if (!checkCorrectGattInstance(gatt, "characteristic changed")) {
|
||||
@@ -785,7 +812,7 @@ public final class BtLEQueue {
|
||||
if (callback != null) {
|
||||
try {
|
||||
callback.onCharacteristicChanged(gatt, characteristic, value);
|
||||
} catch (Throwable ex) {
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onCharacteristicChanged failed", ex);
|
||||
}
|
||||
} else {
|
||||
@@ -804,7 +831,7 @@ public final class BtLEQueue {
|
||||
if (callback != null) {
|
||||
try {
|
||||
callback.onReadRemoteRssi(gatt, rssi, status);
|
||||
} catch (Throwable ex) {
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onReadRemoteRssi failed", ex);
|
||||
}
|
||||
}
|
||||
@@ -822,7 +849,7 @@ public final class BtLEQueue {
|
||||
if (callback != null) {
|
||||
try {
|
||||
callback.onReliableWriteCompleted(gatt, status);
|
||||
} catch (Throwable ex) {
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onReliableWriteCompleted failed", ex);
|
||||
}
|
||||
}
|
||||
@@ -847,7 +874,7 @@ public final class BtLEQueue {
|
||||
if (callback != null) {
|
||||
try {
|
||||
callback.onPhyRead(gatt, txPhy, rxPhy, status);
|
||||
} catch (Throwable ex) {
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onPhyRead failed", ex);
|
||||
}
|
||||
}
|
||||
@@ -872,7 +899,7 @@ public final class BtLEQueue {
|
||||
if (callback != null) {
|
||||
try {
|
||||
callback.onPhyUpdate(gatt, txPhy, rxPhy, status);
|
||||
} catch (Throwable ex) {
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onPhyUpdate failed", ex);
|
||||
}
|
||||
}
|
||||
@@ -894,7 +921,7 @@ public final class BtLEQueue {
|
||||
if (callback != null) {
|
||||
try {
|
||||
callback.onServiceChanged(gatt);
|
||||
} catch (Throwable ex) {
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onServiceChanged failed", ex);
|
||||
}
|
||||
}
|
||||
@@ -1000,7 +1027,11 @@ public final class BtLEQueue {
|
||||
LOG.debug("characteristic read request: {} characteristic: {}", device.getAddress(), characteristic.getUuid());
|
||||
final GattServerCallback callback = getCallbackToUse();
|
||||
if (callback != null) {
|
||||
callback.onCharacteristicReadRequest(device, requestId, offset, characteristic);
|
||||
try {
|
||||
callback.onCharacteristicReadRequest(device, requestId, offset, characteristic);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onCharacteristicReadRequest failed", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1013,7 +1044,11 @@ public final class BtLEQueue {
|
||||
boolean success = false;
|
||||
final GattServerCallback callback = getCallbackToUse();
|
||||
if (callback != null) {
|
||||
success = callback.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
|
||||
try {
|
||||
success = callback.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onCharacteristicWriteRequest failed", ex);
|
||||
}
|
||||
}
|
||||
if (responseNeeded && mSendWriteRequestResponse) {
|
||||
mBluetoothGattServer.sendResponse(device, requestId, success ? BluetoothGatt.GATT_SUCCESS : BluetoothGatt.GATT_FAILURE, 0, EMPTY);
|
||||
@@ -1028,7 +1063,11 @@ public final class BtLEQueue {
|
||||
LOG.debug("onDescriptorReadRequest: {}", device.getAddress());
|
||||
final GattServerCallback callback = getCallbackToUse();
|
||||
if (callback != null) {
|
||||
callback.onDescriptorReadRequest(device, requestId, offset, descriptor);
|
||||
try {
|
||||
callback.onDescriptorReadRequest(device, requestId, offset, descriptor);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onDescriptorReadRequest failed", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1041,11 +1080,48 @@ public final class BtLEQueue {
|
||||
boolean success = false;
|
||||
final GattServerCallback callback = getCallbackToUse();
|
||||
if (callback != null) {
|
||||
success = callback.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
|
||||
try {
|
||||
success = callback.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onDescriptorWriteRequest failed", ex);
|
||||
}
|
||||
}
|
||||
if (responseNeeded && mSendWriteRequestResponse) {
|
||||
mBluetoothGattServer.sendResponse(device, requestId, success ? BluetoothGatt.GATT_SUCCESS : BluetoothGatt.GATT_FAILURE, 0, EMPTY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceAdded(int status, BluetoothGattService service) {
|
||||
LOG.debug("server.onServiceAdded {} {}", service.getUuid(), service.getInstanceId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
|
||||
LOG.debug("server.onExecuteWrite {} {}", requestId, execute);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationSent(BluetoothDevice device, int status) {
|
||||
LOG.debug("server.onNotificationSent {}",
|
||||
BleNamesResolver.getStatusString(status));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMtuChanged(BluetoothDevice device, int mtu) {
|
||||
LOG.debug("server.onMtuChanged mtu={}", mtu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPhyUpdate(BluetoothDevice device, int txPhy, int rxPhy, int status) {
|
||||
LOG.debug("server.onPhyUpdate tx={} rx={} {}", txPhy, rxPhy,
|
||||
BleNamesResolver.getStatusString(status));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPhyRead(BluetoothDevice device, int txPhy, int rxPhy, int status) {
|
||||
LOG.debug("server.onPhyRead tx={} rx={} {}", txPhy, rxPhy,
|
||||
BleNamesResolver.getStatusString(status));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+242
@@ -0,0 +1,242 @@
|
||||
/* Copyright (C) 2025 Thomas Kuehne
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
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.BluetoothGattCallback;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattDescriptor;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/// A {@link BluetoothGattCallback} that ensures no {@link Exception} can kill the current {@link Thread}.
|
||||
/// All processing and logging must be done in the {@link #Delegate} and not here.
|
||||
public class NoThrowBluetoothGattCallback<T extends BluetoothGattCallback> extends BluetoothGattCallback {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(NoThrowBluetoothGattCallback.class);
|
||||
public final T Delegate;
|
||||
|
||||
public NoThrowBluetoothGattCallback(T delegate) {
|
||||
if (delegate == null) {
|
||||
throw new IllegalArgumentException("delegate is null");
|
||||
}
|
||||
Delegate = delegate;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
@Override
|
||||
public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
|
||||
try {
|
||||
Delegate.onPhyUpdate(gatt, txPhy, rxPhy, status);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onPhyUpdate", ex);
|
||||
} catch (Throwable t) {
|
||||
LOG.error("onPhyUpdate", t);
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
@Override
|
||||
public void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
|
||||
try {
|
||||
Delegate.onPhyRead(gatt, txPhy, rxPhy, status);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onPhyRead", ex);
|
||||
} catch (Throwable t) {
|
||||
LOG.error("onPhyRead", t);
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
|
||||
try {
|
||||
Delegate.onConnectionStateChange(gatt, status, newState);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onConnectionStateChange", ex);
|
||||
} catch (Throwable t) {
|
||||
LOG.error("onConnectionStateChange", t);
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
|
||||
try {
|
||||
Delegate.onServicesDiscovered(gatt, status);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onServicesDiscovered", ex);
|
||||
} catch (Throwable t) {
|
||||
LOG.error("onServicesDiscovered", t);
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
||||
try {
|
||||
Delegate.onCharacteristicRead(gatt, characteristic, status);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onCharacteristicRead-old", ex);
|
||||
} catch (Throwable t) {
|
||||
LOG.error("onCharacteristicRead-old", t);
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
|
||||
@Override
|
||||
public void onCharacteristicRead(@NonNull BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic, @NonNull byte[] value, int status) {
|
||||
try {
|
||||
Delegate.onCharacteristicRead(gatt, characteristic, value, status);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onCharacteristicRead", ex);
|
||||
} catch (Throwable t) {
|
||||
LOG.error("onCharacteristicRead", t);
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
||||
try {
|
||||
Delegate.onCharacteristicWrite(gatt, characteristic, status);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onCharacteristicWrite", ex);
|
||||
} catch (Throwable t) {
|
||||
LOG.error("onCharacteristicWrite", t);
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
||||
try {
|
||||
Delegate.onCharacteristicChanged(gatt, characteristic);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onCharacteristicChanged-old", ex);
|
||||
} catch (Throwable t) {
|
||||
LOG.error("onCharacteristicChanged-old", t);
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
|
||||
@Override
|
||||
public void onCharacteristicChanged(@NonNull BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic, @NonNull byte[] value) {
|
||||
try {
|
||||
Delegate.onCharacteristicChanged(gatt, characteristic, value);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onCharacteristicChanged", ex);
|
||||
} catch (Throwable t) {
|
||||
LOG.error("onCharacteristicChanged", t);
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
||||
try {
|
||||
Delegate.onDescriptorRead(gatt, descriptor, status);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onDescriptorRead-old", ex);
|
||||
} catch (Throwable t) {
|
||||
LOG.error("onDescriptorRead-old", t);
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
|
||||
@Override
|
||||
public void onDescriptorRead(@NonNull BluetoothGatt gatt, @NonNull BluetoothGattDescriptor descriptor, int status, @NonNull byte[] value) {
|
||||
try {
|
||||
Delegate.onDescriptorRead(gatt, descriptor, status, value);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onDescriptorRead", ex);
|
||||
} catch (Throwable t) {
|
||||
LOG.error("onDescriptorRead", t);
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
||||
try {
|
||||
Delegate.onDescriptorWrite(gatt, descriptor, status);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onDescriptorWrite", ex);
|
||||
} catch (Throwable t) {
|
||||
LOG.error("onDescriptorWrite", t);
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
|
||||
try {
|
||||
Delegate.onReliableWriteCompleted(gatt, status);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onReliableWriteCompleted", ex);
|
||||
} catch (Throwable t) {
|
||||
LOG.error("onReliableWriteCompleted", t);
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
|
||||
try {
|
||||
Delegate.onReadRemoteRssi(gatt, rssi, status);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onReadRemoteRssi", ex);
|
||||
} catch (Throwable t) {
|
||||
LOG.error("onReadRemoteRssi", t);
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
|
||||
try {
|
||||
Delegate.onMtuChanged(gatt, mtu, status);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onMtuChanged", ex);
|
||||
} catch (Throwable t) {
|
||||
LOG.error("onMtuChanged", t);
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.S)
|
||||
@Override
|
||||
public void onServiceChanged(@NonNull BluetoothGatt gatt) {
|
||||
try {
|
||||
Delegate.onServiceChanged(gatt);
|
||||
} catch (Exception ex) {
|
||||
LOG.error("onServiceChanged", ex);
|
||||
} catch (Throwable t) {
|
||||
LOG.error("onServiceChanged", t);
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
}
|
||||
-6
@@ -19,7 +19,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.btle;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@@ -51,11 +50,6 @@ public class ServerTransaction extends AbstractTransaction {
|
||||
return mActions.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(Locale.US, "%s: Transaction task: %s with %d actions", getCreationTime(), getTaskName(), mActions.size());
|
||||
}
|
||||
|
||||
public void setCallback(@Nullable GattServerCallback callback) {
|
||||
gattCallback = callback;
|
||||
}
|
||||
|
||||
+1
-4
@@ -28,9 +28,6 @@ import org.slf4j.LoggerFactory;
|
||||
public abstract class AbortTransactionAction extends PlainAction {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AbortTransactionAction.class);
|
||||
|
||||
public AbortTransactionAction() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean run(BluetoothGatt gatt) {
|
||||
if (shouldAbort()) {
|
||||
@@ -44,6 +41,6 @@ public abstract class AbortTransactionAction extends PlainAction {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getCreationTime() + ": " + getClass().getSimpleName() + ": aborting? " + shouldAbort();
|
||||
return getCreationTime() + " " + getClass().getSimpleName() + " abort=" + shouldAbort();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -142,7 +142,7 @@ public class NotifyAction extends BtLEAction {
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic();
|
||||
String uuid = characteristic == null ? "(null)" : characteristic.getUuid().toString();
|
||||
|
||||
return getCreationTime() + ": " + getClass().getSimpleName() + " " + uuid +
|
||||
return getCreationTime() + " " + getClass().getSimpleName() + " " + uuid +
|
||||
(enableFlag ? " enable" : " disable");
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -37,6 +37,6 @@ public abstract class PlainAction extends BtLEAction {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getCreationTime() + ": " + getClass().getSimpleName();
|
||||
return getCreationTime() + " " + getClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -58,6 +58,6 @@ public class ReadAction extends BtLEAction {
|
||||
public String toString() {
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic();
|
||||
String uuid = characteristic == null ? "(null)" : characteristic.getUuid().toString();
|
||||
return getCreationTime() + ": " + getClass().getSimpleName() + " " + uuid;
|
||||
return getCreationTime() + " " + getClass().getSimpleName() + " " + uuid;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -59,6 +59,6 @@ public class ReadPhyAction extends BtLEAction {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getCreationTime() + ": " + getClass().getSimpleName();
|
||||
return getCreationTime() + " " + getClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -19,6 +19,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BleNamesResolver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
|
||||
|
||||
/// Calls {@link BluetoothGatt#requestConnectionPriority(int)}.
|
||||
@@ -43,6 +44,7 @@ public class RequestConnectionPriorityAction extends BtLEAction {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getCreationTime() + ": " + getClass().getSimpleName() + " priority=" + priority;
|
||||
return getCreationTime() + " " + getClass().getSimpleName() + " " +
|
||||
BleNamesResolver.getConnectionPriorityString(priority);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -46,6 +46,6 @@ public class RequestMtuAction extends BtLEAction {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getCreationTime() + ": " + getClass().getSimpleName() + " mtu=" + mtu;
|
||||
return getCreationTime() + " " + getClass().getSimpleName() + " mtu=" + mtu;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -48,6 +48,6 @@ public class SetDeviceBusyAction extends PlainAction {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getCreationTime() + ": " + getClass().getName() + ": " + busyTask;
|
||||
return getCreationTime() + " " + getClass().getName() + " " + busyTask;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ public class SetPreferredPhyAction extends BtLEAction {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getCreationTime() + ": " + getClass().getSimpleName() + " tx=" + mTxPhy
|
||||
return getCreationTime() + " " + getClass().getSimpleName() + " tx=" + mTxPhy
|
||||
+ " rx=" + mRxPhy + " opt=" + mPhyOptions;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -64,6 +64,6 @@ public class SetProgressAction extends PlainAction {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getCreationTime() + ": " + getClass().getSimpleName() + ": " + text + "; " + percentage + "%";
|
||||
return getCreationTime() + " " + getClass().getSimpleName() + ": " + text + "; " + percentage + "%";
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -43,6 +43,6 @@ public class WaitAction extends PlainAction {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getCreationTime() + ": " + getClass().getSimpleName() + " " + mMillis + " ms";
|
||||
return getCreationTime() + " " + getClass().getSimpleName() + " " + mMillis + " ms";
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -30,6 +30,7 @@ import nodomain.freeyourgadget.gadgetbridge.Logging;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BleNamesResolver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCallback;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
/**
|
||||
* Invokes a write operation on a given {@link BluetoothGattCharacteristic}.
|
||||
@@ -111,7 +112,7 @@ public class WriteAction extends BtLEAction {
|
||||
public String toString() {
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic();
|
||||
String uuid = characteristic == null ? "(null)" : characteristic.getUuid().toString();
|
||||
return getCreationTime() + ": " + getClass().getSimpleName() + " " + uuid + " - "
|
||||
+ Logging.formatBytes(getValue());
|
||||
return getCreationTime() + " " + getClass().getSimpleName() + " " + uuid + " - "
|
||||
+ GB.hexdump(getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,4 +327,12 @@ public class DateTimeUtils {
|
||||
String fromFormattedDate = new SimpleDateFormat("E, MMM dd").format(from);
|
||||
return fromFormattedDate + " - " + toFormattedDate;
|
||||
}
|
||||
|
||||
private static SimpleDateFormat mFormatLocalTime;
|
||||
/// format UTC millisecond epoch as local time (e.g. 23:59:59)
|
||||
public static String formatLocalTime(long epochMilli) {
|
||||
Date date = new Date(epochMilli);
|
||||
SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss", Locale.ROOT);
|
||||
return format.format(date);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user