From bfa79a6f68d6f323e5006599fd4cc69dae697a19 Mon Sep 17 00:00:00 2001 From: Sylvia van Os Date: Tue, 11 Aug 2026 20:37:49 +0200 Subject: [PATCH] Notify on foreground service start failure --- .../protect/card_locker/NotificationInfo.kt | 2 + .../wearos/WearSyncServiceManager.kt | 57 +++++++++++++++++- .../ic_notification_error.xml | 30 +++++++++ .../drawable-hdpi/ic_notification_error.png | Bin 0 -> 752 bytes .../drawable-mdpi/ic_notification_error.png | Bin 0 -> 478 bytes .../drawable-xhdpi/ic_notification_error.png | Bin 0 -> 919 bytes .../drawable-xxhdpi/ic_notification_error.png | Bin 0 -> 1549 bytes app/src/main/res/values/strings.xml | 2 + 8 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 app/src/main/res/drawable-anydpi-v24/ic_notification_error.xml create mode 100644 app/src/main/res/drawable-hdpi/ic_notification_error.png create mode 100644 app/src/main/res/drawable-mdpi/ic_notification_error.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_notification_error.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_notification_error.png diff --git a/app/src/main/java/protect/card_locker/NotificationInfo.kt b/app/src/main/java/protect/card_locker/NotificationInfo.kt index 356e11fb7..bd2d8750f 100644 --- a/app/src/main/java/protect/card_locker/NotificationInfo.kt +++ b/app/src/main/java/protect/card_locker/NotificationInfo.kt @@ -5,5 +5,7 @@ object NotificationInfo { const val NOTIFICATION_ID = 1001 const val CHANNEL_ID = "catima_wear_bt" const val PAIRING_CHANNEL_ID = "catima_wear_bt_pairing" + const val CRITICAL_ERROR_NOTIFICATION_ID = 1002 + const val CRITICAL_ERROR_CHANNEL_ID = "catima_wear_bt_critical_error" } } diff --git a/app/src/main/java/protect/card_locker/wearos/WearSyncServiceManager.kt b/app/src/main/java/protect/card_locker/wearos/WearSyncServiceManager.kt index 8d5ba1ef3..8498dc0ee 100644 --- a/app/src/main/java/protect/card_locker/wearos/WearSyncServiceManager.kt +++ b/app/src/main/java/protect/card_locker/wearos/WearSyncServiceManager.kt @@ -1,13 +1,23 @@ package protect.card_locker.wearos import android.Manifest +import android.app.ForegroundServiceStartNotAllowedException +import android.app.NotificationChannel +import android.app.NotificationManager import android.content.Context import android.content.Intent -import androidx.core.content.ContextCompat +import android.content.pm.PackageManager +import android.os.Build +import android.util.Log import androidx.activity.result.ActivityResultCaller import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.app.ActivityCompat +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat import androidx.core.content.edit import androidx.preference.PreferenceManager +import protect.card_locker.NotificationInfo import protect.card_locker.R import protect.card_locker.preferences.Settings import protect.card_locker.shared.BluetoothPermissionHelper @@ -55,6 +65,8 @@ class WearSyncPermissionRequester( } object WearSyncServiceManager { + private const val TAG = "CatimaWearSyncServiceManager" + fun synchronize(context: Context, requestPermission: (() -> Unit)? = null) { if (!Settings(context).wearSyncEnabled) { stop(context) @@ -105,7 +117,48 @@ object WearSyncServiceManager { } private fun start(context: Context) { - ContextCompat.startForegroundService(context, Intent(context, BluetoothServerService::class.java)) + // Exception can only throw on API level 31 and up + if (Build.VERSION.SDK_INT >= 31) { + try { + ContextCompat.startForegroundService( + context, + Intent(context, BluetoothServerService::class.java) + ) + } catch (e: ForegroundServiceStartNotAllowedException) { + // We naively assume we have enough permissions to show a notification as the service should never try to start + // before the user gave notification access anyway + Log.d(TAG, "Foreground service failed to launch: $e") + val channel = NotificationChannel( + NotificationInfo.WearBluetooth.CRITICAL_ERROR_CHANNEL_ID, + context.getString(R.string.wear_bt_critical_error_channel_name), + NotificationManager.IMPORTANCE_HIGH + ).apply { setShowBadge(false) } + context.getSystemService(NotificationManager::class.java).createNotificationChannel(channel) + val notification = NotificationCompat.Builder(context, NotificationInfo.WearBluetooth.CRITICAL_ERROR_CHANNEL_ID) + .setSmallIcon(R.drawable.ic_notification_error) + .setContentTitle(context.getString(R.string.wear_bt_failed_foreground_notification_title)) + .setContentText(e.message) + .build() + with(NotificationManagerCompat.from(context)) { + if (ActivityCompat.checkSelfPermission( + context, + Manifest.permission.POST_NOTIFICATIONS + ) != PackageManager.PERMISSION_GRANTED + ) { + return@with + } + notify( + NotificationInfo.WearBluetooth.CRITICAL_ERROR_NOTIFICATION_ID, + notification + ) + } + } + } else { + ContextCompat.startForegroundService( + context, + Intent(context, BluetoothServerService::class.java) + ) + } } private fun stop(context: Context) { diff --git a/app/src/main/res/drawable-anydpi-v24/ic_notification_error.xml b/app/src/main/res/drawable-anydpi-v24/ic_notification_error.xml new file mode 100644 index 000000000..0775b54d8 --- /dev/null +++ b/app/src/main/res/drawable-anydpi-v24/ic_notification_error.xml @@ -0,0 +1,30 @@ + + + + + + diff --git a/app/src/main/res/drawable-hdpi/ic_notification_error.png b/app/src/main/res/drawable-hdpi/ic_notification_error.png new file mode 100644 index 0000000000000000000000000000000000000000..f7053daf93ba2868cf379fc26b5a19e0e24fbec5 GIT binary patch literal 752 zcmV>vCu*+ zVj+n}@dbhiJ}~BUzMS3b?A+aZ_Tf$852i?F=G&9Kdw0*Kr{{mc%yvKrp%c&r=prdPDP_HJWPy^C1Nn zt;ENK%q(^0)K`3FGCSp|W|pvU(_=<7lZS<`d&;c(c_4R;I?kYGJT%1#^^35IXD9{P zpVXUD-vi7I^^36j3%W+V>svjx4N?o~GUBl||3D9GQ0?kAd5 zzZ1xRrQ?pWv6qa}_n@ZmF~K1=GGBY@bQM0P@z*}b{Ck0Q36l++`X0F5F;D=a>nv>E zT$|J{!ot=!CHK*x57*@(-#~ISh5-{#jXhHovklRI_-RkFo+;8eU zQNIPuehNCiTa4~~Eu7ue`g_H~UT7|JgxKp50;i?m+s^1lEr!pq_PzQQ&E;b4e}!*5 zYcI;X*1niho4FPiUR4v7)!EB zsq3;3lrQTGTRbhc@Wk#$f1>g@3V><&G8I^!_zC&_-w@&>{5MX^A{QRlhF+g&GaKL-W4lL1$5t4-pvgoel_FRLuRiMR%CyT0U=spkW35 zG^yqAiqMIv*dy0N!f&$U!;%HcY!n*s%hW0RT!BAFoe~FYW`0vYLUJ6SAp5b-Dw{WE zHmMt-ORb}19dKlwYc>ND)Q!-k)XO?xGL+a1jCHFqlK+75P+~Jsr*4GgKcF5;YzC&N zn?jO*#bNI=l05Nk*t{~Y@0Xkx>^Qbvmu#+>**&!fl01yN-e;MEFB=9kH6m*!r4-a*o@>jmHONcweM z^PLDSK+iPr1~nzVaP;SW%K}d_XgP!O1q~14+|TDil1pDNcD>*|x6r2axI6xoU+q;# UZi2b*ZU6uP07*qoM6N<$g8Jj!djJ3c literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_notification_error.png b/app/src/main/res/drawable-xhdpi/ic_notification_error.png new file mode 100644 index 0000000000000000000000000000000000000000..1023050452490bad60f4987ecca9f20c9423ea96 GIT binary patch literal 919 zcmV;I18Dq-P)_-iMieBX#E()%3rjT?K@?O_5fo8DNGGUR1Vlkm zA;wbl4`^dFCPd=#JzKwY*}6 z7@${$q*-=Zz)R#j4B&beK9d<@hF$_ofAqK0%L7BWo(Hw{Fh}&$TR@8wkSA5`^zwk5 zM(==McljPF`9SXmjJUKQoVl=%o_^!?+%T+WRu|DA*GC7dk_yRcJ3~xj8y%ci=IdR$<5Jqi+z7o8)W6P)w}+tqE7+4$eKF@~;UmPK z*7)W0cty@Q*Yn`BRZ|r?&IG*$v^W6S*-0-C4B>hn)V7;BqK{qz2{Y^v9rUtb0N1ln z%L-D9!d zSEN=QY#riIi{1yq{*Bdej43e}=$3`0fBtob=;lI=_m~tjW(7{AiD#)^Bu6Z8hkg)N z|9E|?HTXW^c&}<3GAk)|ygrZBK4y3P&F&I=vQW?ExVIMf*;5Dwp5}xUzN@ZU^!(X_ z^4qRi`YRkM^m)7{7JU&>?@=qt57kP}Egg#RWPuseHBQ)VcLPHTX{n`~^qKwYvd*JI??B002ovPDHLkV1n7VuDk#M literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_notification_error.png b/app/src/main/res/drawable-xxhdpi/ic_notification_error.png new file mode 100644 index 0000000000000000000000000000000000000000..4b56ba44384acaa35735bd92aaa9559906481c7c GIT binary patch literal 1549 zcmV+o2J-odP)co_OoZLIjueOO?|)QO?JL{-osjZ_L?`p()Ph=&1Zwg8$4{(eA;ESZ~kj{Yxcv%oUi!? z1B+iw`zOsMxEZ*yqHkWM&1ikS!h&w6 z{Vj#|iPdte*4a65m*h{RxH|k!`+&BK_6+T@%QjJzI>^?kLY%4k9K=WY+RLO>KtCO{P!oA< zQ+*B(zNP9Bs!auY=paKAdAzCG9E2YaA5v{9&|e4rqRBG$t2PJWXIr~fn+o*TLH0lXF#@?N`Zk!{k&EdhXRGsm&YKIQOkA%KdZ4M8s< z+MPEvhS5Wg8WR;W)DxEE1E{Gj?apm@+crn)vDzIkD<>Pw;1MH)reqCQPw ztm`#6xlVOWVXQ40T)04eDnZB3G?H+N`ZNXZ@@vnl%vR~~rZCpYV--*Gsrpog&dW8L z@CT-N$K4jIEvqrcGWDwk1Kg_-g91zS33z8NwxaGN*F zwgJ~4m_xwLbjz^ar0dkTYgofsSuJIxovAb6T0~~p9=E7(S1|W&T1)}fhPCRO1O~oD zvjzAL9ga(u3-N7(J{PKgQc(SMD4YB}iK_QE4Y1g>r?OmCxI$x01-RFus=P2V;t9m| z-PFWl!F7IBtk#VhXR0vaDy^af`Wear OS companion Catima Wear OS sync active Smartwatch support + Failed to start Wear OS sync service + Wear OS sync critical error