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