From 54d103a9a883801e920f31b269731bd5a285c806 Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Mon, 21 Mar 2022 16:04:38 +0100 Subject: [PATCH 01/10] Unused methods warning suppressed --- .../no/nordicsemi/android/ble/ktx/BluetoothGattServiceExt.kt | 2 ++ .../main/java/no/nordicsemi/android/ble/ktx/RequestSuspend.kt | 2 ++ .../no/nordicsemi/android/ble/ktx/ValueChangedCallbackExt.kt | 2 ++ 3 files changed, 6 insertions(+) diff --git a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/BluetoothGattServiceExt.kt b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/BluetoothGattServiceExt.kt index 1cc82b84..1ecd6ad6 100644 --- a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/BluetoothGattServiceExt.kt +++ b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/BluetoothGattServiceExt.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package no.nordicsemi.android.ble.ktx import android.bluetooth.BluetoothGattCharacteristic diff --git a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/RequestSuspend.kt b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/RequestSuspend.kt index 82c9b72c..0f86b424 100644 --- a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/RequestSuspend.kt +++ b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/RequestSuspend.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package no.nordicsemi.android.ble.ktx import android.bluetooth.BluetoothDevice diff --git a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ValueChangedCallbackExt.kt b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ValueChangedCallbackExt.kt index 7b7b3812..5795a546 100644 --- a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ValueChangedCallbackExt.kt +++ b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ValueChangedCallbackExt.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package no.nordicsemi.android.ble.ktx import kotlinx.coroutines.ExperimentalCoroutinesApi From 371acbb2eb039b4981576be135a3e8b3fd736599 Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Mon, 21 Mar 2022 16:16:43 +0100 Subject: [PATCH 02/10] Allowing setting Requests' handler to null to invoke callbacks immediately --- .../android/ble/ConditionalWaitRequest.java | 2 +- .../nordicsemi/android/ble/ConnectRequest.java | 3 ++- .../android/ble/ConnectionPriorityRequest.java | 3 ++- .../android/ble/DisconnectRequest.java | 3 ++- .../no/nordicsemi/android/ble/MtuRequest.java | 3 ++- .../no/nordicsemi/android/ble/PhyRequest.java | 3 ++- .../no/nordicsemi/android/ble/ReadRequest.java | 2 +- .../nordicsemi/android/ble/ReadRssiRequest.java | 3 ++- .../android/ble/ReliableWriteRequest.java | 3 ++- .../java/no/nordicsemi/android/ble/Request.java | 16 +++++++++++----- .../no/nordicsemi/android/ble/RequestQueue.java | 2 +- .../nordicsemi/android/ble/SetValueRequest.java | 2 +- .../no/nordicsemi/android/ble/SleepRequest.java | 3 ++- .../android/ble/TimeoutableRequest.java | 2 +- .../android/ble/ValueChangedCallback.java | 11 +++++++---- .../android/ble/WaitForReadRequest.java | 2 +- .../android/ble/WaitForValueChangedRequest.java | 2 +- .../no/nordicsemi/android/ble/WriteRequest.java | 2 +- 18 files changed, 42 insertions(+), 25 deletions(-) diff --git a/ble/src/main/java/no/nordicsemi/android/ble/ConditionalWaitRequest.java b/ble/src/main/java/no/nordicsemi/android/ble/ConditionalWaitRequest.java index b7a56cc6..d0bfe0f4 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/ConditionalWaitRequest.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/ConditionalWaitRequest.java @@ -44,7 +44,7 @@ ConditionalWaitRequest setRequestHandler(@NonNull final RequestHandler reques @NonNull @Override - public ConditionalWaitRequest setHandler(@NonNull final Handler handler) { + public ConditionalWaitRequest setHandler(@Nullable final Handler handler) { super.setHandler(handler); return this; } diff --git a/ble/src/main/java/no/nordicsemi/android/ble/ConnectRequest.java b/ble/src/main/java/no/nordicsemi/android/ble/ConnectRequest.java index c758f73a..0bcd7585 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/ConnectRequest.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/ConnectRequest.java @@ -31,6 +31,7 @@ import androidx.annotation.IntRange; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import no.nordicsemi.android.ble.annotation.PhyMask; import no.nordicsemi.android.ble.callback.AfterCallback; import no.nordicsemi.android.ble.callback.BeforeCallback; @@ -74,7 +75,7 @@ ConnectRequest setRequestHandler(@NonNull final RequestHandler requestHandler) { @NonNull @Override - public ConnectRequest setHandler(@NonNull final Handler handler) { + public ConnectRequest setHandler(@Nullable final Handler handler) { super.setHandler(handler); return this; } diff --git a/ble/src/main/java/no/nordicsemi/android/ble/ConnectionPriorityRequest.java b/ble/src/main/java/no/nordicsemi/android/ble/ConnectionPriorityRequest.java index 9de377ce..51f05fc8 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/ConnectionPriorityRequest.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/ConnectionPriorityRequest.java @@ -28,6 +28,7 @@ import androidx.annotation.IntRange; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import no.nordicsemi.android.ble.annotation.ConnectionPriority; import no.nordicsemi.android.ble.callback.AfterCallback; @@ -97,7 +98,7 @@ ConnectionPriorityRequest setRequestHandler(@NonNull final RequestHandler reques @NonNull @Override - public ConnectionPriorityRequest setHandler(@NonNull final Handler handler) { + public ConnectionPriorityRequest setHandler(@Nullable final Handler handler) { super.setHandler(handler); return this; } diff --git a/ble/src/main/java/no/nordicsemi/android/ble/DisconnectRequest.java b/ble/src/main/java/no/nordicsemi/android/ble/DisconnectRequest.java index 2575cecf..9fc22b16 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/DisconnectRequest.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/DisconnectRequest.java @@ -26,6 +26,7 @@ import androidx.annotation.IntRange; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import no.nordicsemi.android.ble.callback.AfterCallback; import no.nordicsemi.android.ble.callback.BeforeCallback; import no.nordicsemi.android.ble.callback.FailCallback; @@ -47,7 +48,7 @@ DisconnectRequest setRequestHandler(@NonNull final RequestHandler requestHandler @NonNull @Override - public DisconnectRequest setHandler(@NonNull final Handler handler) { + public DisconnectRequest setHandler(@Nullable final Handler handler) { super.setHandler(handler); return this; } diff --git a/ble/src/main/java/no/nordicsemi/android/ble/MtuRequest.java b/ble/src/main/java/no/nordicsemi/android/ble/MtuRequest.java index 203fffa6..8014a9a6 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/MtuRequest.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/MtuRequest.java @@ -27,6 +27,7 @@ import androidx.annotation.IntRange; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import no.nordicsemi.android.ble.callback.AfterCallback; import no.nordicsemi.android.ble.callback.BeforeCallback; import no.nordicsemi.android.ble.callback.FailCallback; @@ -55,7 +56,7 @@ MtuRequest setRequestHandler(@NonNull final RequestHandler requestHandler) { @NonNull @Override - public MtuRequest setHandler(@NonNull final Handler handler) { + public MtuRequest setHandler(@Nullable final Handler handler) { super.setHandler(handler); return this; } diff --git a/ble/src/main/java/no/nordicsemi/android/ble/PhyRequest.java b/ble/src/main/java/no/nordicsemi/android/ble/PhyRequest.java index c955ec49..66142d49 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/PhyRequest.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/PhyRequest.java @@ -26,6 +26,7 @@ import android.os.Handler; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import no.nordicsemi.android.ble.annotation.PhyMask; import no.nordicsemi.android.ble.annotation.PhyOption; import no.nordicsemi.android.ble.annotation.PhyValue; @@ -106,7 +107,7 @@ PhyRequest setRequestHandler(@NonNull final RequestHandler requestHandler) { @NonNull @Override - public PhyRequest setHandler(@NonNull final Handler handler) { + public PhyRequest setHandler(@Nullable final Handler handler) { super.setHandler(handler); return this; } diff --git a/ble/src/main/java/no/nordicsemi/android/ble/ReadRequest.java b/ble/src/main/java/no/nordicsemi/android/ble/ReadRequest.java index e20263da..1c26df1a 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/ReadRequest.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/ReadRequest.java @@ -80,7 +80,7 @@ ReadRequest setRequestHandler(@NonNull final RequestHandler requestHandler) { @NonNull @Override - public ReadRequest setHandler(@NonNull final Handler handler) { + public ReadRequest setHandler(@Nullable final Handler handler) { super.setHandler(handler); return this; } diff --git a/ble/src/main/java/no/nordicsemi/android/ble/ReadRssiRequest.java b/ble/src/main/java/no/nordicsemi/android/ble/ReadRssiRequest.java index 223e8099..1fdbe11d 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/ReadRssiRequest.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/ReadRssiRequest.java @@ -27,6 +27,7 @@ import androidx.annotation.IntRange; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import no.nordicsemi.android.ble.callback.AfterCallback; import no.nordicsemi.android.ble.callback.BeforeCallback; import no.nordicsemi.android.ble.callback.FailCallback; @@ -49,7 +50,7 @@ ReadRssiRequest setRequestHandler(@NonNull final RequestHandler requestHandler) @NonNull @Override - public ReadRssiRequest setHandler(@NonNull final Handler handler) { + public ReadRssiRequest setHandler(@Nullable final Handler handler) { super.setHandler(handler); return this; } diff --git a/ble/src/main/java/no/nordicsemi/android/ble/ReliableWriteRequest.java b/ble/src/main/java/no/nordicsemi/android/ble/ReliableWriteRequest.java index 6eb81407..fe55b325 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/ReliableWriteRequest.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/ReliableWriteRequest.java @@ -25,6 +25,7 @@ import android.os.Handler; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import no.nordicsemi.android.ble.callback.AfterCallback; import no.nordicsemi.android.ble.callback.BeforeCallback; import no.nordicsemi.android.ble.callback.FailCallback; @@ -46,7 +47,7 @@ ReliableWriteRequest setRequestHandler(@NonNull final RequestHandler requestHand @NonNull @Override - public ReliableWriteRequest setHandler(@NonNull final Handler handler) { + public ReliableWriteRequest setHandler(@Nullable final Handler handler) { super.setHandler(handler); return this; } diff --git a/ble/src/main/java/no/nordicsemi/android/ble/Request.java b/ble/src/main/java/no/nordicsemi/android/ble/Request.java index 2f5221f6..080596f2 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/Request.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/Request.java @@ -139,8 +139,8 @@ enum Type { /** * Sets the {@link BleManager} instance. - * @param requestHandler the requestHandler in which the request will be executed. * + * @param requestHandler the requestHandler in which the request will be executed. */ @NonNull Request setRequestHandler(@NonNull final RequestHandler requestHandler) { @@ -155,25 +155,31 @@ Request setRequestHandler(@NonNull final RequestHandler requestHandler) { * Sets the handler that will be used to invoke callbacks. By default, the handler set in * {@link BleManager} will be used. * + * If set to null, the callbacks will be invoked immediately on the BLE looper. + * * @param handler The handler to invoke callbacks for this request. * @return The request. + * @apiNote Since version 2.4.0 this method accepts null as parameter. */ @NonNull - public Request setHandler(@NonNull final Handler handler) { + public Request setHandler(@Nullable final Handler handler) { this.handler = new CallbackHandler() { @Override public void post(@NonNull final Runnable r) { - handler.post(r); + if (handler != null) handler.post(r); + else r.run(); } @Override public void postDelayed(@NonNull final Runnable r, final long delayMillis) { - handler.postDelayed(r, delayMillis); + if (handler != null) handler.postDelayed(r, delayMillis); + else requestHandler.postDelayed(r, delayMillis); } @Override public void removeCallbacks(@NonNull final Runnable r) { - handler.removeCallbacks(r); + if (handler != null) handler.removeCallbacks(r); + else requestHandler.removeCallbacks(r); } }; return this; diff --git a/ble/src/main/java/no/nordicsemi/android/ble/RequestQueue.java b/ble/src/main/java/no/nordicsemi/android/ble/RequestQueue.java index 2bbb3e11..2dff4be7 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/RequestQueue.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/RequestQueue.java @@ -64,7 +64,7 @@ RequestQueue setRequestHandler(@NonNull final RequestHandler requestHandler) { @NonNull @Override - public RequestQueue setHandler(@NonNull final Handler handler) { + public RequestQueue setHandler(@Nullable final Handler handler) { super.setHandler(handler); return this; } diff --git a/ble/src/main/java/no/nordicsemi/android/ble/SetValueRequest.java b/ble/src/main/java/no/nordicsemi/android/ble/SetValueRequest.java index aa63efd6..2b1e4163 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/SetValueRequest.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/SetValueRequest.java @@ -41,7 +41,7 @@ SetValueRequest setRequestHandler(@NonNull final RequestHandler requestHandler) @NonNull @Override - public SetValueRequest setHandler(@NonNull final Handler handler) { + public SetValueRequest setHandler(@Nullable final Handler handler) { super.setHandler(handler); return this; } diff --git a/ble/src/main/java/no/nordicsemi/android/ble/SleepRequest.java b/ble/src/main/java/no/nordicsemi/android/ble/SleepRequest.java index 71f757ca..f9e8db76 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/SleepRequest.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/SleepRequest.java @@ -26,6 +26,7 @@ import androidx.annotation.IntRange; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import no.nordicsemi.android.ble.callback.AfterCallback; import no.nordicsemi.android.ble.callback.BeforeCallback; import no.nordicsemi.android.ble.callback.FailCallback; @@ -49,7 +50,7 @@ SleepRequest setRequestHandler(@NonNull final RequestHandler requestHandler) { @NonNull @Override - public SleepRequest setHandler(@NonNull final Handler handler) { + public SleepRequest setHandler(@Nullable final Handler handler) { super.setHandler(handler); return this; } diff --git a/ble/src/main/java/no/nordicsemi/android/ble/TimeoutableRequest.java b/ble/src/main/java/no/nordicsemi/android/ble/TimeoutableRequest.java index 3a1e5557..c99f8e5c 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/TimeoutableRequest.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/TimeoutableRequest.java @@ -41,7 +41,7 @@ TimeoutableRequest setRequestHandler(@NonNull final RequestHandler requestHandle @NonNull @Override - public TimeoutableRequest setHandler(@NonNull final Handler handler) { + public TimeoutableRequest setHandler(@Nullable final Handler handler) { super.setHandler(handler); return this; } diff --git a/ble/src/main/java/no/nordicsemi/android/ble/ValueChangedCallback.java b/ble/src/main/java/no/nordicsemi/android/ble/ValueChangedCallback.java index 27b954a8..b02dc42f 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/ValueChangedCallback.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/ValueChangedCallback.java @@ -51,21 +51,24 @@ public class ValueChangedCallback { } @NonNull - public ValueChangedCallback setHandler(@NonNull final Handler handler) { + public ValueChangedCallback setHandler(@Nullable final Handler handler) { this.handler = new CallbackHandler() { @Override public void post(@NonNull final Runnable r) { - handler.post(r); + if (handler != null) + handler.post(r); + else + r.run(); } @Override public void postDelayed(@NonNull final Runnable r, final long delayMillis) { - handler.postDelayed(r, delayMillis); + // not used } @Override public void removeCallbacks(@NonNull final Runnable r) { - handler.removeCallbacks(r); + // not used } }; return this; diff --git a/ble/src/main/java/no/nordicsemi/android/ble/WaitForReadRequest.java b/ble/src/main/java/no/nordicsemi/android/ble/WaitForReadRequest.java index fa8a307f..0b43c5d6 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/WaitForReadRequest.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/WaitForReadRequest.java @@ -74,7 +74,7 @@ WaitForReadRequest setRequestHandler(@NonNull final RequestHandler requestHandle @NonNull @Override - public WaitForReadRequest setHandler(@NonNull final Handler handler) { + public WaitForReadRequest setHandler(@Nullable final Handler handler) { super.setHandler(handler); return this; } diff --git a/ble/src/main/java/no/nordicsemi/android/ble/WaitForValueChangedRequest.java b/ble/src/main/java/no/nordicsemi/android/ble/WaitForValueChangedRequest.java index 4341e06b..9a20e7b0 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/WaitForValueChangedRequest.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/WaitForValueChangedRequest.java @@ -80,7 +80,7 @@ WaitForValueChangedRequest setRequestHandler(@NonNull final RequestHandler reque @NonNull @Override - public WaitForValueChangedRequest setHandler(@NonNull final Handler handler) { + public WaitForValueChangedRequest setHandler(@Nullable final Handler handler) { super.setHandler(handler); return this; } diff --git a/ble/src/main/java/no/nordicsemi/android/ble/WriteRequest.java b/ble/src/main/java/no/nordicsemi/android/ble/WriteRequest.java index a9fb20b1..1c6f60cb 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/WriteRequest.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/WriteRequest.java @@ -105,7 +105,7 @@ WriteRequest setRequestHandler(@NonNull final RequestHandler requestHandler) { @NonNull @Override - public WriteRequest setHandler(@NonNull final Handler handler) { + public WriteRequest setHandler(@Nullable final Handler handler) { super.setHandler(handler); return this; } From b8c82d9e6a3e4d26f560c05fca8624df26501b60 Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Mon, 21 Mar 2022 16:32:19 +0100 Subject: [PATCH 03/10] Catching exceptions in user callbacks --- .../no/nordicsemi/android/ble/MtuRequest.java | 10 +++- .../no/nordicsemi/android/ble/PhyRequest.java | 16 +++++- .../nordicsemi/android/ble/ReadRequest.java | 28 ++++++++-- .../android/ble/ReadRssiRequest.java | 10 +++- .../no/nordicsemi/android/ble/Request.java | 56 +++++++++++++++---- .../android/ble/WaitForReadRequest.java | 19 +++++-- .../ble/WaitForValueChangedRequest.java | 26 +++++++-- .../nordicsemi/android/ble/WriteRequest.java | 19 +++++-- 8 files changed, 148 insertions(+), 36 deletions(-) diff --git a/ble/src/main/java/no/nordicsemi/android/ble/MtuRequest.java b/ble/src/main/java/no/nordicsemi/android/ble/MtuRequest.java index 8014a9a6..f705792e 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/MtuRequest.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/MtuRequest.java @@ -24,6 +24,7 @@ import android.bluetooth.BluetoothDevice; import android.os.Handler; +import android.util.Log; import androidx.annotation.IntRange; import androidx.annotation.NonNull; @@ -106,8 +107,13 @@ public MtuRequest with(@NonNull final MtuCallback callback) { void notifyMtuChanged(@NonNull final BluetoothDevice device, @IntRange(from = 23, to = 517) final int mtu) { handler.post(() -> { - if (valueCallback != null) - valueCallback.onMtuChanged(device, mtu); + if (valueCallback != null) { + try { + valueCallback.onMtuChanged(device, mtu); + } catch (final Throwable t) { + Log.e(TAG, "Exception in Value callback", t); + } + } }); } diff --git a/ble/src/main/java/no/nordicsemi/android/ble/PhyRequest.java b/ble/src/main/java/no/nordicsemi/android/ble/PhyRequest.java index 66142d49..1cd732c6 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/PhyRequest.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/PhyRequest.java @@ -24,6 +24,7 @@ import android.bluetooth.BluetoothDevice; import android.os.Handler; +import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -157,15 +158,24 @@ public PhyRequest with(@NonNull final PhyCallback callback) { void notifyPhyChanged(@NonNull final BluetoothDevice device, @PhyValue final int txPhy, @PhyValue final int rxPhy) { handler.post(() -> { - if (valueCallback != null) - valueCallback.onPhyChanged(device, txPhy, rxPhy); + if (valueCallback != null) { + try { + valueCallback.onPhyChanged(device, txPhy, rxPhy); + } catch (final Throwable t) { + Log.e(TAG, "Exception in Value callback", t); + } + } }); } void notifyLegacyPhy(@NonNull final BluetoothDevice device) { handler.post(() -> { if (valueCallback != null) - valueCallback.onPhyChanged(device, PhyCallback.PHY_LE_1M, PhyCallback.PHY_LE_1M); + try { + valueCallback.onPhyChanged(device, PhyCallback.PHY_LE_1M, PhyCallback.PHY_LE_1M); + } catch (final Throwable t) { + Log.e(TAG, "Exception in Value callback", t); + } }); } diff --git a/ble/src/main/java/no/nordicsemi/android/ble/ReadRequest.java b/ble/src/main/java/no/nordicsemi/android/ble/ReadRequest.java index 1c26df1a..8193e425 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/ReadRequest.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/ReadRequest.java @@ -27,6 +27,7 @@ import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.os.Handler; +import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -222,7 +223,7 @@ public E awaitValid(@NonNull final Class resp } /** - * Same as {@link #await(Object)}, but if the response class extends + * Same as {@link #await(DataReceivedCallback)}, but if the response class extends * {@link ProfileReadResponse} and the received response is not valid * ({@link ProfileReadResponse#isValid()} returns false), this method will * throw an exception. @@ -272,11 +273,22 @@ void notifyValueChanged(@NonNull final BluetoothDevice device, @Nullable final b if (dataMerger == null) { complete = true; final Data data = new Data(value); - handler.post(() -> valueCallback.onDataReceived(device, data)); + handler.post(() -> { + try { + valueCallback.onDataReceived(device, data); + } catch (final Throwable t) { + Log.e(TAG, "Exception in Value callback", t); + } + }); } else { handler.post(() -> { - if (progressCallback != null) - progressCallback.onPacketReceived(device, value, count); + if (progressCallback != null) { + try { + progressCallback.onPacketReceived(device, value, count); + } catch (final Throwable t) { + Log.e(TAG, "Exception in Progress callback", t); + } + } }); if (buffer == null) buffer = new DataStream(); @@ -285,7 +297,13 @@ void notifyValueChanged(@NonNull final BluetoothDevice device, @Nullable final b if (packetFilter == null || packetFilter.filter(merged)) { complete = true; final Data data = new Data(merged); - handler.post(() -> valueCallback.onDataReceived(device, data)); + handler.post(() -> { + try { + valueCallback.onDataReceived(device, data); + } catch (final Throwable t) { + Log.e(TAG, "Exception in Value callback", t); + } + }); } buffer = null; count = 0; diff --git a/ble/src/main/java/no/nordicsemi/android/ble/ReadRssiRequest.java b/ble/src/main/java/no/nordicsemi/android/ble/ReadRssiRequest.java index 1fdbe11d..6d975f9f 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/ReadRssiRequest.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/ReadRssiRequest.java @@ -24,6 +24,7 @@ import android.bluetooth.BluetoothDevice; import android.os.Handler; +import android.util.Log; import androidx.annotation.IntRange; import androidx.annotation.NonNull; @@ -100,8 +101,13 @@ public ReadRssiRequest with(@NonNull final RssiCallback callback) { void notifyRssiRead(@NonNull final BluetoothDevice device, @IntRange(from = -128, to = 20) final int rssi) { handler.post(() -> { - if (valueCallback != null) - valueCallback.onRssiRead(device, rssi); + if (valueCallback != null) { + try { + valueCallback.onRssiRead(device, rssi); + } catch (final Throwable t) { + Log.e(TAG, "Exception in Value callback", t); + } + } }); } } diff --git a/ble/src/main/java/no/nordicsemi/android/ble/Request.java b/ble/src/main/java/no/nordicsemi/android/ble/Request.java index 080596f2..1f308cc3 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/Request.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/Request.java @@ -30,6 +30,7 @@ import android.os.ConditionVariable; import android.os.Handler; import android.os.Looper; +import android.util.Log; import androidx.annotation.IntRange; import androidx.annotation.NonNull; @@ -54,6 +55,7 @@ */ @SuppressWarnings({"unused", "WeakerAccess", "deprecation", "DeprecatedIsStillUsed"}) public abstract class Request { + protected static final String TAG = Request.class.getSimpleName(); enum Type { SET, @@ -1214,8 +1216,13 @@ void notifyStarted(@NonNull final BluetoothDevice device) { if (internalBeforeCallback != null) internalBeforeCallback.onRequestStarted(device); handler.post(() -> { - if (beforeCallback != null) - beforeCallback.onRequestStarted(device); + if (beforeCallback != null) { + try { + beforeCallback.onRequestStarted(device); + } catch (final Throwable t) { + Log.e(TAG, "Exception in Before callback", t); + } + } }); } } @@ -1227,10 +1234,20 @@ boolean notifySuccess(@NonNull final BluetoothDevice device) { if (internalSuccessCallback != null) internalSuccessCallback.onRequestCompleted(device); handler.post(() -> { - if (successCallback != null) - successCallback.onRequestCompleted(device); - if (afterCallback != null) - afterCallback.onRequestFinished(device); + if (successCallback != null) { + try { + successCallback.onRequestCompleted(device); + } catch (final Throwable t) { + Log.e(TAG, "Exception in Success callback", t); + } + } + if (afterCallback != null) { + try { + afterCallback.onRequestFinished(device); + } catch (final Throwable t) { + Log.e(TAG, "Exception in After callback", t); + } + } }); return true; } @@ -1244,10 +1261,20 @@ void notifyFail(@NonNull final BluetoothDevice device, final int status) { if (internalFailCallback != null) internalFailCallback.onRequestFailed(device, status); handler.post(() -> { - if (failCallback != null) - failCallback.onRequestFailed(device, status); - if (afterCallback != null) - afterCallback.onRequestFinished(device); + if (failCallback != null) { + try { + failCallback.onRequestFailed(device, status); + } catch (final Throwable t) { + Log.e(TAG, "Exception in Fail callback", t); + } + } + if (afterCallback != null) { + try { + afterCallback.onRequestFinished(device); + } catch (final Throwable t) { + Log.e(TAG, "Exception in After callback", t); + } + } }); } } @@ -1257,8 +1284,13 @@ void notifyInvalidRequest() { finished = true; handler.post(() -> { - if (invalidRequestCallback != null) - invalidRequestCallback.onInvalidRequest(); + if (invalidRequestCallback != null) { + try { + invalidRequestCallback.onInvalidRequest(); + } catch (final Throwable t) { + Log.e(TAG, "Exception in Invalid Request callback", t); + } + } }); } } diff --git a/ble/src/main/java/no/nordicsemi/android/ble/WaitForReadRequest.java b/ble/src/main/java/no/nordicsemi/android/ble/WaitForReadRequest.java index 0b43c5d6..f4021ae1 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/WaitForReadRequest.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/WaitForReadRequest.java @@ -4,6 +4,7 @@ import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.os.Handler; +import android.util.Log; import androidx.annotation.IntRange; import androidx.annotation.NonNull; @@ -234,8 +235,13 @@ byte[] getData(@IntRange(from = 23, to = 517) final int mtu) { */ void notifyPacketRead(@NonNull final BluetoothDevice device, @Nullable final byte[] data) { handler.post(() -> { - if (progressCallback != null) - progressCallback.onPacketSent(device, data, count); + if (progressCallback != null) { + try { + progressCallback.onPacketSent(device, data, count); + } catch (final Throwable t) { + Log.e(TAG, "Exception in Progress callback", t); + } + } }); count++; } @@ -243,8 +249,13 @@ void notifyPacketRead(@NonNull final BluetoothDevice device, @Nullable final byt @Override boolean notifySuccess(@NonNull final BluetoothDevice device) { handler.post(() -> { - if (valueCallback != null) - valueCallback.onDataSent(device, new Data(data)); + if (valueCallback != null) { + try { + valueCallback.onDataSent(device, new Data(data)); + } catch (final Throwable t) { + Log.e(TAG, "Exception in Value callback", t); + } + } }); return super.notifySuccess(device); } diff --git a/ble/src/main/java/no/nordicsemi/android/ble/WaitForValueChangedRequest.java b/ble/src/main/java/no/nordicsemi/android/ble/WaitForValueChangedRequest.java index 9a20e7b0..1c681e18 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/WaitForValueChangedRequest.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/WaitForValueChangedRequest.java @@ -26,6 +26,7 @@ import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.os.Handler; +import android.util.Log; import androidx.annotation.IntRange; import androidx.annotation.NonNull; @@ -357,12 +358,23 @@ void notifyValueChanged(final BluetoothDevice device, final byte[] value) { if (dataMerger == null && (packetFilter == null || packetFilter.filter(value))) { complete = true; final Data data = new Data(value); - handler.post(() -> valueCallback.onDataReceived(device, data)); + handler.post(() -> { + try { + valueCallback.onDataReceived(device, data); + } catch (final Throwable t) { + Log.e(TAG, "Exception in Value callback", t); + } + }); } else { final int c = count; handler.post(() -> { - if (progressCallback != null) - progressCallback.onPacketReceived(device, value, c); + if (progressCallback != null) { + try { + progressCallback.onPacketReceived(device, value, c); + } catch (final Throwable t) { + Log.e(TAG, "Exception in Progress callback", t); + } + } }); if (buffer == null) buffer = new DataStream(); @@ -371,7 +383,13 @@ void notifyValueChanged(final BluetoothDevice device, final byte[] value) { if (packetFilter == null || packetFilter.filter(merged)) { complete = true; final Data data = new Data(merged); - handler.post(() -> valueCallback.onDataReceived(device, data)); + handler.post(() -> { + try { + valueCallback.onDataReceived(device, data); + } catch (final Throwable t) { + Log.e(TAG, "Exception in Value callback", t); + } + }); } buffer = null; count = 0; diff --git a/ble/src/main/java/no/nordicsemi/android/ble/WriteRequest.java b/ble/src/main/java/no/nordicsemi/android/ble/WriteRequest.java index 1c6f60cb..c96031e4 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/WriteRequest.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/WriteRequest.java @@ -27,6 +27,7 @@ import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.os.Handler; +import android.util.Log; import java.util.Arrays; @@ -269,14 +270,24 @@ byte[] getData(@IntRange(from = 23, to = 517) final int mtu) { */ boolean notifyPacketSent(@NonNull final BluetoothDevice device, @Nullable final byte[] data) { handler.post(() -> { - if (progressCallback != null) - progressCallback.onPacketSent(device, data, count); + if (progressCallback != null) { + try { + progressCallback.onPacketSent(device, data, count); + } catch (final Throwable t) { + Log.e(TAG, "Exception in Progress callback", t); + } + } }); count++; if (complete) { handler.post(() -> { - if (valueCallback != null) - valueCallback.onDataSent(device, new Data(WriteRequest.this.data)); + if (valueCallback != null) { + try { + valueCallback.onDataSent(device, new Data(WriteRequest.this.data)); + } catch (final Throwable t) { + Log.e(TAG, "Exception in Value callback", t); + } + } }); } return Arrays.equals(data, currentChunk); From 11af5273b20ff112415c12208b027376723cce48 Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Mon, 21 Mar 2022 16:34:56 +0100 Subject: [PATCH 04/10] isComplete reverted to hasMore in ReadRequest to match other request types --- .../java/no/nordicsemi/android/ble/BleManagerHandler.java | 4 ++-- ble/src/main/java/no/nordicsemi/android/ble/ReadRequest.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ble/src/main/java/no/nordicsemi/android/ble/BleManagerHandler.java b/ble/src/main/java/no/nordicsemi/android/ble/BleManagerHandler.java index f13e50ef..5a494f4a 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/BleManagerHandler.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/BleManagerHandler.java @@ -2083,7 +2083,7 @@ public void onCharacteristicRead(final BluetoothGatt gatt, if (matches) { rr.notifyValueChanged(gatt.getDevice(), data); } - if (!matches || !rr.isComplete()) { + if (!matches || rr.hasMore()) { enqueueFirst(rr); } else { rr.notifySuccess(gatt.getDevice()); @@ -2197,7 +2197,7 @@ public void onDescriptorRead(final BluetoothGatt gatt, final BluetoothGattDescri if (request instanceof ReadRequest) { final ReadRequest request = (ReadRequest) BleManagerHandler.this.request; request.notifyValueChanged(gatt.getDevice(), data); - if (!request.isComplete()) { + if (request.hasMore()) { enqueueFirst(request); } else { request.notifySuccess(gatt.getDevice()); diff --git a/ble/src/main/java/no/nordicsemi/android/ble/ReadRequest.java b/ble/src/main/java/no/nordicsemi/android/ble/ReadRequest.java index 8193e425..ca317faf 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/ReadRequest.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/ReadRequest.java @@ -313,7 +313,7 @@ void notifyValueChanged(@NonNull final BluetoothDevice device, @Nullable final b } @SuppressWarnings("BooleanMethodIsAlwaysInverted") - boolean isComplete() { - return complete; + boolean hasMore() { + return !complete; } } From a028024d326496affac095fbb74a8cc20902b20d Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Mon, 21 Mar 2022 17:02:36 +0100 Subject: [PATCH 05/10] Feature: progress notifications as Flow --- .../android/ble/ktx/ProgressIndicaton.kt | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ProgressIndicaton.kt diff --git a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ProgressIndicaton.kt b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ProgressIndicaton.kt new file mode 100644 index 00000000..a39f97f9 --- /dev/null +++ b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ProgressIndicaton.kt @@ -0,0 +1,136 @@ +@file:Suppress("unused") + +package no.nordicsemi.android.ble.ktx + +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import no.nordicsemi.android.ble.* +import no.nordicsemi.android.ble.data.DataMerger +import no.nordicsemi.android.ble.data.DataSplitter +import no.nordicsemi.android.ble.data.DefaultMtuSplitter + +/** + * The upload or download progress indication. + * + * @property index The 0-based index of the packet. The packet will be app + */ +data class ProgressIndication(val index: Int, val data: ByteArray?) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ProgressIndication + + if (index != other.index) return false + if (data != null) { + if (other.data == null) return false + if (!data.contentEquals(other.data)) return false + } else if (other.data != null) return false + + return true + } + + override fun hashCode(): Int { + var result = index + result = 31 * result + (data?.contentHashCode() ?: 0) + return result + } +} + +/** + * Adds a merger that will be used to merge multiple packets into a single Data. + * The merger may modify each packet if necessary. + * + * The returned flow will be notified each time a new packet is received. + * + * @return The flow with progress indications. + */ +fun ReadRequest.mergeWithProgressFlow(merger: DataMerger): Flow = callbackFlow { + merge(merger) { _, data, index -> trySend(ProgressIndication(index, data)) } + awaitClose { + merge(merger) // remove the progress listener, but keep the merger + } +} + +/** + * Adds a merger that will be used to merge multiple packets into a single Data. + * The merger may modify each packet if necessary. + * + * The returned flow will be notified each time a new packet is received. + * + * @return The flow with progress indications. + */ +fun WaitForValueChangedRequest.mergeWithProgressFlow(merger: DataMerger): Flow = callbackFlow { + merge(merger) { _, data, index -> trySend(ProgressIndication(index, data)) } + awaitClose { + merge(merger) // remove the progress listener, but keep the merger + } +} + +/** + * Adds a merger that will be used to merge multiple packets into a single Data. + * The merger may modify each packet if necessary. + * + * The returned flow will be notified each time a new packet is received. + * + * @return The flow with progress indications. + */ +fun ValueChangedCallback.mergeWithProgressFlow(merger: DataMerger): Flow = callbackFlow { + merge(merger) { _, data, index -> trySend(ProgressIndication(index, data)) } + awaitClose { + merge(merger) // remove the progress listener, but keep the merger + } +} + +/** + * Adds a default MTU splitter that will be used to cut given data into at-most MTU-3 + * bytes long packets. + * + * The returned flow will be notified each time a new packet is sent. + * + * @return The flow with progress indications. + */ +fun WriteRequest.splitWithProgressFlow(): Flow = splitWithProgressFlow(DefaultMtuSplitter()) + +/** + * Adds a splitter that will be used to cut given data into multiple packets. + * The splitter may modify each packet if necessary, i.e. add a flag indicating first packet, + * continuation or the last packet. + * + * The returned flow will be notified each time a new packet is sent. + * + * @return The flow with progress indications. + */ +fun WriteRequest.splitWithProgressFlow(splitter: DataSplitter): Flow = callbackFlow { + split(splitter) { _, data, index -> trySend(ProgressIndication(index, data)) } + awaitClose { + split(splitter) // remove the progress listener, but keep the merger + } +} + +/** + * Adds a default MTU splitter that will be used to cut given data into at-most MTU-3 + * bytes long packets. + * + * The returned flow will be notified each time a new packet is sent. + * + * @return The flow with progress indications. + */ +fun WaitForReadRequest.splitWithProgressFlow(): Flow = splitWithProgressFlow(DefaultMtuSplitter()) + +/** + * Adds a splitter that will be used to cut given data into multiple packets. + * The splitter may modify each packet if necessary, i.e. add a flag indicating first packet, + * continuation or the last packet. + * + * The returned flow will be notified each time a new packet is sent. + * + * @return The flow with progress indications. + */ +fun WaitForReadRequest.splitWithProgressFlow(splitter: DataSplitter): Flow = callbackFlow { + split(splitter) { _, data, index -> trySend(ProgressIndication(index, data)) } + awaitClose { + split(splitter) // remove the progress listener, but keep the merger + } +} \ No newline at end of file From cf42c8f62de7db65cac39d0367b0f5815dd210d1 Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Mon, 21 Mar 2022 20:37:22 +0100 Subject: [PATCH 06/10] Comment --- .../java/no/nordicsemi/android/ble/ktx/ProgressIndicaton.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ProgressIndicaton.kt b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ProgressIndicaton.kt index a39f97f9..2e43dd60 100644 --- a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ProgressIndicaton.kt +++ b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ProgressIndicaton.kt @@ -13,7 +13,10 @@ import no.nordicsemi.android.ble.data.DefaultMtuSplitter /** * The upload or download progress indication. * - * @property index The 0-based index of the packet. The packet will be app + * @property index The 0-based index of the packet. Only the packets that passed the filter + * will be reported. As the number of expected packets is not know, it is up to the + * application to calculate the real progress based on the index and data length. + * @property data The latest received packet as it was sent by the remote device. */ data class ProgressIndication(val index: Int, val data: ByteArray?) { override fun equals(other: Any?): Boolean { From 3c1cc29af0654b6c3eec8eb105b3399c741542f2 Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Mon, 21 Mar 2022 20:56:26 +0100 Subject: [PATCH 07/10] Missing try-catch block for user callbacks --- .../android/ble/ValueChangedCallback.java | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/ble/src/main/java/no/nordicsemi/android/ble/ValueChangedCallback.java b/ble/src/main/java/no/nordicsemi/android/ble/ValueChangedCallback.java index b02dc42f..d16f22d5 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/ValueChangedCallback.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/ValueChangedCallback.java @@ -24,6 +24,7 @@ import android.bluetooth.BluetoothDevice; import android.os.Handler; +import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -37,6 +38,8 @@ @SuppressWarnings({"unused", "UnusedReturnValue"}) public class ValueChangedCallback { + private static final String TAG = ValueChangedCallback.class.getSimpleName(); + private ReadProgressCallback progressCallback; private DataReceivedCallback valueCallback; private DataMerger dataMerger; @@ -172,11 +175,22 @@ void notifyValueChanged(@NonNull final BluetoothDevice device, @Nullable final b if (dataMerger == null && (packetFilter == null || packetFilter.filter(value))) { final Data data = new Data(value); - handler.post(() -> valueCallback.onDataReceived(device, data)); + handler.post(() -> { + try { + valueCallback.onDataReceived(device, data); + } catch (final Throwable t) { + Log.e(TAG, "Exception in Value callback", t); + } + }); } else { handler.post(() -> { - if (progressCallback != null) - progressCallback.onPacketReceived(device, value, count); + if (progressCallback != null) { + try { + progressCallback.onPacketReceived(device, value, count); + } catch (final Throwable t) { + Log.e(TAG, "Exception in Progress callback", t); + } + } }); if (buffer == null) buffer = new DataStream(); @@ -184,7 +198,13 @@ void notifyValueChanged(@NonNull final BluetoothDevice device, @Nullable final b final byte[] merged = buffer.toByteArray(); if (packetFilter == null || packetFilter.filter(merged)) { final Data data = new Data(merged); - handler.post(() -> valueCallback.onDataReceived(device, data)); + handler.post(() -> { + try { + valueCallback.onDataReceived(device, data); + } catch (final Throwable t) { + Log.e(TAG, "Exception in Value callback", t); + } + }); } buffer = null; count = 0; From 85921f6337fc9bd316b1accdd6e90701d032d283 Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Mon, 21 Mar 2022 20:57:54 +0100 Subject: [PATCH 08/10] Calling user callbacks without a handler when suspend or flow is used --- .../no/nordicsemi/android/ble/ktx/ProgressIndicaton.kt | 10 ++++++++++ .../no/nordicsemi/android/ble/ktx/RequestSuspend.kt | 4 ++++ .../android/ble/ktx/ValueChangedCallbackExt.kt | 6 ++++++ 3 files changed, 20 insertions(+) diff --git a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ProgressIndicaton.kt b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ProgressIndicaton.kt index 2e43dd60..6725cde4 100644 --- a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ProgressIndicaton.kt +++ b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ProgressIndicaton.kt @@ -50,6 +50,8 @@ data class ProgressIndication(val index: Int, val data: ByteArray?) { * @return The flow with progress indications. */ fun ReadRequest.mergeWithProgressFlow(merger: DataMerger): Flow = callbackFlow { + // Make sure the callbacks are called without unnecessary delay. + setHandler(null) merge(merger) { _, data, index -> trySend(ProgressIndication(index, data)) } awaitClose { merge(merger) // remove the progress listener, but keep the merger @@ -65,6 +67,8 @@ fun ReadRequest.mergeWithProgressFlow(merger: DataMerger): Flow = callbackFlow { + // Make sure the callbacks are called without unnecessary delay. + setHandler(null) merge(merger) { _, data, index -> trySend(ProgressIndication(index, data)) } awaitClose { merge(merger) // remove the progress listener, but keep the merger @@ -80,6 +84,8 @@ fun WaitForValueChangedRequest.mergeWithProgressFlow(merger: DataMerger): Flow

= callbackFlow { + // Make sure the callbacks are called without unnecessary delay. + setHandler(null) merge(merger) { _, data, index -> trySend(ProgressIndication(index, data)) } awaitClose { merge(merger) // remove the progress listener, but keep the merger @@ -106,6 +112,8 @@ fun WriteRequest.splitWithProgressFlow(): Flow = splitWithPr * @return The flow with progress indications. */ fun WriteRequest.splitWithProgressFlow(splitter: DataSplitter): Flow = callbackFlow { + // Make sure the callbacks are called without unnecessary delay. + setHandler(null) split(splitter) { _, data, index -> trySend(ProgressIndication(index, data)) } awaitClose { split(splitter) // remove the progress listener, but keep the merger @@ -132,6 +140,8 @@ fun WaitForReadRequest.splitWithProgressFlow(): Flow = split * @return The flow with progress indications. */ fun WaitForReadRequest.splitWithProgressFlow(splitter: DataSplitter): Flow = callbackFlow { + // Make sure the callbacks are called without unnecessary delay. + setHandler(null) split(splitter) { _, data, index -> trySend(ProgressIndication(index, data)) } awaitClose { split(splitter) // remove the progress listener, but keep the merger diff --git a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/RequestSuspend.kt b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/RequestSuspend.kt index 0f86b424..66adadb2 100644 --- a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/RequestSuspend.kt +++ b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/RequestSuspend.kt @@ -289,6 +289,8 @@ suspend inline fun WaitForValueChangedRequest.s suspend fun WaitForReadRequest.suspend(): Data = suspendCancellableCoroutine { continuation -> var data: Data? = null this + // Make sure the callbacks are called without unnecessary delay. + .setHandler(null) // DON'T USE .before callback here, it's used to get BluetoothDevice instance above. .with { _, d -> data = d } .invalid { continuation.resumeWithException(InvalidRequestException(this)) } @@ -335,6 +337,8 @@ suspend inline fun WaitForReadRequest.suspendForRespo private suspend fun Request.suspendCancellable(): Unit = suspendCancellableCoroutine { continuation -> this + // Make sure the callbacks are called without unnecessary delay. + .setHandler(null) // DON'T USE .before callback here, it's used to get BluetoothDevice instance above. .invalid { continuation.resumeWithException(InvalidRequestException(this)) } .fail { _, status -> diff --git a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ValueChangedCallbackExt.kt b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ValueChangedCallbackExt.kt index 5795a546..2822824a 100644 --- a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ValueChangedCallbackExt.kt +++ b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ValueChangedCallbackExt.kt @@ -21,6 +21,8 @@ import no.nordicsemi.android.ble.response.ReadResponse */ @ExperimentalCoroutinesApi fun ValueChangedCallback.asFlow(): Flow = callbackFlow { + // Make sure the callbacks are called without unnecessary delay. + setHandler(null) with { _, data -> trySend(data) } @@ -42,6 +44,8 @@ fun ValueChangedCallback.asFlow(): Flow = callbackFlow { */ @ExperimentalCoroutinesApi inline fun ValueChangedCallback.asResponseFlow(): Flow = callbackFlow { + // Make sure the callbacks are called without unnecessary delay. + setHandler(null) with { device, data -> trySend(T::class.java.newInstance().apply { onDataReceived(device, data) }) } @@ -64,6 +68,8 @@ inline fun ValueChangedCallback.asResponseFlow(): Flow */ @ExperimentalCoroutinesApi inline fun ValueChangedCallback.asValidResponseFlow(): Flow = callbackFlow { + // Make sure the callbacks are called without unnecessary delay. + setHandler(null) with { device, data -> T::class.java.newInstance() .apply { onDataReceived(device, data) } From 995a8433a856d15649b464f3a669ef998367fc33 Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Tue, 22 Mar 2022 15:44:36 +0100 Subject: [PATCH 09/10] Bug fix: The splitter or merger can't be set in callbackFlow, as it's too late --- .../android/ble/ktx/ProgressIndicaton.kt | 90 ++++++++++++++----- .../android/ble/BleManagerHandler.java | 16 +++- .../android/ble/ValueChangedCallback.java | 42 +++++++-- .../android/ble/callback/ClosedCallback.java | 32 +++++++ 4 files changed, 149 insertions(+), 31 deletions(-) create mode 100644 ble/src/main/java/no/nordicsemi/android/ble/callback/ClosedCallback.java diff --git a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ProgressIndicaton.kt b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ProgressIndicaton.kt index 6725cde4..d6f73bef 100644 --- a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ProgressIndicaton.kt +++ b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ProgressIndicaton.kt @@ -49,12 +49,22 @@ data class ProgressIndication(val index: Int, val data: ByteArray?) { * * @return The flow with progress indications. */ -fun ReadRequest.mergeWithProgressFlow(merger: DataMerger): Flow = callbackFlow { +fun ReadRequest.mergeWithProgressFlow(merger: DataMerger): Flow { // Make sure the callbacks are called without unnecessary delay. setHandler(null) - merge(merger) { _, data, index -> trySend(ProgressIndication(index, data)) } - awaitClose { - merge(merger) // remove the progress listener, but keep the merger + // Create a temporary callback that will be used to emit progress. + var callback: ((ProgressIndication) -> Unit)? = null + // Set the merger, which will invoke the temporary callback on progress. + // The merger must be called here, not in the callbackFlow. + merge(merger) { _, data, index -> + // The temporary callback will be set in the callbackFlow below. + callback?.invoke(ProgressIndication(index, data)) + } + // Return the callback flow. It will be closed when the request is complete or has failed. + return callbackFlow { + callback = { trySend(it) } + then { close() } + awaitClose { callback = null } } } @@ -66,12 +76,22 @@ fun ReadRequest.mergeWithProgressFlow(merger: DataMerger): Flow = callbackFlow { +fun WaitForValueChangedRequest.mergeWithProgressFlow(merger: DataMerger): Flow { // Make sure the callbacks are called without unnecessary delay. setHandler(null) - merge(merger) { _, data, index -> trySend(ProgressIndication(index, data)) } - awaitClose { - merge(merger) // remove the progress listener, but keep the merger + // Create a temporary callback that will be used to emit progress. + var callback: ((ProgressIndication) -> Unit)? = null + // Set the merger, which will invoke the temporary callback on progress. + // The merger must be called here, not in the callbackFlow. + merge(merger) { _, data, index -> + // The temporary callback will be set in the callbackFlow below. + callback?.invoke(ProgressIndication(index, data)) + } + // Return the callback flow. It will be closed when the request is complete or has failed. + return callbackFlow { + callback = { trySend(it) } + then { close() } + awaitClose { callback = null } } } @@ -83,12 +103,22 @@ fun WaitForValueChangedRequest.mergeWithProgressFlow(merger: DataMerger): Flow

= callbackFlow { +fun ValueChangedCallback.mergeWithProgressFlow(merger: DataMerger): Flow { // Make sure the callbacks are called without unnecessary delay. setHandler(null) - merge(merger) { _, data, index -> trySend(ProgressIndication(index, data)) } - awaitClose { - merge(merger) // remove the progress listener, but keep the merger + // Create a temporary callback that will be used to emit progress. + var callback: ((ProgressIndication) -> Unit)? = null + // Set the merger, which will invoke the temporary callback on progress. + // The merger must be called here, not in the callbackFlow. + merge(merger) { _, data, index -> + // The temporary callback will be set in the callbackFlow below. + callback?.invoke(ProgressIndication(index, data)) + } + // Return the callback flow. It will be closed when the request is complete or has failed. + return callbackFlow { + callback = { trySend(it) } + then { close() } + awaitClose { callback = null } } } @@ -111,12 +141,22 @@ fun WriteRequest.splitWithProgressFlow(): Flow = splitWithPr * * @return The flow with progress indications. */ -fun WriteRequest.splitWithProgressFlow(splitter: DataSplitter): Flow = callbackFlow { +fun WriteRequest.splitWithProgressFlow(splitter: DataSplitter): Flow { // Make sure the callbacks are called without unnecessary delay. setHandler(null) - split(splitter) { _, data, index -> trySend(ProgressIndication(index, data)) } - awaitClose { - split(splitter) // remove the progress listener, but keep the merger + // Create a temporary callback that will be used to emit progress. + var callback: ((ProgressIndication) -> Unit)? = null + // Set the splitter, which will invoke the temporary callback on progress. + // The splitter must be called here, not in the callbackFlow. + split(splitter) { _, data, index -> + // The temporary callback will be set in the callbackFlow below. + callback?.invoke(ProgressIndication(index, data)) + } + // Return the callback flow. It will be closed when the request is complete or has failed. + return callbackFlow { + callback = { trySend(it) } + then { close() } + awaitClose { callback = null } } } @@ -139,11 +179,21 @@ fun WaitForReadRequest.splitWithProgressFlow(): Flow = split * * @return The flow with progress indications. */ -fun WaitForReadRequest.splitWithProgressFlow(splitter: DataSplitter): Flow = callbackFlow { +fun WaitForReadRequest.splitWithProgressFlow(splitter: DataSplitter): Flow { // Make sure the callbacks are called without unnecessary delay. setHandler(null) - split(splitter) { _, data, index -> trySend(ProgressIndication(index, data)) } - awaitClose { - split(splitter) // remove the progress listener, but keep the merger + // Set the splitter, which will invoke the temporary callback on progress. + // The splitter must be called here, not in the callbackFlow. + var callback: ((ProgressIndication) -> Unit)? = null + // Set the splitter, which invokes the temporary callback. + split(splitter) { _, data, index -> + // The temporary callback will be set in the callbackFlow below. + callback?.invoke(ProgressIndication(index, data)) + } + // Return the callback flow. It will be closed when the request is complete or has failed. + return callbackFlow { + callback = { trySend(it) } + then { close() } + awaitClose { callback = null } } } \ No newline at end of file diff --git a/ble/src/main/java/no/nordicsemi/android/ble/BleManagerHandler.java b/ble/src/main/java/no/nordicsemi/android/ble/BleManagerHandler.java index 5a494f4a..11716af6 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/BleManagerHandler.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/BleManagerHandler.java @@ -49,6 +49,7 @@ import no.nordicsemi.android.ble.callback.MtuCallback; import no.nordicsemi.android.ble.callback.SuccessCallback; import no.nordicsemi.android.ble.data.Data; +import no.nordicsemi.android.ble.data.PacketFilter; import no.nordicsemi.android.ble.error.GattError; import no.nordicsemi.android.ble.observer.BondingObserver; import no.nordicsemi.android.ble.observer.ConnectionObserver; @@ -461,7 +462,6 @@ void close() { } reliableWriteInProgress = false; initialConnection = false; - valueChangedCallbacks.clear(); // close() is called in notifyDeviceDisconnected, which may enqueue new requests. // Setting this flag to false would allow to enqueue a new request before the // current one ends processing. The following line should not be uncommented. @@ -1178,8 +1178,11 @@ ValueChangedCallback getValueChangedCallback(@Nullable final Object attribute) { if (attribute != null) { valueChangedCallbacks.put(attribute, callback); } + } else if (bluetoothDevice != null) { + callback.notifyClosed(); } - return callback.free(); + // TODO If attribute is null, the notifyDone(device) will never be called. + return callback; } /** @@ -1188,7 +1191,10 @@ ValueChangedCallback getValueChangedCallback(@Nullable final Object attribute) { * @param attribute attribute to unbind the callback from. */ void removeValueChangedCallback(@Nullable final Object attribute) { - valueChangedCallbacks.remove(attribute); + final ValueChangedCallback callback = valueChangedCallbacks.remove(attribute); + if (callback != null) { + callback.notifyClosed(); + } } @Deprecated @@ -1615,6 +1621,10 @@ private void notifyDeviceDisconnected(@NonNull final BluetoothDevice device, fin // automatically. // This may be only called when the shouldAutoConnect() method returned true. } + for (final ValueChangedCallback callback : valueChangedCallbacks.values()) { + callback.notifyClosed(); + } + valueChangedCallbacks.clear(); onServicesInvalidated(); onDeviceDisconnected(); } diff --git a/ble/src/main/java/no/nordicsemi/android/ble/ValueChangedCallback.java b/ble/src/main/java/no/nordicsemi/android/ble/ValueChangedCallback.java index d16f22d5..e840f609 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/ValueChangedCallback.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/ValueChangedCallback.java @@ -28,6 +28,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import no.nordicsemi.android.ble.callback.ClosedCallback; import no.nordicsemi.android.ble.callback.DataReceivedCallback; import no.nordicsemi.android.ble.callback.ReadProgressCallback; import no.nordicsemi.android.ble.data.Data; @@ -40,6 +41,7 @@ public class ValueChangedCallback { private static final String TAG = ValueChangedCallback.class.getSimpleName(); + private ClosedCallback closedCallback; private ReadProgressCallback progressCallback; private DataReceivedCallback valueCallback; private DataMerger dataMerger; @@ -149,14 +151,16 @@ public ValueChangedCallback merge(@NonNull final DataMerger merger, return this; } - ValueChangedCallback free() { - valueCallback = null; - dataMerger = null; - progressCallback = null; - filter = null; - packetFilter = null; - buffer = null; - count = 0; + /** + * Sets a callback that will be executed when the device services were invalidated (i.e. on + * disconnection) or the callback has been unregistered and it can release resources. + * + * @param callback the callback. + * @return The request. + */ + @NonNull + public ValueChangedCallback then(@NonNull final ClosedCallback callback) { + this.closedCallback = callback; return this; } @@ -212,4 +216,26 @@ void notifyValueChanged(@NonNull final BluetoothDevice device, @Nullable final b // wait for more packets to be merged } } + + void notifyClosed() { + if (closedCallback != null) { + try { + closedCallback.onClosed(); + } catch (final Throwable t) { + Log.e(TAG, "Exception in Closed callback", t); + } + } + free(); + } + + private void free() { + closedCallback = null; + valueCallback = null; + dataMerger = null; + progressCallback = null; + filter = null; + packetFilter = null; + buffer = null; + count = 0; + } } diff --git a/ble/src/main/java/no/nordicsemi/android/ble/callback/ClosedCallback.java b/ble/src/main/java/no/nordicsemi/android/ble/callback/ClosedCallback.java new file mode 100644 index 00000000..bc392b8b --- /dev/null +++ b/ble/src/main/java/no/nordicsemi/android/ble/callback/ClosedCallback.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.ble.callback; + +@FunctionalInterface +public interface ClosedCallback { + + /** + * A callback when the value changed callback will not be used anymore. + */ + void onClosed(); +} From fba63e69447a2e71f8b2872345b256f7ed4321c9 Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Tue, 22 Mar 2022 15:49:24 +0100 Subject: [PATCH 10/10] Version information included --- .../no/nordicsemi/android/ble/ktx/BleManagerExt.kt | 2 ++ .../android/ble/ktx/BluetoothGattServiceExt.kt | 1 + .../android/ble/ktx/ProgressIndicaton.kt | 8 ++++++++ .../nordicsemi/android/ble/ktx/RequestSuspend.kt | 14 ++++++++++++++ .../android/ble/ktx/ValueChangedCallbackExt.kt | 3 +++ 5 files changed, 28 insertions(+) diff --git a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/BleManagerExt.kt b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/BleManagerExt.kt index 97607b2d..4ab35be8 100644 --- a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/BleManagerExt.kt +++ b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/BleManagerExt.kt @@ -45,6 +45,7 @@ val BleManager.bondingState: BondState * Multiple calls for this method return the same object. * If a different connection observer was set using [BleManager.setConnectionObserver], this * method will throw [IllegalStateException]. + * @since 2.3.0 */ fun BleManager.stateAsFlow(): StateFlow = with(connectionObserver) { when (this) { @@ -59,6 +60,7 @@ fun BleManager.stateAsFlow(): StateFlow = with(connectionObserv * Multiple calls for this method return the same object. * If a different bond state observer was set using [BleManager.setBondingObserver], this * method will throw [IllegalStateException]. + * @since 2.3.0 */ fun BleManager.bondingStateAsFlow(): StateFlow = with(bondingObserver) { when (this) { diff --git a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/BluetoothGattServiceExt.kt b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/BluetoothGattServiceExt.kt index 1ecd6ad6..8b862422 100644 --- a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/BluetoothGattServiceExt.kt +++ b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/BluetoothGattServiceExt.kt @@ -16,6 +16,7 @@ import java.util.UUID * manually. * * @return GATT characteristic object or null if no characteristic was found. + * @since 2.3.0 */ fun BluetoothGattService.getCharacteristic( uuid: UUID, diff --git a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ProgressIndicaton.kt b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ProgressIndicaton.kt index d6f73bef..a734a052 100644 --- a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ProgressIndicaton.kt +++ b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ProgressIndicaton.kt @@ -13,6 +13,7 @@ import no.nordicsemi.android.ble.data.DefaultMtuSplitter /** * The upload or download progress indication. * + * @since 2.4.0 * @property index The 0-based index of the packet. Only the packets that passed the filter * will be reported. As the number of expected packets is not know, it is up to the * application to calculate the real progress based on the index and data length. @@ -48,6 +49,7 @@ data class ProgressIndication(val index: Int, val data: ByteArray?) { * The returned flow will be notified each time a new packet is received. * * @return The flow with progress indications. + * @since 2.4.0 */ fun ReadRequest.mergeWithProgressFlow(merger: DataMerger): Flow { // Make sure the callbacks are called without unnecessary delay. @@ -75,6 +77,7 @@ fun ReadRequest.mergeWithProgressFlow(merger: DataMerger): Flow { // Make sure the callbacks are called without unnecessary delay. @@ -102,6 +105,7 @@ fun WaitForValueChangedRequest.mergeWithProgressFlow(merger: DataMerger): Flow

{ // Make sure the callbacks are called without unnecessary delay. @@ -129,6 +133,7 @@ fun ValueChangedCallback.mergeWithProgressFlow(merger: DataMerger): Flow = splitWithProgressFlow(DefaultMtuSplitter()) @@ -140,6 +145,7 @@ fun WriteRequest.splitWithProgressFlow(): Flow = splitWithPr * The returned flow will be notified each time a new packet is sent. * * @return The flow with progress indications. + * @since 2.4.0 */ fun WriteRequest.splitWithProgressFlow(splitter: DataSplitter): Flow { // Make sure the callbacks are called without unnecessary delay. @@ -167,6 +173,7 @@ fun WriteRequest.splitWithProgressFlow(splitter: DataSplitter): Flow = splitWithProgressFlow(DefaultMtuSplitter()) @@ -178,6 +185,7 @@ fun WaitForReadRequest.splitWithProgressFlow(): Flow = split * The returned flow will be notified each time a new packet is sent. * * @return The flow with progress indications. + * @since 2.4.0 */ fun WaitForReadRequest.splitWithProgressFlow(splitter: DataSplitter): Flow { // Make sure the callbacks are called without unnecessary delay. diff --git a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/RequestSuspend.kt b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/RequestSuspend.kt index 66adadb2..8db8e910 100644 --- a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/RequestSuspend.kt +++ b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/RequestSuspend.kt @@ -16,6 +16,7 @@ import kotlin.coroutines.resumeWithException /** * Suspends the coroutine until the request is completed. + * @since 2.3.0 */ @Throws( BluetoothDisabledException::class, @@ -28,6 +29,7 @@ suspend fun Request.suspend() = suspendCancellable() /** * Suspends the coroutine until the data have been written. * @return The data written. + * @since 2.3.0 */ @Throws( BluetoothDisabledException::class, @@ -58,6 +60,7 @@ suspend fun WriteRequest.suspend(): Data { * ).suspendForResponse() * * @return The data written parsed to required type. + * @since 2.4.0 */ @Throws( BluetoothDisabledException::class, @@ -78,6 +81,7 @@ suspend inline fun WriteRequest.suspendForResponse(): /** * Suspends the coroutine until the data have been read. * @return The data read. + * @since 2.3.0 */ @Throws( BluetoothDisabledException::class, @@ -103,6 +107,7 @@ suspend fun ReadRequest.suspend(): Data { * .suspendForResponse() * * @return The data read parsed to required type. + * @since 2.4.0 */ @Throws( BluetoothDisabledException::class, @@ -131,6 +136,7 @@ suspend inline fun ReadRequest.suspendForResponse(): T * .suspendForValidResponse() * * @return The data read parsed to required type. + * @since 2.4.0 */ @Throws( BluetoothDisabledException::class, @@ -147,6 +153,7 @@ suspend inline fun ReadRequest.suspendForValidR /** * Suspends the coroutine until the RSSI value is received. * @return The current RSSI value. + * @since 2.3.0 */ @Throws( BluetoothDisabledException::class, @@ -165,6 +172,7 @@ suspend fun ReadRssiRequest.suspend(): Int { /** * Suspends the coroutine until the MTU value is received. * @return The current MTU value. + * @since 2.3.0 */ @Throws( BluetoothDisabledException::class, @@ -183,6 +191,7 @@ suspend fun MtuRequest.suspend(): Int { /** * Suspends the coroutine until the TX and RX PHY values are received. * @return A pair of TX and RX PHYs. + * @since 2.3.0 */ @Throws( BluetoothDisabledException::class, @@ -201,6 +210,7 @@ suspend fun PhyRequest.suspend(): Pair { /** * Suspends the coroutine until the value of the attribute has changed. * @return The new value of the attribute. + * @since 2.3.0 */ @Throws( BluetoothDisabledException::class, @@ -236,6 +246,7 @@ suspend fun WaitForValueChangedRequest.suspend(): Data = suspendCancellableCoro * .suspendForResponse() * * @return The new value of the attribute. + * @since 2.4.0 */ @Throws( BluetoothDisabledException::class, @@ -263,6 +274,7 @@ suspend inline fun WaitForValueChangedRequest.suspendF * .suspendForValidResponse() * * @return The new value of the attribute. + * @since 2.4.0 */ @Throws( BluetoothDisabledException::class, @@ -279,6 +291,7 @@ suspend inline fun WaitForValueChangedRequest.s /** * Suspends the coroutine until the value of the attribute has changed. * @return The new value of the attribute. + * @since 2.3.0 */ @Throws( BluetoothDisabledException::class, @@ -318,6 +331,7 @@ suspend fun WaitForReadRequest.suspend(): Data = suspendCancellableCoroutine { * .suspendForResponse() * * @return The new value of the attribute. + * @since 2.4.0 */ @Throws( BluetoothDisabledException::class, diff --git a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ValueChangedCallbackExt.kt b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ValueChangedCallbackExt.kt index 2822824a..337ae166 100644 --- a/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ValueChangedCallbackExt.kt +++ b/ble-ktx/src/main/java/no/nordicsemi/android/ble/ktx/ValueChangedCallbackExt.kt @@ -18,6 +18,7 @@ import no.nordicsemi.android.ble.response.ReadResponse * * val hrmMeasurementsData = setNotificationCallback(hrmCharacteristic).asFlow() // Flow * @return The flow. + * @since 2.3.0 */ @ExperimentalCoroutinesApi fun ValueChangedCallback.asFlow(): Flow = callbackFlow { @@ -41,6 +42,7 @@ fun ValueChangedCallback.asFlow(): Flow = callbackFlow { * setNotificationCallback(hrmCharacteristic) * .asResponseFlow() * @return The flow. + * @since 2.4.0 */ @ExperimentalCoroutinesApi inline fun ValueChangedCallback.asResponseFlow(): Flow = callbackFlow { @@ -65,6 +67,7 @@ inline fun ValueChangedCallback.asResponseFlow(): Flow * setNotificationCallback(hrmCharacteristic) * .asValidResponseFlow() * @return The flow. + * @since 2.4.0 */ @ExperimentalCoroutinesApi inline fun ValueChangedCallback.asValidResponseFlow(): Flow = callbackFlow {