Skip to content

java.lang.IndexOutOfBoundsException when parsing PPI data and starting offline HR recording #532

Open
@orestesgaolin

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

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions