Fixing ForegroundServiceDidNotStartInTimeException (#1349)

(cherry picked from commit a5d235e559)

Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
This commit is contained in:
JRoberts
2022-11-14 14:02:04 +04:00
committed by GitHub
parent b1d8600215
commit 07e8c1d76e
5 changed files with 42 additions and 27 deletions

View File

@@ -10,7 +10,6 @@ import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.work.*
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.onboarding.OnboardingStage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -20,7 +19,6 @@ import kotlinx.coroutines.withContext
class SimplexService: Service() {
private var wakeLock: PowerManager.WakeLock? = null
private var isServiceStarted = false
private var isStartingService = false
private var notificationManager: NotificationManager? = null
private var serviceNotification: Notification? = null
@@ -48,11 +46,32 @@ class SimplexService: Service() {
notificationManager = createNotificationChannel()
serviceNotification = createNotification(title, text)
startForeground(SIMPLEX_SERVICE_ID, serviceNotification)
/**
* The reason [stopAfterStart] exists is because when the service is not called [startForeground] yet, and
* we call [stopSelf] on the same service, [ForegroundServiceDidNotStartInTimeException] will be thrown.
* To prevent that, we can call [stopSelf] only when the service made [startForeground] call
* */
if (stopAfterStart) {
stopForeground(true)
stopSelf()
} else {
isServiceStarted = true
}
}
override fun onDestroy() {
Log.d(TAG, "Simplex service destroyed")
stopService()
try {
wakeLock?.let {
while (it.isHeld) it.release() // release all, in case acquired more than once
}
wakeLock = null
} catch (e: Exception) {
Log.d(TAG, "Exception while releasing wakelock: ${e.message}")
}
isServiceStarted = false
stopAfterStart = false
saveServiceState(this, ServiceState.STOPPED)
// If notification service is enabled and battery optimization is disabled, restart the service
if (SimplexApp.context.allowToStartServiceAfterAppExit())
@@ -62,7 +81,7 @@ class SimplexService: Service() {
private fun startService() {
Log.d(TAG, "SimplexService startService")
if (isServiceStarted || isStartingService) return
if (wakeLock != null || isStartingService) return
val self = this
isStartingService = true
withApi {
@@ -73,10 +92,9 @@ class SimplexService: Service() {
if (chatDbStatus != DBMigrationResult.OK) {
Log.w(chat.simplex.app.TAG, "SimplexService: problem with the database: $chatDbStatus")
showPassphraseNotification(chatDbStatus)
stopService()
safeStopService(self)
return@withApi
}
isServiceStarted = true
saveServiceState(self, ServiceState.STARTED)
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG).apply {
@@ -89,22 +107,6 @@ class SimplexService: Service() {
}
}
private fun stopService() {
Log.d(TAG, "Stopping foreground service")
try {
wakeLock?.let {
while (it.isHeld) it.release() // release all, in case acquired more than once
}
wakeLock = null
stopForeground(true)
stopSelf()
} catch (e: Exception) {
Log.d(TAG, "Service stopped without being started: ${e.message}")
}
isServiceStarted = false
saveServiceState(this, ServiceState.STOPPED)
}
private fun createNotificationChannel(): NotificationManager? {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
@@ -235,6 +237,9 @@ class SimplexService: Service() {
private const val SHARED_PREFS_SERVICE_STATE = "SIMPLEX_SERVICE_STATE"
private const val WORK_NAME_ONCE = "ServiceStartWorkerOnce"
private var isServiceStarted = false
private var stopAfterStart = false
fun scheduleStart(context: Context) {
Log.d(TAG, "Enqueuing work to start subscriber service")
val workManager = WorkManager.getInstance(context)
@@ -244,7 +249,17 @@ class SimplexService: Service() {
suspend fun start(context: Context) = serviceAction(context, Action.START)
fun stop(context: Context) = context.stopService(Intent(context, SimplexService::class.java))
/**
* If there is a need to stop the service, use this function only. It makes sure that the service will be stopped without an
* exception related to foreground services lifecycle
* */
fun safeStopService(context: Context) {
if (isServiceStarted) {
context.stopService(Intent(context, SimplexService::class.java))
} else {
stopAfterStart = true
}
}
private suspend fun serviceAction(context: Context, action: Action) {
Log.d(TAG, "SimplexService serviceAction: ${action.name}")

View File

@@ -1220,7 +1220,7 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
chatModel.notificationsMode.value = NotificationsMode.OFF
SimplexService.StartReceiver.toggleReceiver(false)
MessagesFetcherWorker.cancelAll()
SimplexService.stop(SimplexApp.context)
SimplexService.safeStopService(SimplexApp.context)
} else {
// show battery optimization notice
showBGServiceNoticeIgnoreOptimization(mode)

View File

@@ -63,7 +63,7 @@ fun ActiveCallView(chatModel: ChatModel) {
DisposableEffect(Unit) {
onDispose {
// Stop it when call ended
if (!ntfModeService) SimplexService.stop(SimplexApp.context)
if (!ntfModeService) SimplexService.safeStopService(SimplexApp.context)
// Clear selected communication device to default value after we changed it in call
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val am = SimplexApp.context.getSystemService(Context.AUDIO_SERVICE) as AudioManager

View File

@@ -409,7 +409,7 @@ private fun stopChat(m: ChatModel, runChat: MutableState<Boolean>, context: Cont
m.controller.apiStopChat()
runChat.value = false
m.chatRunning.value = false
SimplexService.stop(context)
SimplexService.safeStopService(context)
MessagesFetcherWorker.cancelAll()
} catch (e: Error) {
runChat.value = true

View File

@@ -57,7 +57,7 @@ fun NotificationsSettingsView(
if (mode == NotificationsMode.SERVICE)
SimplexService.start(SimplexApp.context)
else
SimplexService.stop(SimplexApp.context)
SimplexService.safeStopService(SimplexApp.context)
}
if (mode != NotificationsMode.PERIODIC) {