java.lang.IndexOutOfBoundsException when parsing PPI data and starting offline HR recording #532
Open
Description
Platform on which you observed the bug:
- Android
- iOS
- Other
- Platform is not relevant for this bug
Device on which you observed the bug:
- Polar OH1
- Polar Verity Sense
- Polar H10
- Polar H9
- Other [360]
- Device is not relevant for this bug
Describe the bug
There are 2 cases where IndexOutOfBoundsException happens
Case 1 - offline recording
When starting offline recording the following exception is sometimes thrown from this slice
call:
Caused by java.lang.Exception: java.lang.IndexOutOfBoundsException: toIndex (11021) is greater than size (425).
at com.polar.sdk.impl.BDBleApiImpl.handleError(BDBleApiImpl.kt:3317)
at com.polar.sdk.impl.BDBleApiImpl.access$handleError(BDBleApiImpl.kt)
at com.polar.sdk.impl.BDBleApiImpl$getOfflineRecord$1$1$3.apply(BDBleApiImpl.kt:825)
at com.polar.sdk.impl.BDBleApiImpl$getOfflineRecord$1$1$3.apply(BDBleApiImpl.kt:825)
at io.reactivex.rxjava3.internal.operators.single.SingleResumeNext$ResumeMainSingleObserver.onError(SingleResumeNext.java:73)
at io.reactivex.rxjava3.internal.operators.single.SingleMap$MapSingleObserver.onError(SingleMap.java:70)
at io.reactivex.rxjava3.internal.operators.single.SingleMap$MapSingleObserver.onError(SingleMap.java:70)
at io.reactivex.rxjava3.internal.operators.single.SingleMap$MapSingleObserver.onSuccess(SingleMap.java:61)
at io.reactivex.rxjava3.internal.observers.ResumeSingleObserver.onSuccess(ResumeSingleObserver.java:46)
at io.reactivex.rxjava3.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.onSuccess(SingleSubscribeOn.java:68)
at io.reactivex.rxjava3.internal.operators.single.SingleDoFinally$DoFinallyObserver.onSuccess(SingleDoFinally.java:73)
at io.reactivex.rxjava3.internal.operators.single.SingleDoOnSubscribe$DoOnSubscribeSingleObserver.onSuccess(SingleDoOnSubscribe.java:77)
at io.reactivex.rxjava3.internal.operators.single.SingleCreate$Emitter.onSuccess(SingleCreate.java:68)
at com.polar.androidcommunications.api.ble.model.gatt.client.psftp.BlePsFtpClient.lambda$request$0(BlePsFtpClient.java:243)
at io.reactivex.rxjava3.internal.operators.single.SingleCreate.subscribeActual(SingleCreate.java:40)
at io.reactivex.rxjava3.core.Single.subscribe(Single.java:4855)
at io.reactivex.rxjava3.internal.operators.single.SingleDoOnSubscribe.subscribeActual(SingleDoOnSubscribe.java:41)
at io.reactivex.rxjava3.core.Single.subscribe(Single.java:4855)
at io.reactivex.rxjava3.internal.operators.single.SingleDoFinally.subscribeActual(SingleDoFinally.java:44)
at io.reactivex.rxjava3.core.Single.subscribe(Single.java:4855)
at io.reactivex.rxjava3.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.run(SingleSubscribeOn.java:89)
at io.reactivex.rxjava3.core.Scheduler$DisposeTask.run(Scheduler.java:644)
at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:65)
at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java)
at java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:307)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
at java.lang.Thread.run(Thread.java:1012)
Caused by java.lang.IndexOutOfBoundsException: toIndex (11021) is greater than size (425).
at kotlin.collections.ArraysKt__ArraysJVMKt.copyOfRangeToIndexCheck(ArraysJVM.kt:49)
at kotlin.collections.ArraysKt___ArraysJvmKt.copyOfRange(_ArraysJvm.kt:1465)
at kotlin.collections.ArraysKt___ArraysKt.slice(_Arrays.kt:4441)
at com.polar.androidcommunications.api.ble.model.offlinerecording.OfflineRecordingData$Companion.parseData-ARK9YDc(OfflineRecordingData.kt:288)
at com.polar.androidcommunications.api.ble.model.offlinerecording.OfflineRecordingData$Companion.parseDataFromOfflineFile-ARK9YDc(OfflineRecordingData.kt:70)
at com.polar.androidcommunications.api.ble.model.offlinerecording.OfflineRecordingData$Companion.parseDataFromOfflineFile-ARK9YDc$default(OfflineRecordingData.kt:49)
at com.polar.sdk.impl.BDBleApiImpl$getOfflineRecord$1$1$1.apply(BDBleApiImpl.kt:796)
at com.polar.sdk.impl.BDBleApiImpl$getOfflineRecord$1$1$1.apply(BDBleApiImpl.kt:794)
at io.reactivex.rxjava3.internal.operators.single.SingleMap$MapSingleObserver.onSuccess(SingleMap.java:58)
at io.reactivex.rxjava3.internal.observers.ResumeSingleObserver.onSuccess(ResumeSingleObserver.java:46)
at io.reactivex.rxjava3.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.onSuccess(SingleSubscribeOn.java:68)
at io.reactivex.rxjava3.internal.operators.single.SingleDoFinally$DoFinallyObserver.onSuccess(SingleDoFinally.java:73)
at io.reactivex.rxjava3.internal.operators.single.SingleDoOnSubscribe$DoOnSubscribeSingleObserver.onSuccess(SingleDoOnSubscribe.java:77)
at io.reactivex.rxjava3.internal.operators.single.SingleCreate$Emitter.onSuccess(SingleCreate.java:68)
at com.polar.androidcommunications.api.ble.model.gatt.client.psftp.BlePsFtpClient.lambda$request$0(BlePsFtpClient.java:243)
at io.reactivex.rxjava3.internal.operators.single.SingleCreate.subscribeActual(SingleCreate.java:40)
at io.reactivex.rxjava3.core.Single.subscribe(Single.java:4855)
at io.reactivex.rxjava3.internal.operators.single.SingleDoOnSubscribe.subscribeActual(SingleDoOnSubscribe.java:41)
at io.reactivex.rxjava3.core.Single.subscribe(Single.java:4855)
at io.reactivex.rxjava3.internal.operators.single.SingleDoFinally.subscribeActual(SingleDoFinally.java:44)
at io.reactivex.rxjava3.core.Single.subscribe(Single.java:4855)
at io.reactivex.rxjava3.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.run(SingleSubscribeOn.java:89)
at io.reactivex.rxjava3.core.Scheduler$DisposeTask.run(Scheduler.java:644)
at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:65)
at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java)
at java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:307)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
at java.lang.Thread.run(Thread.java:1012)
This exception seems to be irrecoverable due to following, and this means the PPI stream stops:
Non-fatal Exception: rl.d: The exception was not handled due to missing onError handler in the subscribe() method call. Further reading: https://github.com/ReactiveX/RxJava/wiki/Error-Handling | java.lang.Exception: java.lang.IndexOutOfBoundsException: toIndex (11021) is greater than size (425).
at io.reactivex.rxjava3.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:718)
at io.reactivex.rxjava3.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:715)
at io.reactivex.rxjava3.internal.observers.ConsumerSingleObserver.onError(ConsumerSingleObserver.java:46)
at io.reactivex.rxjava3.internal.operators.single.SingleDoFinally$DoFinallyObserver.onError(SingleDoFinally.java:79)
at io.reactivex.rxjava3.internal.observers.ResumeSingleObserver.onError(ResumeSingleObserver.java:51)
at io.reactivex.rxjava3.internal.disposables.EmptyDisposable.error(EmptyDisposable.java:78)
at io.reactivex.rxjava3.internal.operators.single.SingleError.subscribeActual(SingleError.java:41)
at io.reactivex.rxjava3.core.Single.subscribe(Single.java:4855)
at io.reactivex.rxjava3.internal.operators.single.SingleResumeNext$ResumeMainSingleObserver.onError(SingleResumeNext.java:80)
at io.reactivex.rxjava3.internal.operators.single.SingleMap$MapSingleObserver.onError(SingleMap.java:70)
at io.reactivex.rxjava3.internal.operators.single.SingleMap$MapSingleObserver.onError(SingleMap.java:70)
at io.reactivex.rxjava3.internal.operators.single.SingleMap$MapSingleObserver.onSuccess(SingleMap.java:61)
at io.reactivex.rxjava3.internal.observers.ResumeSingleObserver.onSuccess(ResumeSingleObserver.java:46)
at io.reactivex.rxjava3.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.onSuccess(SingleSubscribeOn.java:68)
at io.reactivex.rxjava3.internal.operators.single.SingleDoFinally$DoFinallyObserver.onSuccess(SingleDoFinally.java:73)
at io.reactivex.rxjava3.internal.operators.single.SingleDoOnSubscribe$DoOnSubscribeSingleObserver.onSuccess(SingleDoOnSubscribe.java:77)
at io.reactivex.rxjava3.internal.operators.single.SingleCreate$Emitter.onSuccess(SingleCreate.java:68)
at com.polar.androidcommunications.api.ble.model.gatt.client.psftp.BlePsFtpClient.lambda$request$0(BlePsFtpClient.java:243)
at io.reactivex.rxjava3.internal.operators.single.SingleCreate.subscribeActual(SingleCreate.java:40)
at io.reactivex.rxjava3.core.Single.subscribe(Single.java:4855)
at io.reactivex.rxjava3.internal.operators.single.SingleDoOnSubscribe.subscribeActual(SingleDoOnSubscribe.java:41)
at io.reactivex.rxjava3.core.Single.subscribe(Single.java:4855)
at io.reactivex.rxjava3.internal.operators.single.SingleDoFinally.subscribeActual(SingleDoFinally.java:44)
at io.reactivex.rxjava3.core.Single.subscribe(Single.java:4855)
at io.reactivex.rxjava3.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.run(SingleSubscribeOn.java:89)
at io.reactivex.rxjava3.core.Scheduler$DisposeTask.run(Scheduler.java:644)
at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:65)
at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java)
at java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:307)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
at java.lang.Thread.run(Thread.java:1012)
Case 2 - starting PPI stream
The exception is similar but has a different stack trace:
Non-fatal Exception: java.lang.IndexOutOfBoundsException: toIndex (8) is greater than size (6).
at kotlin.collections.ArraysKt__ArraysJVMKt.copyOfRangeToIndexCheck(ArraysJVM.kt:49)
at kotlin.collections.ArraysKt___ArraysJvmKt.copyOfRange(_ArraysJvm.kt:1465)
at com.polar.androidcommunications.common.ble.TypeUtils.convertArrayToSignedInt(TypeUtils.kt:37)
at com.polar.androidcommunications.api.ble.model.gatt.client.pmd.PmdSetting.parsePmdSettingsData(PmdSetting.kt:48)
at com.polar.androidcommunications.api.ble.model.gatt.client.pmd.PmdSetting.updateSelectedFromStartResponse(PmdSetting.kt:58)
at com.polar.androidcommunications.api.ble.model.gatt.client.pmd.BlePMDClient$startMeasurement$1.accept(BlePMDClient.kt:357)
at com.polar.androidcommunications.api.ble.model.gatt.client.pmd.BlePMDClient$startMeasurement$1.accept(BlePMDClient.kt:357)
at io.reactivex.rxjava3.internal.operators.single.SingleDoOnSuccess$DoOnSuccess.onSuccess(SingleDoOnSuccess.java:54)
at io.reactivex.rxjava3.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.onSuccess(SingleSubscribeOn.java:68)
at io.reactivex.rxjava3.internal.operators.single.SingleCreate$Emitter.onSuccess(SingleCreate.java:68)
at com.polar.androidcommunications.api.ble.model.gatt.client.pmd.BlePMDClient.sendControlPointCommand$lambda$16(BlePMDClient.kt:268)
at io.reactivex.rxjava3.internal.operators.single.SingleCreate.subscribeActual(SingleCreate.java:40)
at io.reactivex.rxjava3.core.Single.subscribe(Single.java:4855)
at io.reactivex.rxjava3.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.run(SingleSubscribeOn.java:89)
at io.reactivex.rxjava3.core.Scheduler$DisposeTask.run(Scheduler.java:644)
at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:65)
at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:919)
How to Reproduce
In case of PPI streaming we just call following API on some phones:
if (ppiDisposable != null) {
ppiDisposable?.dispose()
}
var initialTimestamp: Instant? = null
ppiDisposable =
bluetoothForegroundService
.startPpiStreaming(deviceId)
.subscribe(
{ data ->
Logger.info(
TAG,
"Received ${data.samples.size} PPI samples")
lastReceivedTimestamp = Instant.now()
val totalDuration =
data.samples.sumOf { it.ppi } + 50 // Offset by 50ms to account for latency
if (initialTimestamp == null) {
initialTimestamp = Instant.now().minusMillis(totalDuration.toLong())
}
val startTimestamp = initialTimestamp!!
var accumulatedMs = 0L
data.samples.forEach {
val sampleTime = startTimestamp.plusMillis(accumulatedMs)
var ppiData =
PpiData(
it.ppi,
sampleTime,
deviceId,
it.errorEstimate,
it.blockerBit,
it.hr,
it.skinContactStatus,
)
// posting ppi samples to event channel
Handler(Looper.getMainLooper()).post { eventSink.success(ppiData.toString()) }
accumulatedMs += it.ppi.toLong()
}
// Set the initialTimestamp for the next batch
initialTimestamp = startTimestamp.plusMillis(accumulatedMs)
},
{ error ->
Logger.error(
TAG,
"Error occurred when streaming PPI data",
error)
})
Expected behavior
The slice
operation should not fail and PPI stream should continue, and the offline recording should be able to start