Fix ForegroundServiceDidNotStartInTimeException (#6470)
* Start the `FetchPushForegroundService ` in foreground ASAP. This is a first step to mitigate `ForegroundServiceDidNotStartInTimeException` being thrown. * Don't stop the service immediately if it's running but not in foreground. Try waiting up to 5s for it to be in foreground.
This commit is contained in:
committed by
GitHub
parent
2c083a87d4
commit
8239a5ba0a
@@ -22,5 +22,5 @@ interface PushHandlingWakeLock {
|
||||
/**
|
||||
* Release the wakelock. If no wakelock is associated with the key, this method does nothing.
|
||||
*/
|
||||
fun unlock()
|
||||
suspend fun unlock()
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ class DefaultPushHandlingWakeLock(
|
||||
count.incrementAndGet()
|
||||
}
|
||||
|
||||
override fun unlock() {
|
||||
override suspend fun unlock() {
|
||||
Timber.d("Releasing wakelock used for push handling.")
|
||||
FetchPushForegroundService.stop(context)
|
||||
if (count.decrementAndGet() <= 0) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
package io.element.android.libraries.push.impl.push
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
@@ -25,7 +26,12 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import timber.log.Timber
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
private const val NOTIFICATION_ID = 1001
|
||||
|
||||
@@ -51,11 +57,13 @@ class FetchPushForegroundService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
override fun onCreate() {
|
||||
Timber.d("Creating FetchPushForegroundService")
|
||||
|
||||
bindings<PushBindings>().inject(this)
|
||||
|
||||
wakelock.acquire(wakelockTimeout)
|
||||
|
||||
Timber.d("Starting FetchPushForegroundService with wakelock timeout of $wakelockTimeout ms")
|
||||
// Start the foreground service as soon as possible
|
||||
val notificationCompat = NotificationCompat.Builder(this, notificationChannels.getSilentChannelId())
|
||||
.setSmallIcon(CommonDrawables.ic_notification)
|
||||
.setContentTitle(getString(CommonStrings.common_android_fetching_notifications_title))
|
||||
@@ -65,6 +73,12 @@ class FetchPushForegroundService : Service() {
|
||||
.build()
|
||||
startForeground(NOTIFICATION_ID, notificationCompat)
|
||||
|
||||
super.onCreate()
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
wakelock.acquire(wakelockTimeout)
|
||||
|
||||
// The timeout is not automatic before Android 15, so we need to schedule it ourselves
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
|
||||
coroutineScope.launch {
|
||||
@@ -86,10 +100,14 @@ class FetchPushForegroundService : Service() {
|
||||
override fun onTimeout(startId: Int) {
|
||||
super.onTimeout(startId)
|
||||
|
||||
pushHandlingWakeLock.unlock()
|
||||
Timber.d("Wakelock timeout reached, stopping FetchPushForegroundService")
|
||||
|
||||
coroutineScope.launch { pushHandlingWakeLock.unlock() }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val stopMutex = Mutex()
|
||||
|
||||
fun startIfNeeded(context: Context) {
|
||||
// Don't start the foreground service if the device is already awake
|
||||
val powerManager = context.getSystemService(POWER_SERVICE) as PowerManager
|
||||
@@ -107,9 +125,34 @@ class FetchPushForegroundService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
fun stop(context: Context) {
|
||||
val intent = Intent(context, FetchPushForegroundService::class.java)
|
||||
context.stopService(intent)
|
||||
suspend fun stop(context: Context) = stopMutex.withLock {
|
||||
val runningServiceInfo = getRunningServiceInfo(context)
|
||||
if (runningServiceInfo != null) {
|
||||
val intent = Intent(context, FetchPushForegroundService::class.java)
|
||||
// If it's still not running in foreground, it means the service is still starting,
|
||||
// so we delay the stop to give it time to start and be set as foreground, otherwise we can crash
|
||||
// with `ForegroundServiceDidNotStartInTimeException`.
|
||||
var isInForeground = runningServiceInfo.foreground
|
||||
withTimeoutOrNull(5.seconds) {
|
||||
while (!isInForeground) {
|
||||
delay(50)
|
||||
val updatedServiceInfo = getRunningServiceInfo(context)
|
||||
if (updatedServiceInfo == null) {
|
||||
Timber.d("FetchPushForegroundService is no longer running, no need to stop it.")
|
||||
return@withTimeoutOrNull
|
||||
}
|
||||
isInForeground = updatedServiceInfo.foreground == true
|
||||
}
|
||||
} ?: Timber.w("FetchPushForegroundService did not start in foreground after 5s, stopping it anyway.")
|
||||
context.stopService(intent)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun getRunningServiceInfo(context: Context): ActivityManager.RunningServiceInfo? {
|
||||
val activityManager = context.getSystemService(ACTIVITY_SERVICE) as ActivityManager
|
||||
return activityManager.getRunningServices(Int.MAX_VALUE)
|
||||
.firstOrNull { it.service.className == FetchPushForegroundService::class.java.name }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ class FakePushHandlingWakeLock(
|
||||
lock.invoke(time)
|
||||
}
|
||||
|
||||
override fun unlock() {
|
||||
override suspend fun unlock() {
|
||||
unlock.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user