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 000000000..f7053daf9
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_notification_error.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_notification_error.png b/app/src/main/res/drawable-mdpi/ic_notification_error.png
new file mode 100644
index 000000000..9cf5563ac
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_notification_error.png differ
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 000000000..102305045
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_notification_error.png differ
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 000000000..4b56ba443
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_notification_error.png differ
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b18f044ab..a91c19d12 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -393,4 +393,6 @@
Wear OS companion
Catima Wear OS sync active
Smartwatch support
+ Failed to start Wear OS sync service
+ Wear OS sync critical error