diff --git a/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/Constants.kt b/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/Constants.kt index b8d972ff3..a2e7fccb0 100644 --- a/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/Constants.kt +++ b/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/Constants.kt @@ -1,8 +1,9 @@ package breez_sdk_notification object Constants { + const val SERVICE_TIMEOUT_MS = 3 * 60 * 1000L const val SHUTDOWN_DELAY_MS = 60 * 1000L - + // Notification Channels const val NOTIFICATION_CHANNEL_FOREGROUND_SERVICE = "FOREGROUND_SERVICE" const val NOTIFICATION_CHANNEL_LNURL_PAY = "LNURL_PAY" diff --git a/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/ForegroundService.kt b/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/ForegroundService.kt index c28bcd833..c8d4730fd 100644 --- a/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/ForegroundService.kt +++ b/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/ForegroundService.kt @@ -16,6 +16,7 @@ import breez_sdk_notification.Constants.MESSAGE_TYPE_LNURL_PAY_INFO import breez_sdk_notification.Constants.MESSAGE_TYPE_LNURL_PAY_INVOICE import breez_sdk_notification.Constants.MESSAGE_TYPE_PAYMENT_RECEIVED import breez_sdk_notification.Constants.NOTIFICATION_ID_FOREGROUND_SERVICE +import breez_sdk_notification.Constants.SERVICE_TIMEOUT_MS import breez_sdk_notification.Constants.SHUTDOWN_DELAY_MS import breez_sdk_notification.NotificationHelper.Companion.notifyForegroundService import breez_sdk_notification.job.Job @@ -53,6 +54,7 @@ abstract class ForegroundService : SdkForegroundService, EventListener, Service( return null } + /** Called by a Job to signal that it is complete. */ override fun onFinished(job: Job) { synchronized(this) { logger.log(TAG, "Job has finished: $job", "DEBUG") @@ -62,14 +64,27 @@ abstract class ForegroundService : SdkForegroundService, EventListener, Service( } /** Stop the service */ + private val serviceTimeoutHandler = Handler(Looper.getMainLooper()) + private val serviceTimeoutRunnable: Runnable = Runnable { + logger.log(TAG, "Reached service timeout...", "DEBUG") + synchronized(this) { + jobs.forEach { job -> job.onShutdown() } + } + + shutdown() + } + private val shutdownHandler = Handler(Looper.getMainLooper()) private val shutdownRunnable: Runnable = Runnable { logger.log(TAG, "Reached scheduled shutdown...", "DEBUG") shutdown() } - private fun resetShutdown() { + private fun resetDelayedCallbacks() { + serviceTimeoutHandler.removeCallbacksAndMessages(null) shutdownHandler.removeCallbacksAndMessages(null) + + shutdownHandler.postDelayed(serviceTimeoutRunnable, SERVICE_TIMEOUT_MS) } private fun delayedShutdown() { @@ -84,14 +99,10 @@ abstract class ForegroundService : SdkForegroundService, EventListener, Service( stopSelf() } - // =========================================================== // - // START COMMAND HANDLER // - // =========================================================== // - /** Called when an intent is called for this service. */ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { super.onStartCommand(intent, flags, startId) - resetShutdown() + resetDelayedCallbacks() val intentDetails = "[ intent=$intent, flag=$flags, startId=$startId ]" logger.log(TAG, "Start foreground service from intent $intentDetails", "DEBUG") @@ -127,7 +138,7 @@ abstract class ForegroundService : SdkForegroundService, EventListener, Service( abstract fun getConnectRequest(): ConnectRequest? /** To be implemented by the application foreground service. - * Allows the user to override the default ServiceConfig. */ + * Allows the user to override the default ServiceConfig. */ abstract fun getServiceConfig(): ServiceConfig? /** Get the job to be executed from the Message data in the Intent. @@ -189,6 +200,7 @@ abstract class ForegroundService : SdkForegroundService, EventListener, Service( } } + /** Handles incoming events from the Breez SDK EventListener */ override fun onEvent(e: BreezEvent) { synchronized(this) { jobs.forEach { job -> job.onEvent(e) } diff --git a/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/MessagingService.kt b/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/MessagingService.kt index 61939c682..985ecbea1 100644 --- a/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/MessagingService.kt +++ b/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/MessagingService.kt @@ -7,6 +7,7 @@ import android.content.Context import android.util.Log import breez_sdk_notification.Constants.MESSAGE_TYPE_ADDRESS_TXS_CONFIRMED import breez_sdk_notification.Constants.MESSAGE_TYPE_PAYMENT_RECEIVED +import breez_sdk_notification.NotificationHelper.Companion.getNotificationManager @Suppress("unused") interface MessagingService { @@ -22,12 +23,13 @@ interface MessagingService { /** Check if the foreground service is needed depending on the * message type and foreground state of the application. */ fun startServiceIfNeeded(context: Context, message: Message) { + val notificationManager = getNotificationManager(context) val isServiceNeeded = when (message.type) { MESSAGE_TYPE_ADDRESS_TXS_CONFIRMED -> !isAppForeground(context) MESSAGE_TYPE_PAYMENT_RECEIVED -> !isAppForeground(context) else -> true } - if (isServiceNeeded) startForegroundService(message) + if (notificationManager != null && isServiceNeeded) startForegroundService(message) else Log.w(TAG, "Ignoring message ${message.type}: ${message.payload}") } diff --git a/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/NotificationHelper.kt b/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/NotificationHelper.kt index 72d66e728..a3f742a2d 100644 --- a/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/NotificationHelper.kt +++ b/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/NotificationHelper.kt @@ -72,7 +72,7 @@ class NotificationHelper { private const val TAG = "NotificationHelper" private var defaultClickAction: String? = null - private fun getNotificationManager(context: Context): NotificationManager? { + fun getNotificationManager(context: Context): NotificationManager? { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) @@ -85,7 +85,7 @@ class NotificationHelper { } @SuppressLint("NewApi") - private fun createNotificationChannelGroup( + fun createNotificationChannelGroup( context: Context, groupId: String, groupName: String, diff --git a/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/job/Job.kt b/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/job/Job.kt index 8b46b6706..1bfe4b02d 100644 --- a/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/job/Job.kt +++ b/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/job/Job.kt @@ -4,5 +4,13 @@ import breez_sdk.BlockingBreezServices import breez_sdk.EventListener interface Job : EventListener { + /** When the notification service is connected to the Breez SDK + * it calls `start` to initiate the job. + */ fun start(breezSDK: BlockingBreezServices) + + /** When the short service timeout is reached it calls `onShutdown` + * to cleanup the job. + */ + fun onShutdown() } diff --git a/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/job/LnurlPayInfo.kt b/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/job/LnurlPayInfo.kt index 35b0cd07d..61b575c1b 100644 --- a/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/job/LnurlPayInfo.kt +++ b/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/job/LnurlPayInfo.kt @@ -118,4 +118,6 @@ class LnurlPayInfoJob( fgService.onFinished(this) } + + override fun onShutdown() {} } diff --git a/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/job/LnurlPayInvoice.kt b/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/job/LnurlPayInvoice.kt index d69abfc3a..73e197050 100644 --- a/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/job/LnurlPayInvoice.kt +++ b/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/job/LnurlPayInvoice.kt @@ -118,4 +118,6 @@ class LnurlPayInvoiceJob( fgService.onFinished(this) } + + override fun onShutdown() {} } diff --git a/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/job/ReceivePayment.kt b/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/job/ReceivePayment.kt index d621e1a42..398b77bba 100644 --- a/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/job/ReceivePayment.kt +++ b/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/job/ReceivePayment.kt @@ -68,6 +68,8 @@ class ReceivePaymentJob( } } + override fun onShutdown() {} + private fun handleReceivedPayment( bolt11: String, paymentHash: String, diff --git a/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/job/RedeemSwap.kt b/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/job/RedeemSwap.kt index 1294c74f6..e44c1f354 100644 --- a/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/job/RedeemSwap.kt +++ b/libs/sdk-bindings/bindings-android/lib/src/main/kotlin/breez_sdk_notification/job/RedeemSwap.kt @@ -38,7 +38,7 @@ class RedeemSwapJob( breezSDK.redeemSwap(request.address) logger.log(TAG, "Found swap for ${request.address}", "INFO") } catch (e: Exception) { - logger.log(TAG, "Failed to manually reedeem swap notification: ${e.message}", "WARN") + logger.log(TAG, "Failed to manually redeem swap notification: ${e.message}", "WARN") } } @@ -47,7 +47,7 @@ class RedeemSwapJob( when (e) { is BreezEvent.SwapUpdated -> { val swapInfo = e.details - logger.log(TAG, "Received swap updated event: ${swapInfo.bitcoinAddress} current address: ${address} status: ${swapInfo.status}", "TRACE") + logger.log(TAG, "Received swap updated event: ${swapInfo.bitcoinAddress} current address: $address status: ${swapInfo.status}", "TRACE") if (swapInfo.bitcoinAddress == address) { if (swapInfo.paidMsat.toLong() > 0) { notifySuccessAndShutdown(address) @@ -60,8 +60,10 @@ class RedeemSwapJob( } } + override fun onShutdown() {} + private fun notifySuccessAndShutdown(address: String) { - logger.log(TAG, "Swap address ${address} redeemed succesfully", "INFO") + logger.log(TAG, "Swap address $address redeemed successfully", "INFO") notifyChannel( context, NOTIFICATION_CHANNEL_SWAP_TX_CONFIRMED,