diff --git a/app/build.gradle b/app/build.gradle
index bab1da71..ecf7ff50 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -17,8 +17,8 @@ android {
minSdkVersion 26
targetSdkVersion 36
- versionCode 53
- versionName "1.20.0"
+ versionCode 54
+ versionName "1.21.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
diff --git a/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt b/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt
index b6c53447..59daf8e5 100644
--- a/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt
+++ b/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt
@@ -71,6 +71,14 @@ class SubscriberService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG, "onStartCommand executed with startId: $startId")
+
+ // Safety check: ensure we're in foreground state. This handles edge cases where
+ // onCreate() may not have been called or completed before onStartCommand(). See #1520.
+ if (serviceNotification == null) {
+ Log.d(TAG, "onStartCommand: Notification not set, initializing foreground state")
+ initializeForegroundState()
+ }
+
if (intent != null) {
Log.d(TAG, "using an intent with action ${intent.action}")
when (intent.action) {
@@ -90,6 +98,15 @@ class SubscriberService : Service() {
Log.init(this) // Init logs in all entry points
Log.d(TAG, "Subscriber service has been created")
+ initializeForegroundState()
+ }
+
+ /**
+ * Initializes the foreground state by creating the notification channel and notification,
+ * then calling startForeground(). This is called from onCreate() and as a safety fallback
+ * from onStartCommand() if onCreate() didn't complete properly.
+ */
+ private fun initializeForegroundState() {
val title = getString(R.string.channel_subscriber_notification_title)
val text = if (BuildConfig.FIREBASE_AVAILABLE) {
getString(R.string.channel_subscriber_notification_instant_text)
@@ -113,9 +130,10 @@ class SubscriberService : Service() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && e is ForegroundServiceStartNotAllowedException) {
Log.w(TAG, "Cannot start foreground service from background, stopping: ${e.message}")
stopSelf()
- return
} else {
- throw e
+ Log.w(TAG, "Failed to start foreground: ${e.message}")
+ // Don't rethrow: let the service continue and hope for the best,
+ // or Android will kill it. Either way, we don't crash the app (see #1520).
}
}
}
@@ -134,7 +152,6 @@ class SubscriberService : Service() {
}
Log.d(TAG, "Starting the foreground service task")
isServiceStarted = true
- saveServiceState(this, ServiceState.STARTED)
wakeLock = (getSystemService(POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG)
}
@@ -164,7 +181,6 @@ class SubscriberService : Service() {
}
isServiceStarted = false
- saveServiceState(this, ServiceState.STOPPED)
}
private fun refreshConnections() {
@@ -238,7 +254,7 @@ class SubscriberService : Service() {
}
// Update foreground service notification popup
- if (connections.size > 0) {
+ if (connections.isNotEmpty()) {
val title = getString(R.string.channel_subscriber_notification_title)
val text = if (BuildConfig.FIREBASE_AVAILABLE) {
when (instantSubscriptions.size) {
@@ -357,11 +373,6 @@ class SubscriberService : Service() {
STOP
}
- enum class ServiceState {
- STARTED,
- STOPPED,
- }
-
companion object {
const val TAG = "NtfySubscriberService"
const val SERVICE_START_WORKER_VERSION = BuildConfig.VERSION_CODE
@@ -372,20 +383,5 @@ class SubscriberService : Service() {
private const val NOTIFICATION_GROUP_ID = "io.heckel.ntfy.NOTIFICATION_GROUP_SERVICE"
private const val NOTIFICATION_SERVICE_ID = 2586
private const val NOTIFICATION_RECEIVED_WAKELOCK_TIMEOUT_MILLIS = 10*60*1000L /*10 minutes*/
- private const val SHARED_PREFS_ID = "SubscriberService"
- private const val SHARED_PREFS_SERVICE_STATE = "ServiceState"
-
- fun saveServiceState(context: Context, state: ServiceState) {
- val sharedPrefs = context.getSharedPreferences(SHARED_PREFS_ID, MODE_PRIVATE)
- sharedPrefs.edit {
- putString(SHARED_PREFS_SERVICE_STATE, state.name)
- }
- }
-
- fun readServiceState(context: Context): ServiceState {
- val sharedPrefs = context.getSharedPreferences(SHARED_PREFS_ID, MODE_PRIVATE)
- val value = sharedPrefs.getString(SHARED_PREFS_SERVICE_STATE, ServiceState.STOPPED.name)
- return ServiceState.valueOf(value!!)
- }
}
}
diff --git a/app/src/main/java/io/heckel/ntfy/service/SubscriberServiceManager.kt b/app/src/main/java/io/heckel/ntfy/service/SubscriberServiceManager.kt
index f86110c3..c51d3039 100644
--- a/app/src/main/java/io/heckel/ntfy/service/SubscriberServiceManager.kt
+++ b/app/src/main/java/io/heckel/ntfy/service/SubscriberServiceManager.kt
@@ -52,7 +52,14 @@ class SubscriberServiceManager(private val context: Context) {
Log.d(TAG, "ServiceStartWorker: Starting foreground service (work ID: ${id})")
Intent(context, SubscriberService::class.java).also {
it.action = SubscriberService.Action.START.name
- ContextCompat.startForegroundService(context, it)
+ try {
+ ContextCompat.startForegroundService(context, it)
+ } catch (e: Exception) {
+ // ForegroundServiceDidNotStartInTimeException or other exceptions can occur
+ // due to race conditions or system constraints. We log and continue;
+ // the service will be retried on the next refresh() call.
+ Log.w(TAG, "ServiceStartWorker: Failed to start foreground service: ${e.message}")
+ }
}
} else {
// No instant subscriptions, stop the service using stopService()
diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml
index a3deb0bd..a3db2ccf 100644
--- a/app/src/main/res/values-bg/strings.xml
+++ b/app/src/main/res/values-bg/strings.xml
@@ -400,4 +400,19 @@
Добавяне на заглавка
Добавяне на заглавка за сървъра
Заглавките ще бъдат изпращани с всяка заявка по HTTP. Всеки сървър може да има свой набор от заявки.
+ Не може да бъде добавен потребител ако за сървъра е зададена потребителска заглавка Authorization
+ Добавяна на заглавка по избор
+ Променяне на заглавка по избор
+ Адрес на услугата
+ Име на заглавката (напр. CF-Access-Client-Id)
+ Стойност на заглавката (напр. 9f3c2e4a1b2d4e)
+ Добавяне на заглавка на HTTP по избор, която да бъде изпращана с всяка заявка към избрания сървър.
+ Можете да променяте името и стойността на заглавката или да я премахнете.
+ Името на заглавката съдържа неприемливи знаци
+ Тази заглавка е запазена и се задава от ntfy
+ Не може да бъде добавена заглавка Authorization ако за сървъра е зададен потребител
+ Заглавка с това име вече има за този сървър
+ Добавяне
+ Запазване
+ Премахване
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index d581d2b1..dfe04752 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -349,4 +349,70 @@
動態色彩
使用系統動態色彩
使用 ntfy 主題色彩
+ 發佈至 %1$s
+ 標題
+ 例如:有人在門口
+ 訊息
+ 標籤
+ 例如:warning、skull
+ 優先順序
+ 發佈
+ 無法發佈訊息:%1$s
+ 無法發佈訊息:%1$s(代碼 %2$d)
+ 訊息已發佈
+ 上傳中:%1$s(%2$s / %3$s)
+ 上傳已取消
+ 標題
+ 標籤
+ 優先順序
+ 點擊網址
+ 電子郵件
+ 延遲
+ Markdown
+ 透過網址附加
+ 附加本機檔案
+ 電話撥打
+ 點擊網址
+ 例如:https://example.com/alerts/1234
+ 電子郵件
+ 例如:phil@example.com
+ 延遲傳送
+ 例如:30m、1h、today 9pm(僅支援英文)
+ 附件網址
+ 例如:https://example.com/flowers.jpg
+ 附件檔名
+ 例如:lilies.jpg
+ 電話撥打
+ 例如:+1234567890
+ 在此輸入訊息
+ 發佈訊息
+ 更多選項
+ 發佈通知
+ 語言
+ 使用系統預設
+ 系統預設
+ 顯示訊息列
+ 在主題檢視底部顯示訊息列
+ 在主題檢視底部顯示發佈按鈕
+ 自訂標頭
+ 定義隨每次請求一併傳送的自訂 HTTP 標頭,例如當你的 ntfy 伺服器位於需要驗證的代理或通道之後。
+ 新增標頭
+ 為伺服器新增標頭
+ 標頭會隨每次 HTTP 請求一併傳送。每個 ntfy 伺服器都可以有自己的一組自訂標頭。
+ 若此伺服器已設定自訂 Authorization 標頭,則無法新增使用者
+ 新增自訂標頭
+ 編輯自訂標頭
+ 服務網址
+ 標頭名稱(例如:CF-Access-Client-Id)
+ 標頭值(例如:9f3c2e4a1b2d4e)
+ 新增一個會隨每次請求傳送至指定伺服器的自訂 HTTP 標頭。
+ 你可以編輯所選標頭的名稱或值,或將其刪除。
+ 標頭名稱包含無效字元
+ 此標頭為保留項目,並由 ntfy 設定
+ 若此伺服器已設定使用者,則無法新增 Authorization 標頭
+ 此伺服器已存在相同名稱的標頭
+ 新增
+ 儲存
+ 刪除
+ 如需範例與所有發佈功能的詳細說明,請參考 說明文件。
diff --git a/fastlane/metadata/android/en-US/changelog/NEXT.txt b/fastlane/metadata/android/en-US/changelog/54.txt
similarity index 93%
rename from fastlane/metadata/android/en-US/changelog/NEXT.txt
rename to fastlane/metadata/android/en-US/changelog/54.txt
index 51527f9a..36434ad4 100644
--- a/fastlane/metadata/android/en-US/changelog/NEXT.txt
+++ b/fastlane/metadata/android/en-US/changelog/54.txt
@@ -10,3 +10,4 @@ Maintenance + bug fixes:
* Add support for (technically incorrect) 'image/jpg' MIME type (ntfy-android#142, thanks to @Murilobeluco)
* Unify "copy to clipboard" notifications, use Android 13 style (ntfy-android#61, thanks to @thgoebel)
* Fix crash in user add dialog (onAddUser)
+* Fix ForegroundServiceDidNotStartInTimeException (attempt 2, see #1520)