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:
parent
2c083a87d4
commit
8239a5ba0a
4 changed files with 53 additions and 10 deletions
|
|
@ -22,5 +22,5 @@ interface PushHandlingWakeLock {
|
||||||
/**
|
/**
|
||||||
* Release the wakelock. If no wakelock is associated with the key, this method does nothing.
|
* 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()
|
count.incrementAndGet()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun unlock() {
|
override suspend fun unlock() {
|
||||||
Timber.d("Releasing wakelock used for push handling.")
|
Timber.d("Releasing wakelock used for push handling.")
|
||||||
FetchPushForegroundService.stop(context)
|
FetchPushForegroundService.stop(context)
|
||||||
if (count.decrementAndGet() <= 0) {
|
if (count.decrementAndGet() <= 0) {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
package io.element.android.libraries.push.impl.push
|
package io.element.android.libraries.push.impl.push
|
||||||
|
|
||||||
|
import android.app.ActivityManager
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
|
@ -25,7 +26,12 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
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.minutes
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
private const val NOTIFICATION_ID = 1001
|
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)
|
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())
|
val notificationCompat = NotificationCompat.Builder(this, notificationChannels.getSilentChannelId())
|
||||||
.setSmallIcon(CommonDrawables.ic_notification)
|
.setSmallIcon(CommonDrawables.ic_notification)
|
||||||
.setContentTitle(getString(CommonStrings.common_android_fetching_notifications_title))
|
.setContentTitle(getString(CommonStrings.common_android_fetching_notifications_title))
|
||||||
|
|
@ -65,6 +73,12 @@ class FetchPushForegroundService : Service() {
|
||||||
.build()
|
.build()
|
||||||
startForeground(NOTIFICATION_ID, notificationCompat)
|
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
|
// 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) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
|
|
@ -86,10 +100,14 @@ class FetchPushForegroundService : Service() {
|
||||||
override fun onTimeout(startId: Int) {
|
override fun onTimeout(startId: Int) {
|
||||||
super.onTimeout(startId)
|
super.onTimeout(startId)
|
||||||
|
|
||||||
pushHandlingWakeLock.unlock()
|
Timber.d("Wakelock timeout reached, stopping FetchPushForegroundService")
|
||||||
|
|
||||||
|
coroutineScope.launch { pushHandlingWakeLock.unlock() }
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private val stopMutex = Mutex()
|
||||||
|
|
||||||
fun startIfNeeded(context: Context) {
|
fun startIfNeeded(context: Context) {
|
||||||
// Don't start the foreground service if the device is already awake
|
// Don't start the foreground service if the device is already awake
|
||||||
val powerManager = context.getSystemService(POWER_SERVICE) as PowerManager
|
val powerManager = context.getSystemService(POWER_SERVICE) as PowerManager
|
||||||
|
|
@ -107,9 +125,34 @@ class FetchPushForegroundService : Service() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stop(context: Context) {
|
suspend fun stop(context: Context) = stopMutex.withLock {
|
||||||
val intent = Intent(context, FetchPushForegroundService::class.java)
|
val runningServiceInfo = getRunningServiceInfo(context)
|
||||||
context.stopService(intent)
|
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)
|
lock.invoke(time)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun unlock() {
|
override suspend fun unlock() {
|
||||||
unlock.invoke()
|
unlock.invoke()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue