From 419aff08d1fb7e81e3d217fc6acd8d465a654375 Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Sun, 5 Oct 2025 16:12:34 +0200 Subject: [PATCH] Notify about foreground service errors rather than crashing Change-Id: I30043d02a4756b9310d1382a1cda015364a235a1 --- .../src/main/res/values/strings_sc.xml | 6 +++ .../app/fdroid/service/GuardAndroidService.kt | 3 +- .../im/vector/app/core/extensions/Service.kt | 47 +++++++++++++++++++ .../notifications/NotificationUtils.kt | 16 +++++++ 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values/strings_sc.xml b/library/ui-strings/src/main/res/values/strings_sc.xml index 148d267d8c..e6ef61aa06 100644 --- a/library/ui-strings/src/main/res/values/strings_sc.xml +++ b/library/ui-strings/src/main/res/values/strings_sc.xml @@ -241,4 +241,10 @@ ⚠️ 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. + Application errors + Cannot sync notifications + Consider setting up UnifiedPush and disabling battery restrictions for SchildiChat in your system settings. + Failed to start foreground service + You may need to exempt SchildiChat from battery restrictions in your system settings. + diff --git a/vector-app/src/fdroid/java/im/vector/app/fdroid/service/GuardAndroidService.kt b/vector-app/src/fdroid/java/im/vector/app/fdroid/service/GuardAndroidService.kt index 3d5cb4bdb4..1f25d2d153 100644 --- a/vector-app/src/fdroid/java/im/vector/app/fdroid/service/GuardAndroidService.kt +++ b/vector-app/src/fdroid/java/im/vector/app/fdroid/service/GuardAndroidService.kt @@ -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 } } diff --git a/vector/src/main/java/im/vector/app/core/extensions/Service.kt b/vector/src/main/java/im/vector/app/core/extensions/Service.kt index 2f56b13e1a..7c4efe4f7c 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/Service.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/Service.kt @@ -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 diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt index 9db264446b..82a2977b36 100755 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt @@ -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? {