From 1906a2e13c2fb99f4902d87b925cd7d6bca86ecb Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Sat, 27 Jan 2018 21:10:43 +0100 Subject: [PATCH] Fixed crash on Oreo and issue on mobile network --- .../dissem/apps/abit/listener/WifiReceiver.kt | 4 +- .../abit/notification/AbstractNotification.kt | 31 ++++++++++ .../abit/notification/ErrorNotification.kt | 6 +- .../abit/notification/NetworkNotification.kt | 55 +++++++++++------ .../notification/NewMessageNotification.kt | 58 +++++++++++------- .../notification/ProofOfWorkNotification.kt | 11 ++-- .../apps/abit/service/BitmessageService.kt | 17 ++++-- .../apps/abit/service/ProofOfWorkService.kt | 61 +++++++++++-------- .../abit/service/StartupNodeOnWifiService.kt | 3 +- .../ch/dissem/apps/abit/util/NetworkUtils.kt | 9 ++- 10 files changed, 174 insertions(+), 81 deletions(-) diff --git a/app/src/main/java/ch/dissem/apps/abit/listener/WifiReceiver.kt b/app/src/main/java/ch/dissem/apps/abit/listener/WifiReceiver.kt index 7171483..82851c6 100644 --- a/app/src/main/java/ch/dissem/apps/abit/listener/WifiReceiver.kt +++ b/app/src/main/java/ch/dissem/apps/abit/listener/WifiReceiver.kt @@ -19,8 +19,8 @@ package ch.dissem.apps.abit.listener import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import ch.dissem.apps.abit.service.BitmessageService import ch.dissem.apps.abit.service.Singleton +import ch.dissem.apps.abit.util.NetworkUtils import ch.dissem.apps.abit.util.Preferences import org.jetbrains.anko.connectivityManager @@ -29,7 +29,7 @@ class WifiReceiver : BroadcastReceiver() { if ("android.net.conn.CONNECTIVITY_CHANGE" == intent.action) { val bmc = Singleton.getBitmessageContext(ctx) if (Preferences.isFullNodeActive(ctx) && !bmc.isRunning() && !(Preferences.isWifiOnly(ctx) && ctx.connectivityManager.isActiveNetworkMetered)) { - ctx.startService(Intent(ctx, BitmessageService::class.java)) + NetworkUtils.doStartBitmessageService(ctx) } } } diff --git a/app/src/main/java/ch/dissem/apps/abit/notification/AbstractNotification.kt b/app/src/main/java/ch/dissem/apps/abit/notification/AbstractNotification.kt index 6bb006a..f008439 100644 --- a/app/src/main/java/ch/dissem/apps/abit/notification/AbstractNotification.kt +++ b/app/src/main/java/ch/dissem/apps/abit/notification/AbstractNotification.kt @@ -17,8 +17,13 @@ package ch.dissem.apps.abit.notification import android.app.Notification +import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context +import android.os.Build +import android.support.annotation.ColorRes +import android.support.v4.content.ContextCompat +import ch.dissem.apps.abit.R import org.jetbrains.anko.notificationManager /** @@ -46,4 +51,30 @@ abstract class AbstractNotification(ctx: Context) { showing = false manager.cancel(notificationId) } + + protected fun initChannel(channelId: String, @ColorRes color: Int = R.color.colorPrimary) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + ctx.notificationManager.createNotificationChannel( + NotificationChannel( + channelId, + ctx.getText(R.string.app_name), + NotificationManager.IMPORTANCE_LOW + ).apply { + lightColor = ContextCompat.getColor(ctx, color) + lockscreenVisibility = Notification.VISIBILITY_PRIVATE + } + ) + } + } + + + companion object { + internal const val ONGOING_CHANNEL_ID = "abit.ongoing" + internal const val MESSAGE_CHANNEL_ID = "abit.message" + internal const val ERROR_CHANNEL_ID = "abit.error" + + init { + + } + } } diff --git a/app/src/main/java/ch/dissem/apps/abit/notification/ErrorNotification.kt b/app/src/main/java/ch/dissem/apps/abit/notification/ErrorNotification.kt index 341b86c..052c638 100644 --- a/app/src/main/java/ch/dissem/apps/abit/notification/ErrorNotification.kt +++ b/app/src/main/java/ch/dissem/apps/abit/notification/ErrorNotification.kt @@ -30,10 +30,14 @@ import ch.dissem.apps.abit.R */ class ErrorNotification(ctx: Context) : AbstractNotification(ctx) { - private val builder = NotificationCompat.Builder(ctx, "abit.error") + private val builder = NotificationCompat.Builder(ctx, ERROR_CHANNEL_ID) .setContentTitle(ctx.getString(R.string.app_name)) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + init { + initChannel(ERROR_CHANNEL_ID, R.color.colorPrimaryDark) + } + fun setWarning(@StringRes resId: Int, vararg args: Any): ErrorNotification { builder.setSmallIcon(R.drawable.ic_notification_warning) .setContentText(ctx.getString(resId, *args)) diff --git a/app/src/main/java/ch/dissem/apps/abit/notification/NetworkNotification.kt b/app/src/main/java/ch/dissem/apps/abit/notification/NetworkNotification.kt index 14cdb73..1baebb4 100644 --- a/app/src/main/java/ch/dissem/apps/abit/notification/NetworkNotification.kt +++ b/app/src/main/java/ch/dissem/apps/abit/notification/NetworkNotification.kt @@ -34,18 +34,19 @@ import kotlin.concurrent.fixedRateTimer */ class NetworkNotification(ctx: Context) : AbstractNotification(ctx) { - private val builder = NotificationCompat.Builder(ctx, "abit.network") + private val builder = NotificationCompat.Builder(ctx, ONGOING_CHANNEL_ID) private var timer: Timer? = null init { + initChannel(ONGOING_CHANNEL_ID, R.color.colorAccent) val showAppIntent = Intent(ctx, MainActivity::class.java) val pendingIntent = PendingIntent.getActivity(ctx, 1, showAppIntent, 0) builder - .setSmallIcon(R.drawable.ic_notification_full_node) - .setContentTitle(ctx.getString(R.string.bitmessage_full_node)) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - .setShowWhen(false) - .setContentIntent(pendingIntent) + .setSmallIcon(R.drawable.ic_notification_full_node) + .setContentTitle(ctx.getString(R.string.bitmessage_full_node)) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setShowWhen(false) + .setContentIntent(pendingIntent) } @SuppressLint("StringFormatMatches") @@ -63,11 +64,19 @@ class NetworkNotification(ctx: Context) : AbstractNotification(ctx) { val streamNumber = Integer.parseInt(stream.name.substring("stream ".length)) val nodeCount = stream.getProperty("nodes")!!.value as Int? if (nodeCount == 1) { - info.append(ctx.getString(R.string.connection_info_1, - streamNumber)) + info.append( + ctx.getString( + R.string.connection_info_1, + streamNumber + ) + ) } else { - info.append(ctx.getString(R.string.connection_info_n, - streamNumber, nodeCount)) + info.append( + ctx.getString( + R.string.connection_info_n, + streamNumber, nodeCount + ) + ) } info.append('\n') } @@ -77,14 +86,18 @@ class NetworkNotification(ctx: Context) : AbstractNotification(ctx) { val intent = Intent(ctx, BitmessageIntentService::class.java) if (running) { intent.putExtra(BitmessageIntentService.EXTRA_SHUTDOWN_NODE, true) - builder.addAction(R.drawable.ic_notification_node_stop, - ctx.getString(R.string.full_node_stop), - PendingIntent.getService(ctx, 0, intent, FLAG_UPDATE_CURRENT)) + builder.addAction( + R.drawable.ic_notification_node_stop, + ctx.getString(R.string.full_node_stop), + PendingIntent.getService(ctx, 0, intent, FLAG_UPDATE_CURRENT) + ) } else { intent.putExtra(BitmessageIntentService.EXTRA_STARTUP_NODE, true) - builder.addAction(R.drawable.ic_notification_node_start, - ctx.getString(R.string.full_node_restart), - PendingIntent.getService(ctx, 1, intent, FLAG_UPDATE_CURRENT)) + builder.addAction( + R.drawable.ic_notification_node_start, + ctx.getString(R.string.full_node_restart), + PendingIntent.getService(ctx, 1, intent, FLAG_UPDATE_CURRENT) + ) } notification = builder.build() return running @@ -116,13 +129,15 @@ class NetworkNotification(ctx: Context) : AbstractNotification(ctx) { val intent = Intent(ctx, BitmessageIntentService::class.java) intent.putExtra(BitmessageIntentService.EXTRA_SHUTDOWN_NODE, true) builder.mActions.clear() - builder.addAction(R.drawable.ic_notification_node_stop, - ctx.getString(R.string.full_node_stop), - PendingIntent.getService(ctx, 0, intent, FLAG_UPDATE_CURRENT)) + builder.addAction( + R.drawable.ic_notification_node_stop, + ctx.getString(R.string.full_node_stop), + PendingIntent.getService(ctx, 0, intent, FLAG_UPDATE_CURRENT) + ) notification = builder.build() } companion object { - val NETWORK_NOTIFICATION_ID = 2 + const val NETWORK_NOTIFICATION_ID = 2 } } diff --git a/app/src/main/java/ch/dissem/apps/abit/notification/NewMessageNotification.kt b/app/src/main/java/ch/dissem/apps/abit/notification/NewMessageNotification.kt index fef64e0..fc14f76 100644 --- a/app/src/main/java/ch/dissem/apps/abit/notification/NewMessageNotification.kt +++ b/app/src/main/java/ch/dissem/apps/abit/notification/NewMessageNotification.kt @@ -42,36 +42,48 @@ import ch.dissem.apps.abit.util.Drawables.toBitmap class NewMessageNotification(ctx: Context) : AbstractNotification(ctx) { + init { + initChannel(MESSAGE_CHANNEL_ID, R.color.colorPrimary) + } + fun singleNotification(plaintext: Plaintext): NewMessageNotification { - val builder = NotificationCompat.Builder(ctx, CHANNEL_ID) + val builder = NotificationCompat.Builder(ctx, MESSAGE_CHANNEL_ID) val bigText = SpannableString(plaintext.subject + "\n" + plaintext.text) plaintext.subject?.let { subject -> bigText.setSpan(SPAN_EMPHASIS, 0, subject.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE) } builder.setSmallIcon(R.drawable.ic_notification_new_message) - .setLargeIcon(toBitmap(Identicon(plaintext.from), 192)) - .setContentTitle(plaintext.from.toString()) - .setContentText(plaintext.subject) - .setStyle(BigTextStyle().bigText(bigText)) - .setContentInfo("Info") + .setLargeIcon(toBitmap(Identicon(plaintext.from), 192)) + .setContentTitle(plaintext.from.toString()) + .setContentText(plaintext.subject) + .setStyle(BigTextStyle().bigText(bigText)) + .setContentInfo("Info") builder.setContentIntent( - createActivityIntent(EXTRA_SHOW_MESSAGE, plaintext)) - builder.addAction(R.drawable.ic_action_reply, ctx.getString(R.string.reply), - createActivityIntent(EXTRA_REPLY_TO_MESSAGE, plaintext)) - builder.addAction(R.drawable.ic_action_delete, ctx.getString(R.string.delete), - createServiceIntent(ctx, EXTRA_DELETE_MESSAGE, plaintext)) + createActivityIntent(EXTRA_SHOW_MESSAGE, plaintext) + ) + builder.addAction( + R.drawable.ic_action_reply, ctx.getString(R.string.reply), + createActivityIntent(EXTRA_REPLY_TO_MESSAGE, plaintext) + ) + builder.addAction( + R.drawable.ic_action_delete, ctx.getString(R.string.delete), + createServiceIntent(ctx, EXTRA_DELETE_MESSAGE, plaintext) + ) notification = builder.build() return this } private fun createActivityIntent(action: String, message: Plaintext): PendingIntent { - val intent = Intent(ctx, MainActivity::class.java) - intent.putExtra(action, message) + val intent = Intent(ctx, MainActivity::class.java).putExtra(action, message) return PendingIntent.getActivity(ctx, action.hashCode(), intent, FLAG_UPDATE_CURRENT) } - private fun createServiceIntent(ctx: Context, action: String, message: Plaintext): PendingIntent { + private fun createServiceIntent( + ctx: Context, + action: String, + message: Plaintext + ): PendingIntent { val intent = Intent(ctx, BitmessageIntentService::class.java) intent.putExtra(action, message) return PendingIntent.getService(ctx, action.hashCode(), intent, FLAG_UPDATE_CURRENT) @@ -82,19 +94,24 @@ class NewMessageNotification(ctx: Context) : AbstractNotification(ctx) { * * accessed it will be in a `synchronized(unacknowledged) * * {}` block */ - fun multiNotification(unacknowledged: Collection, numberOfUnacknowledgedMessages: Int): NewMessageNotification { - val builder = NotificationCompat.Builder(ctx, CHANNEL_ID) + fun multiNotification( + unacknowledged: Collection<Plaintext>, + numberOfUnacknowledgedMessages: Int + ): NewMessageNotification { + val builder = NotificationCompat.Builder(ctx, MESSAGE_CHANNEL_ID) builder.setSmallIcon(R.drawable.ic_notification_new_message) - .setContentTitle(ctx.getString(R.string.n_new_messages, numberOfUnacknowledgedMessages)) - .setContentText(ctx.getString(R.string.app_name)) + .setContentTitle(ctx.getString(R.string.n_new_messages, numberOfUnacknowledgedMessages)) + .setContentText(ctx.getString(R.string.app_name)) val inboxStyle = InboxStyle() synchronized(unacknowledged) { for (msg in unacknowledged) { val sb = SpannableString(msg.from.toString() + " " + msg.subject) - sb.setSpan(SPAN_EMPHASIS, 0, msg.from.toString().length, Spannable - .SPAN_INCLUSIVE_EXCLUSIVE) + sb.setSpan( + SPAN_EMPHASIS, 0, msg.from.toString().length, Spannable + .SPAN_INCLUSIVE_EXCLUSIVE + ) inboxStyle.addLine(sb) } } @@ -113,6 +130,5 @@ class NewMessageNotification(ctx: Context) : AbstractNotification(ctx) { companion object { private val NEW_MESSAGE_NOTIFICATION_ID = 1 private val SPAN_EMPHASIS = StyleSpan(Typeface.BOLD) - private val CHANNEL_ID = "abit.message" } } diff --git a/app/src/main/java/ch/dissem/apps/abit/notification/ProofOfWorkNotification.kt b/app/src/main/java/ch/dissem/apps/abit/notification/ProofOfWorkNotification.kt index 3a49b7b..4703e7c 100644 --- a/app/src/main/java/ch/dissem/apps/abit/notification/ProofOfWorkNotification.kt +++ b/app/src/main/java/ch/dissem/apps/abit/notification/ProofOfWorkNotification.kt @@ -33,7 +33,7 @@ import kotlin.concurrent.fixedRateTimer */ class ProofOfWorkNotification(ctx: Context) : AbstractNotification(ctx) { - private val builder = NotificationCompat.Builder(ctx, "abit.pow") + private val builder = NotificationCompat.Builder(ctx, ONGOING_CHANNEL_ID) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setUsesChronometer(true) .setOngoing(true) @@ -46,6 +46,7 @@ class ProofOfWorkNotification(ctx: Context) : AbstractNotification(ctx) { private var timer: Timer? = null init { + initChannel(ONGOING_CHANNEL_ID, R.color.colorAccent) update(0) } @@ -67,10 +68,6 @@ class ProofOfWorkNotification(ctx: Context) : AbstractNotification(ctx) { return this } - companion object { - const val ONGOING_NOTIFICATION_ID = 3 - } - fun start(item: ProofOfWorkService.PowItem) { val expectedPowTimeInMilliseconds = PowStats.getExpectedPowTimeInMilliseconds(ctx, item.targetValue) val delta = (expectedPowTimeInMilliseconds / 3).toInt() @@ -101,4 +98,8 @@ class ProofOfWorkNotification(ctx: Context) : AbstractNotification(ctx) { show() } } + + companion object { + const val ONGOING_NOTIFICATION_ID = 3 + } } diff --git a/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.kt b/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.kt index 76b4d3e..31c0a88 100644 --- a/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.kt +++ b/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.kt @@ -25,6 +25,8 @@ import android.net.ConnectivityManager import android.os.Handler import ch.dissem.apps.abit.notification.NetworkNotification import ch.dissem.apps.abit.notification.NetworkNotification.Companion.NETWORK_NOTIFICATION_ID +import ch.dissem.apps.abit.util.NetworkUtils +import ch.dissem.apps.abit.util.Preferences import ch.dissem.bitmessage.BitmessageContext import ch.dissem.bitmessage.utils.Property import org.jetbrains.anko.connectivityManager @@ -39,9 +41,9 @@ class BitmessageService : Service() { private val bmc: BitmessageContext by lazy { Singleton.getBitmessageContext(this) } private lateinit var notification: NetworkNotification - private val connectivityReceiver: BroadcastReceiver = object: BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { - if (bmc.isRunning() && connectivityManager.isActiveNetworkMetered){ + private val connectivityReceiver: BroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent?) { + if (bmc.isRunning() && !Preferences.isConnectionAllowed(this@BitmessageService)) { bmc.shutdown() } } @@ -58,7 +60,10 @@ class BitmessageService : Service() { } override fun onCreate() { - registerReceiver(connectivityReceiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)) + registerReceiver( + connectivityReceiver, + IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION) + ) notification = NetworkNotification(this) running = false } @@ -85,13 +90,15 @@ class BitmessageService : Service() { notification.showShutdown() cleanupHandler.removeCallbacks(cleanupTask) bmc.cleanup() + unregisterReceiver(connectivityReceiver) stopSelf() } override fun onBind(intent: Intent) = null companion object { - @Volatile private var running = false + @Volatile + private var running = false val isRunning: Boolean get() = running && Singleton.bitmessageContext?.isRunning() == true diff --git a/app/src/main/java/ch/dissem/apps/abit/service/ProofOfWorkService.kt b/app/src/main/java/ch/dissem/apps/abit/service/ProofOfWorkService.kt index 2288896..4d8b3e7 100644 --- a/app/src/main/java/ch/dissem/apps/abit/service/ProofOfWorkService.kt +++ b/app/src/main/java/ch/dissem/apps/abit/service/ProofOfWorkService.kt @@ -20,6 +20,7 @@ import android.app.Service import android.content.Intent import android.os.Binder import android.os.IBinder +import android.support.v4.content.ContextCompat import ch.dissem.apps.abit.notification.ProofOfWorkNotification import ch.dissem.apps.abit.notification.ProofOfWorkNotification.Companion.ONGOING_NOTIFICATION_ID import ch.dissem.apps.abit.util.PowStats @@ -44,9 +45,14 @@ class ProofOfWorkService : Service() { private val notification = service.notification fun process(item: PowItem) = synchronized(queue) { - service.startService(Intent(service, ProofOfWorkService::class.java)) - service.startForeground(ONGOING_NOTIFICATION_ID, - notification.notification) + ContextCompat.startForegroundService( + service, + Intent(service, ProofOfWorkService::class.java) + ) + service.startForeground( + ONGOING_NOTIFICATION_ID, + notification.notification + ) if (!calculating) { calculating = true service.calculateNonce(item) @@ -58,7 +64,11 @@ class ProofOfWorkService : Service() { } - data class PowItem(val initialHash: ByteArray, val targetValue: ByteArray, val callback: ProofOfWorkEngine.Callback) { + data class PowItem( + val initialHash: ByteArray, + val targetValue: ByteArray, + val callback: ProofOfWorkEngine.Callback + ) { override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false @@ -81,29 +91,32 @@ class ProofOfWorkService : Service() { private fun calculateNonce(item: PowItem) { notification.start(item) val startTime = System.currentTimeMillis() - engine.calculateNonce(item.initialHash, item.targetValue, object : ProofOfWorkEngine.Callback { - override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) { - notification.finished() - val time = System.currentTimeMillis() - startTime - PowStats.addPow(this@ProofOfWorkService, time, item.targetValue) - try { - item.callback.onNonceCalculated(initialHash, nonce) - } finally { - var next: PowItem? = null - synchronized(queue) { - next = queue.poll() - if (next == null) { - calculating = false - stopForeground(true) - stopSelf() - } else { - notification.update(queue.size).show() + engine.calculateNonce( + item.initialHash, + item.targetValue, + object : ProofOfWorkEngine.Callback { + override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) { + notification.finished() + val time = System.currentTimeMillis() - startTime + PowStats.addPow(this@ProofOfWorkService, time, item.targetValue) + try { + item.callback.onNonceCalculated(initialHash, nonce) + } finally { + var next: PowItem? = null + synchronized(queue) { + next = queue.poll() + if (next == null) { + calculating = false + stopForeground(true) + stopSelf() + } else { + notification.update(queue.size).show() + } } + next?.let { calculateNonce(it) } } - next?.let { calculateNonce(it) } } - } - }) + }) } companion object { diff --git a/app/src/main/java/ch/dissem/apps/abit/service/StartupNodeOnWifiService.kt b/app/src/main/java/ch/dissem/apps/abit/service/StartupNodeOnWifiService.kt index 5aa3f9b..0e1d557 100644 --- a/app/src/main/java/ch/dissem/apps/abit/service/StartupNodeOnWifiService.kt +++ b/app/src/main/java/ch/dissem/apps/abit/service/StartupNodeOnWifiService.kt @@ -5,6 +5,7 @@ import android.app.job.JobService import android.content.Intent import android.os.Build import android.support.annotation.RequiresApi +import ch.dissem.apps.abit.util.NetworkUtils import ch.dissem.apps.abit.util.Preferences /** @@ -19,7 +20,7 @@ class StartupNodeOnWifiService : JobService() { override fun onStartJob(params: JobParameters?): Boolean { val bmc = Singleton.getBitmessageContext(this) if (Preferences.isFullNodeActive(this) && !bmc.isRunning()) { - applicationContext.startService(Intent(this, BitmessageService::class.java)) + NetworkUtils.doStartBitmessageService(applicationContext) } return true } diff --git a/app/src/main/java/ch/dissem/apps/abit/util/NetworkUtils.kt b/app/src/main/java/ch/dissem/apps/abit/util/NetworkUtils.kt index 03d1143..7dd32cb 100644 --- a/app/src/main/java/ch/dissem/apps/abit/util/NetworkUtils.kt +++ b/app/src/main/java/ch/dissem/apps/abit/util/NetworkUtils.kt @@ -8,6 +8,7 @@ import android.content.Context import android.content.Intent import android.os.Build import android.support.annotation.RequiresApi +import android.support.v4.content.ContextCompat import ch.dissem.apps.abit.MainActivity import ch.dissem.apps.abit.dialog.FullNodeDialogActivity import ch.dissem.apps.abit.service.BitmessageService @@ -23,7 +24,7 @@ object NetworkUtils { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { scheduleNodeStart(ctx) } else { - ctx.startService(Intent(ctx, BitmessageService::class.java)) + doStartBitmessageService(ctx) MainActivity.updateNodeSwitch() } } else if (ask) { @@ -37,11 +38,15 @@ object NetworkUtils { scheduleNodeStart(ctx) } } else { - ctx.startService(Intent(ctx, BitmessageService::class.java)) + doStartBitmessageService(ctx) MainActivity.updateNodeSwitch() } } + fun doStartBitmessageService(ctx: Context) { + ContextCompat.startForegroundService(ctx, Intent(ctx, BitmessageService::class.java)) + } + fun disableNode(ctx: Context) { Preferences.setFullNodeActive(ctx, false) ctx.stopService(Intent(ctx, BitmessageService::class.java))