diff --git a/app/src/main/java/protect/card_locker/wearos/BluetoothServerService.kt b/app/src/main/java/protect/card_locker/wearos/BluetoothServerService.kt
index 17670195b3..7a3e4e9b54 100644
--- a/app/src/main/java/protect/card_locker/wearos/BluetoothServerService.kt
+++ b/app/src/main/java/protect/card_locker/wearos/BluetoothServerService.kt
@@ -1,5 +1,6 @@
package protect.card_locker.wearos
+import android.Manifest
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
@@ -7,11 +8,15 @@ import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothServerSocket
import android.bluetooth.BluetoothSocket
+import android.content.Context
import android.content.Intent
+import android.content.pm.PackageManager
import android.os.Build
import android.os.IBinder
import android.util.Log
+import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
import org.json.JSONArray
import org.json.JSONObject
import protect.card_locker.DBHelper
@@ -33,13 +38,67 @@ class BluetoothServerService : Service() {
private const val TAG = "CatimaBtServer"
private const val NOTIFICATION_ID = NotificationInfo.WearBluetooth.NOTIFICATION_ID
private const val CHANNEL_ID = NotificationInfo.WearBluetooth.CHANNEL_ID
+
+ fun showBluetoothServerError(context: Context, error: String) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ 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(error)
+ .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
+ )
+ }
+ }
}
private var serverThread: AcceptThread? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ // First, create the foreground service with notification
+ // Android is very picky about requiring the foreground service to be started quickly and will crash the entire app if that doesn't happen
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val channel = NotificationChannel(
+ CHANNEL_ID,
+ getString(R.string.wear_bt_channel_name),
+ NotificationManager.IMPORTANCE_MIN
+ ).apply { setShowBadge(false) }
+ getSystemService(NotificationManager::class.java).createNotificationChannel(channel)
+ }
+ val notification = NotificationCompat.Builder(this, CHANNEL_ID)
+ .setContentTitle(getString(R.string.wear_bt_notification_title))
+ .setSmallIcon(R.drawable.ic_notification_sync)
+ .setPriority(NotificationCompat.PRIORITY_MIN)
+ .setSilent(true)
+ .build()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ startForeground(NOTIFICATION_ID, notification,
+ android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE)
+ } else {
+ startForeground(NOTIFICATION_ID, notification)
+ }
+
+ // After we have a running notification, try error handling and stopping the service if we have issues
if (!BluetoothPermissionHelper.isBluetoothConnectGranted(this)) {
Log.w(TAG, "BLUETOOTH_CONNECT permission not granted, stopping")
+ showBluetoothServerError(this, getString(R.string.wear_sync_permission_required))
stopSelf()
return START_NOT_STICKY
}
@@ -56,36 +115,12 @@ class BluetoothServerService : Service() {
return START_STICKY
}
- startForegroundWithNotification()
serverThread?.cancel()
serverThread = AcceptThread(adapter).also { it.start() }
Log.d(TAG, "Bluetooth server started")
return START_STICKY
}
- private fun startForegroundWithNotification() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- val channel = NotificationChannel(
- CHANNEL_ID,
- getString(R.string.wear_bt_channel_name),
- NotificationManager.IMPORTANCE_MIN
- ).apply { setShowBadge(false) }
- getSystemService(NotificationManager::class.java).createNotificationChannel(channel)
- }
- val notification = NotificationCompat.Builder(this, CHANNEL_ID)
- .setContentTitle(getString(R.string.wear_bt_notification_title))
- .setSmallIcon(R.drawable.ic_notification_sync)
- .setPriority(NotificationCompat.PRIORITY_MIN)
- .setSilent(true)
- .build()
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
- startForeground(NOTIFICATION_ID, notification,
- android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE)
- } else {
- startForeground(NOTIFICATION_ID, notification)
- }
- }
-
override fun onDestroy() {
serverThread?.cancel()
serverThread = null
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 8498dc0ee7..9e91025c1d 100644
--- a/app/src/main/java/protect/card_locker/wearos/WearSyncServiceManager.kt
+++ b/app/src/main/java/protect/card_locker/wearos/WearSyncServiceManager.kt
@@ -2,22 +2,15 @@ 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 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
@@ -128,30 +121,7 @@ object WearSyncServiceManager {
// 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
- )
- }
+ BluetoothServerService.showBluetoothServerError(context, context.getString(R.string.android_blocked_startup))
}
} else {
ContextCompat.startForegroundService(
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a91c19d123..1fa6b3c720 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -395,4 +395,5 @@
Smartwatch support
Failed to start Wear OS sync service
Wear OS sync critical error
+ Android blocked starting the Wear OS service