Notify about foreground service errors rather than crashing

Change-Id: I30043d02a4756b9310d1382a1cda015364a235a1
This commit is contained in:
SpiritCroc 2025-10-05 16:12:34 +02:00
parent 11a1a2072c
commit 419aff08d1
4 changed files with 71 additions and 1 deletions

View File

@ -241,4 +241,10 @@
<string name="settings_integrations_scalar_warning">⚠️ This setting by default (unless overridden by your homeserver\'s configuration) enables access to \"scalar\", Element\'s integration manager which is unfortunately proprietary, i.e. its source code is not open and can not be checked by the public or the SchildiChat developers.</string>
<string name="notification_channel_app_errors">Application errors</string>
<string name="notification_sync_service_failed_title">Cannot sync notifications</string>
<string name="notification_sync_service_failed_summary">Consider setting up UnifiedPush and disabling battery restrictions for SchildiChat in your system settings.</string>
<string name="notification_foreground_service_failed_title">Failed to start foreground service</string>
<string name="notification_foreground_service_failed_summary">You may need to exempt SchildiChat from battery restrictions in your system settings.</string>
</resources>

View File

@ -8,6 +8,7 @@ package im.vector.app.fdroid.service
import android.content.Intent
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.extensions.startForegroundCompat
import im.vector.app.core.services.VectorAndroidService
import im.vector.app.features.notifications.NotificationUtils
import im.vector.lib.strings.CommonStrings
@ -27,7 +28,7 @@ class GuardAndroidService : VectorAndroidService() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notificationSubtitleRes = CommonStrings.notification_listening_for_notifications
val notification = notificationUtils.buildForegroundServiceNotification(notificationSubtitleRes, false)
startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification)
startForegroundCompat(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification, errorNotificationTitle = getString(CommonStrings.notification_sync_service_failed_title), errorNotificationSummary = getString(CommonStrings.notification_sync_service_failed_summary))
return START_STICKY
}
}

View File

@ -7,12 +7,59 @@
package im.vector.app.core.extensions
import android.Manifest
import android.app.ForegroundServiceStartNotAllowedException
import android.app.Notification
import android.app.Service
import android.content.pm.PackageManager
import android.content.pm.ServiceInfo
import android.os.Build
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import im.vector.app.R
import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.themes.ThemeUtils
import im.vector.lib.strings.CommonStrings
import timber.log.Timber
/** SC wrapper around [startForegroundCompatUpstream] which catches [ForegroundServiceStartNotAllowedException].
* Come on Element, don't you care about these crashes spamming your rageshake inbox from FDroid users?
* */
fun Service.startForegroundCompat(
id: Int,
notification: Notification,
provideForegroundServiceType: (() -> Int)? = null,
errorNotificationTitle: String = getString(CommonStrings.notification_foreground_service_failed_title),
errorNotificationSummary: String = getString(CommonStrings.notification_foreground_service_failed_summary),
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
try {
startForegroundCompatUpstream(id, notification, provideForegroundServiceType)
Timber.tag("SchildiService").d("Started foreground service $id successfully on >= S")
} catch (e: ForegroundServiceStartNotAllowedException) {
Timber.tag("SchildiService").e(e, "Failed to start foreground service")
val notificationManager = NotificationManagerCompat.from(this)
val errorNotification = NotificationCompat.Builder(this, NotificationUtils.SC_APP_ERRORS_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_status_bar_sc)
.setContentTitle(errorNotificationTitle)
.setContentText(errorNotificationSummary)
.setColor(ThemeUtils.getColor(this, android.R.attr.colorPrimary))
.setCategory(NotificationCompat.CATEGORY_ERROR)
.build()
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
Timber.tag("SchildiService").w("Not allowed to notify.")
} else {
notificationManager.notify("FAILED_FG_SERVICE_TAG", id, errorNotification)
}
}
} else {
startForegroundCompatUpstream(id, notification, provideForegroundServiceType)
Timber.tag("SchildiService").d("Started foreground service $id successfully on < S")
}
}
fun Service.startForegroundCompatUpstream(
id: Int,
notification: Notification,
provideForegroundServiceType: (() -> Int)? = null

View File

@ -101,6 +101,8 @@ class NotificationUtils @Inject constructor(
const val SILENT_NOTIFICATION_CHANNEL_ID = "DEFAULT_SILENT_NOTIFICATION_CHANNEL_ID_V2"
private const val CALL_NOTIFICATION_CHANNEL_ID = "CALL_NOTIFICATION_CHANNEL_ID_V2"
const val SC_APP_ERRORS_CHANNEL_ID = "SC_APP_ERRORS_CHANNEL_ID"
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O)
fun supportNotificationChannels() = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
@ -208,6 +210,20 @@ class NotificationUtils @Inject constructor(
enableLights(true)
lightColor = accentColor
})
// SC
notificationManager.createNotificationChannel(
NotificationChannel(
SC_APP_ERRORS_CHANNEL_ID,
stringProvider.getString(CommonStrings.notification_channel_app_errors).ifEmpty { "Application errors" },
NotificationManager.IMPORTANCE_DEFAULT
)
.apply {
description = stringProvider.getString(CommonStrings.notification_channel_app_errors)
setSound(null, null)
enableLights(false)
lightColor = accentColor
})
}
fun getChannel(channelId: String): NotificationChannel? {