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