Skip to content

Commit fffac9c

Browse files
fix(core): replace ThreeTenBP with SystemClock in CallService (#1623)
When the system launches CallService (e.g. from a push notification or process restart), AndroidThreeTen may not be initialized since StreamVideoBuilder.build() hasn't run yet. This causes a ZoneRulesException crash on OffsetDateTime.now() in onCreate. Since the only purpose is measuring elapsed service uptime for the stop debouncer, replace OffsetDateTime with SystemClock.elapsedRealtime() which is monotonic, requires no initialization, and is unaffected by wall clock changes.
1 parent fc23079 commit fffac9c

3 files changed

Lines changed: 11 additions & 15 deletions

File tree

stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import android.app.Notification
2121
import android.app.Service
2222
import android.content.Intent
2323
import android.os.IBinder
24+
import android.os.SystemClock
2425
import androidx.core.app.NotificationManagerCompat
2526
import androidx.media.session.MediaButtonReceiver
2627
import io.getstream.log.taggedLogger
@@ -53,9 +54,7 @@ import kotlinx.coroutines.CoroutineScope
5354
import kotlinx.coroutines.Dispatchers
5455
import kotlinx.coroutines.SupervisorJob
5556
import kotlinx.coroutines.cancel
56-
import org.threeten.bp.Duration
57-
import org.threeten.bp.OffsetDateTime
58-
import kotlin.math.absoluteValue
57+
import java.util.concurrent.TimeUnit
5958

6059
/**
6160
* A foreground service that is running when there is an active call.
@@ -117,7 +116,7 @@ internal open class CallService : Service() {
117116

118117
override fun onCreate() {
119118
super.onCreate()
120-
serviceStateController.setStartTime(OffsetDateTime.now())
119+
serviceStateController.setStartTime(SystemClock.elapsedRealtime())
121120
}
122121

123122
private fun shouldStopService(intent: Intent?): Boolean {
@@ -616,11 +615,10 @@ internal open class CallService : Service() {
616615
logger.w { "[stopServiceGracefully] source: $source" }
617616
}
618617

619-
serviceStateController.startTime?.let { startTime ->
618+
serviceStateController.startTimeElapsedRealtime?.let { startTime ->
620619

621-
val currentTime = OffsetDateTime.now()
622-
val duration = Duration.between(startTime, currentTime)
623-
val differenceInSeconds = duration.seconds.absoluteValue
620+
val elapsedMs = SystemClock.elapsedRealtime() - startTime
621+
val differenceInSeconds = TimeUnit.MILLISECONDS.toSeconds(elapsedMs)
624622
val debouncerThresholdTimeInSeconds = SERVICE_DESTROY_THRESHOLD_TIME_MS / 1_000
625623
logger.d { "[stopServiceGracefully] differenceInSeconds: $differenceInSeconds" }
626624
if (differenceInSeconds >= debouncerThresholdTimeInSeconds) {

stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/controllers/ServiceStateController.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import kotlinx.coroutines.CoroutineScope
2727
import kotlinx.coroutines.flow.MutableStateFlow
2828
import kotlinx.coroutines.flow.StateFlow
2929
import kotlinx.coroutines.flow.update
30-
import org.threeten.bp.OffsetDateTime
3130

3231
internal class ServiceStateController {
3332
private val _state = MutableStateFlow(ServiceStateSnapshot())
@@ -42,8 +41,8 @@ internal class ServiceStateController {
4241
val soundPlayer: CallSoundAndVibrationPlayer?
4342
get() = state.value.soundPlayer
4443

45-
val startTime: OffsetDateTime?
46-
get() = state.value.startTime
44+
val startTimeElapsedRealtime: Long?
45+
get() = state.value.startTimeElapsedRealtime
4746

4847
fun setCurrentCallId(callId: StreamCallId) {
4948
_state.update { it.copy(currentCallId = callId) }
@@ -57,8 +56,8 @@ internal class ServiceStateController {
5756
_state.update { it.copy(soundPlayer = player) }
5857
}
5958

60-
fun setStartTime(time: OffsetDateTime) {
61-
_state.update { it.copy(startTime = time) }
59+
fun setStartTime(elapsedRealtime: Long) {
60+
_state.update { it.copy(startTimeElapsedRealtime = elapsedRealtime) }
6261
}
6362

6463
fun registerToggleCameraBroadcastReceiver(

stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/models/ServiceStateSnapshot.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,12 @@ package io.getstream.video.android.core.notifications.internal.service.models
1919
import io.getstream.video.android.core.notifications.internal.receivers.ToggleCameraBroadcastReceiver
2020
import io.getstream.video.android.core.sounds.CallSoundAndVibrationPlayer
2121
import io.getstream.video.android.model.StreamCallId
22-
import org.threeten.bp.OffsetDateTime
2322

2423
internal data class ServiceStateSnapshot(
2524
val currentCallId: StreamCallId? = null,
2625
val notificationId: Int? = null,
2726
val soundPlayer: CallSoundAndVibrationPlayer? = null,
2827
val toggleCameraBroadcastReceiver: ToggleCameraBroadcastReceiver? = null,
2928
val isReceiverRegistered: Boolean = false,
30-
val startTime: OffsetDateTime? = null,
29+
val startTimeElapsedRealtime: Long? = null,
3130
)

0 commit comments

Comments
 (0)