Fixed crash on Oreo and issue on mobile network

This commit is contained in:
Christian Basler 2018-01-27 21:10:43 +01:00
parent 708529fc0a
commit 1906a2e13c
10 changed files with 174 additions and 81 deletions

View File

@ -19,8 +19,8 @@ package ch.dissem.apps.abit.listener
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import ch.dissem.apps.abit.service.BitmessageService
import ch.dissem.apps.abit.service.Singleton import ch.dissem.apps.abit.service.Singleton
import ch.dissem.apps.abit.util.NetworkUtils
import ch.dissem.apps.abit.util.Preferences import ch.dissem.apps.abit.util.Preferences
import org.jetbrains.anko.connectivityManager import org.jetbrains.anko.connectivityManager
@ -29,7 +29,7 @@ class WifiReceiver : BroadcastReceiver() {
if ("android.net.conn.CONNECTIVITY_CHANGE" == intent.action) { if ("android.net.conn.CONNECTIVITY_CHANGE" == intent.action) {
val bmc = Singleton.getBitmessageContext(ctx) val bmc = Singleton.getBitmessageContext(ctx)
if (Preferences.isFullNodeActive(ctx) && !bmc.isRunning() && !(Preferences.isWifiOnly(ctx) && ctx.connectivityManager.isActiveNetworkMetered)) { if (Preferences.isFullNodeActive(ctx) && !bmc.isRunning() && !(Preferences.isWifiOnly(ctx) && ctx.connectivityManager.isActiveNetworkMetered)) {
ctx.startService(Intent(ctx, BitmessageService::class.java)) NetworkUtils.doStartBitmessageService(ctx)
} }
} }
} }

View File

@ -17,8 +17,13 @@
package ch.dissem.apps.abit.notification package ch.dissem.apps.abit.notification
import android.app.Notification import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Context 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 import org.jetbrains.anko.notificationManager
/** /**
@ -46,4 +51,30 @@ abstract class AbstractNotification(ctx: Context) {
showing = false showing = false
manager.cancel(notificationId) 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 {
}
}
} }

View File

@ -30,10 +30,14 @@ import ch.dissem.apps.abit.R
*/ */
class ErrorNotification(ctx: Context) : AbstractNotification(ctx) { 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)) .setContentTitle(ctx.getString(R.string.app_name))
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
init {
initChannel(ERROR_CHANNEL_ID, R.color.colorPrimaryDark)
}
fun setWarning(@StringRes resId: Int, vararg args: Any): ErrorNotification { fun setWarning(@StringRes resId: Int, vararg args: Any): ErrorNotification {
builder.setSmallIcon(R.drawable.ic_notification_warning) builder.setSmallIcon(R.drawable.ic_notification_warning)
.setContentText(ctx.getString(resId, *args)) .setContentText(ctx.getString(resId, *args))

View File

@ -34,10 +34,11 @@ import kotlin.concurrent.fixedRateTimer
*/ */
class NetworkNotification(ctx: Context) : AbstractNotification(ctx) { 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 private var timer: Timer? = null
init { init {
initChannel(ONGOING_CHANNEL_ID, R.color.colorAccent)
val showAppIntent = Intent(ctx, MainActivity::class.java) val showAppIntent = Intent(ctx, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(ctx, 1, showAppIntent, 0) val pendingIntent = PendingIntent.getActivity(ctx, 1, showAppIntent, 0)
builder builder
@ -63,11 +64,19 @@ class NetworkNotification(ctx: Context) : AbstractNotification(ctx) {
val streamNumber = Integer.parseInt(stream.name.substring("stream ".length)) val streamNumber = Integer.parseInt(stream.name.substring("stream ".length))
val nodeCount = stream.getProperty("nodes")!!.value as Int? val nodeCount = stream.getProperty("nodes")!!.value as Int?
if (nodeCount == 1) { if (nodeCount == 1) {
info.append(ctx.getString(R.string.connection_info_1, info.append(
streamNumber)) ctx.getString(
R.string.connection_info_1,
streamNumber
)
)
} else { } else {
info.append(ctx.getString(R.string.connection_info_n, info.append(
streamNumber, nodeCount)) ctx.getString(
R.string.connection_info_n,
streamNumber, nodeCount
)
)
} }
info.append('\n') info.append('\n')
} }
@ -77,14 +86,18 @@ class NetworkNotification(ctx: Context) : AbstractNotification(ctx) {
val intent = Intent(ctx, BitmessageIntentService::class.java) val intent = Intent(ctx, BitmessageIntentService::class.java)
if (running) { if (running) {
intent.putExtra(BitmessageIntentService.EXTRA_SHUTDOWN_NODE, true) intent.putExtra(BitmessageIntentService.EXTRA_SHUTDOWN_NODE, true)
builder.addAction(R.drawable.ic_notification_node_stop, builder.addAction(
R.drawable.ic_notification_node_stop,
ctx.getString(R.string.full_node_stop), ctx.getString(R.string.full_node_stop),
PendingIntent.getService(ctx, 0, intent, FLAG_UPDATE_CURRENT)) PendingIntent.getService(ctx, 0, intent, FLAG_UPDATE_CURRENT)
)
} else { } else {
intent.putExtra(BitmessageIntentService.EXTRA_STARTUP_NODE, true) intent.putExtra(BitmessageIntentService.EXTRA_STARTUP_NODE, true)
builder.addAction(R.drawable.ic_notification_node_start, builder.addAction(
R.drawable.ic_notification_node_start,
ctx.getString(R.string.full_node_restart), ctx.getString(R.string.full_node_restart),
PendingIntent.getService(ctx, 1, intent, FLAG_UPDATE_CURRENT)) PendingIntent.getService(ctx, 1, intent, FLAG_UPDATE_CURRENT)
)
} }
notification = builder.build() notification = builder.build()
return running return running
@ -116,13 +129,15 @@ class NetworkNotification(ctx: Context) : AbstractNotification(ctx) {
val intent = Intent(ctx, BitmessageIntentService::class.java) val intent = Intent(ctx, BitmessageIntentService::class.java)
intent.putExtra(BitmessageIntentService.EXTRA_SHUTDOWN_NODE, true) intent.putExtra(BitmessageIntentService.EXTRA_SHUTDOWN_NODE, true)
builder.mActions.clear() builder.mActions.clear()
builder.addAction(R.drawable.ic_notification_node_stop, builder.addAction(
R.drawable.ic_notification_node_stop,
ctx.getString(R.string.full_node_stop), ctx.getString(R.string.full_node_stop),
PendingIntent.getService(ctx, 0, intent, FLAG_UPDATE_CURRENT)) PendingIntent.getService(ctx, 0, intent, FLAG_UPDATE_CURRENT)
)
notification = builder.build() notification = builder.build()
} }
companion object { companion object {
val NETWORK_NOTIFICATION_ID = 2 const val NETWORK_NOTIFICATION_ID = 2
} }
} }

View File

@ -42,8 +42,12 @@ import ch.dissem.apps.abit.util.Drawables.toBitmap
class NewMessageNotification(ctx: Context) : AbstractNotification(ctx) { class NewMessageNotification(ctx: Context) : AbstractNotification(ctx) {
init {
initChannel(MESSAGE_CHANNEL_ID, R.color.colorPrimary)
}
fun singleNotification(plaintext: Plaintext): NewMessageNotification { 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) val bigText = SpannableString(plaintext.subject + "\n" + plaintext.text)
plaintext.subject?.let { subject -> plaintext.subject?.let { subject ->
bigText.setSpan(SPAN_EMPHASIS, 0, subject.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE) bigText.setSpan(SPAN_EMPHASIS, 0, subject.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
@ -56,22 +60,30 @@ class NewMessageNotification(ctx: Context) : AbstractNotification(ctx) {
.setContentInfo("Info") .setContentInfo("Info")
builder.setContentIntent( builder.setContentIntent(
createActivityIntent(EXTRA_SHOW_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(
builder.addAction(R.drawable.ic_action_delete, ctx.getString(R.string.delete), R.drawable.ic_action_reply, ctx.getString(R.string.reply),
createServiceIntent(ctx, EXTRA_DELETE_MESSAGE, plaintext)) 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() notification = builder.build()
return this return this
} }
private fun createActivityIntent(action: String, message: Plaintext): PendingIntent { private fun createActivityIntent(action: String, message: Plaintext): PendingIntent {
val intent = Intent(ctx, MainActivity::class.java) val intent = Intent(ctx, MainActivity::class.java).putExtra(action, message)
intent.putExtra(action, message)
return PendingIntent.getActivity(ctx, action.hashCode(), intent, FLAG_UPDATE_CURRENT) 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) val intent = Intent(ctx, BitmessageIntentService::class.java)
intent.putExtra(action, message) intent.putExtra(action, message)
return PendingIntent.getService(ctx, action.hashCode(), intent, FLAG_UPDATE_CURRENT) return PendingIntent.getService(ctx, action.hashCode(), intent, FLAG_UPDATE_CURRENT)
@ -82,8 +94,11 @@ class NewMessageNotification(ctx: Context) : AbstractNotification(ctx) {
* * accessed it will be in a `synchronized(unacknowledged) * * accessed it will be in a `synchronized(unacknowledged)
* * {}` block * * {}` block
*/ */
fun multiNotification(unacknowledged: Collection<Plaintext>, numberOfUnacknowledgedMessages: Int): NewMessageNotification { fun multiNotification(
val builder = NotificationCompat.Builder(ctx, CHANNEL_ID) unacknowledged: Collection<Plaintext>,
numberOfUnacknowledgedMessages: Int
): NewMessageNotification {
val builder = NotificationCompat.Builder(ctx, MESSAGE_CHANNEL_ID)
builder.setSmallIcon(R.drawable.ic_notification_new_message) builder.setSmallIcon(R.drawable.ic_notification_new_message)
.setContentTitle(ctx.getString(R.string.n_new_messages, numberOfUnacknowledgedMessages)) .setContentTitle(ctx.getString(R.string.n_new_messages, numberOfUnacknowledgedMessages))
.setContentText(ctx.getString(R.string.app_name)) .setContentText(ctx.getString(R.string.app_name))
@ -93,8 +108,10 @@ class NewMessageNotification(ctx: Context) : AbstractNotification(ctx) {
synchronized(unacknowledged) { synchronized(unacknowledged) {
for (msg in unacknowledged) { for (msg in unacknowledged) {
val sb = SpannableString(msg.from.toString() + " " + msg.subject) val sb = SpannableString(msg.from.toString() + " " + msg.subject)
sb.setSpan(SPAN_EMPHASIS, 0, msg.from.toString().length, Spannable sb.setSpan(
.SPAN_INCLUSIVE_EXCLUSIVE) SPAN_EMPHASIS, 0, msg.from.toString().length, Spannable
.SPAN_INCLUSIVE_EXCLUSIVE
)
inboxStyle.addLine(sb) inboxStyle.addLine(sb)
} }
} }
@ -113,6 +130,5 @@ class NewMessageNotification(ctx: Context) : AbstractNotification(ctx) {
companion object { companion object {
private val NEW_MESSAGE_NOTIFICATION_ID = 1 private val NEW_MESSAGE_NOTIFICATION_ID = 1
private val SPAN_EMPHASIS = StyleSpan(Typeface.BOLD) private val SPAN_EMPHASIS = StyleSpan(Typeface.BOLD)
private val CHANNEL_ID = "abit.message"
} }
} }

View File

@ -33,7 +33,7 @@ import kotlin.concurrent.fixedRateTimer
*/ */
class ProofOfWorkNotification(ctx: Context) : AbstractNotification(ctx) { 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) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setUsesChronometer(true) .setUsesChronometer(true)
.setOngoing(true) .setOngoing(true)
@ -46,6 +46,7 @@ class ProofOfWorkNotification(ctx: Context) : AbstractNotification(ctx) {
private var timer: Timer? = null private var timer: Timer? = null
init { init {
initChannel(ONGOING_CHANNEL_ID, R.color.colorAccent)
update(0) update(0)
} }
@ -67,10 +68,6 @@ class ProofOfWorkNotification(ctx: Context) : AbstractNotification(ctx) {
return this return this
} }
companion object {
const val ONGOING_NOTIFICATION_ID = 3
}
fun start(item: ProofOfWorkService.PowItem) { fun start(item: ProofOfWorkService.PowItem) {
val expectedPowTimeInMilliseconds = PowStats.getExpectedPowTimeInMilliseconds(ctx, item.targetValue) val expectedPowTimeInMilliseconds = PowStats.getExpectedPowTimeInMilliseconds(ctx, item.targetValue)
val delta = (expectedPowTimeInMilliseconds / 3).toInt() val delta = (expectedPowTimeInMilliseconds / 3).toInt()
@ -101,4 +98,8 @@ class ProofOfWorkNotification(ctx: Context) : AbstractNotification(ctx) {
show() show()
} }
} }
companion object {
const val ONGOING_NOTIFICATION_ID = 3
}
} }

View File

@ -25,6 +25,8 @@ import android.net.ConnectivityManager
import android.os.Handler import android.os.Handler
import ch.dissem.apps.abit.notification.NetworkNotification import ch.dissem.apps.abit.notification.NetworkNotification
import ch.dissem.apps.abit.notification.NetworkNotification.Companion.NETWORK_NOTIFICATION_ID 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.BitmessageContext
import ch.dissem.bitmessage.utils.Property import ch.dissem.bitmessage.utils.Property
import org.jetbrains.anko.connectivityManager import org.jetbrains.anko.connectivityManager
@ -40,8 +42,8 @@ class BitmessageService : Service() {
private lateinit var notification: NetworkNotification private lateinit var notification: NetworkNotification
private val connectivityReceiver: BroadcastReceiver = object : BroadcastReceiver() { private val connectivityReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context, intent: Intent?) {
if (bmc.isRunning() && connectivityManager.isActiveNetworkMetered){ if (bmc.isRunning() && !Preferences.isConnectionAllowed(this@BitmessageService)) {
bmc.shutdown() bmc.shutdown()
} }
} }
@ -58,7 +60,10 @@ class BitmessageService : Service() {
} }
override fun onCreate() { override fun onCreate() {
registerReceiver(connectivityReceiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)) registerReceiver(
connectivityReceiver,
IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
)
notification = NetworkNotification(this) notification = NetworkNotification(this)
running = false running = false
} }
@ -85,13 +90,15 @@ class BitmessageService : Service() {
notification.showShutdown() notification.showShutdown()
cleanupHandler.removeCallbacks(cleanupTask) cleanupHandler.removeCallbacks(cleanupTask)
bmc.cleanup() bmc.cleanup()
unregisterReceiver(connectivityReceiver)
stopSelf() stopSelf()
} }
override fun onBind(intent: Intent) = null override fun onBind(intent: Intent) = null
companion object { companion object {
@Volatile private var running = false @Volatile
private var running = false
val isRunning: Boolean val isRunning: Boolean
get() = running && Singleton.bitmessageContext?.isRunning() == true get() = running && Singleton.bitmessageContext?.isRunning() == true

View File

@ -20,6 +20,7 @@ import android.app.Service
import android.content.Intent import android.content.Intent
import android.os.Binder import android.os.Binder
import android.os.IBinder import android.os.IBinder
import android.support.v4.content.ContextCompat
import ch.dissem.apps.abit.notification.ProofOfWorkNotification import ch.dissem.apps.abit.notification.ProofOfWorkNotification
import ch.dissem.apps.abit.notification.ProofOfWorkNotification.Companion.ONGOING_NOTIFICATION_ID import ch.dissem.apps.abit.notification.ProofOfWorkNotification.Companion.ONGOING_NOTIFICATION_ID
import ch.dissem.apps.abit.util.PowStats import ch.dissem.apps.abit.util.PowStats
@ -44,9 +45,14 @@ class ProofOfWorkService : Service() {
private val notification = service.notification private val notification = service.notification
fun process(item: PowItem) = synchronized(queue) { fun process(item: PowItem) = synchronized(queue) {
service.startService(Intent(service, ProofOfWorkService::class.java)) ContextCompat.startForegroundService(
service.startForeground(ONGOING_NOTIFICATION_ID, service,
notification.notification) Intent(service, ProofOfWorkService::class.java)
)
service.startForeground(
ONGOING_NOTIFICATION_ID,
notification.notification
)
if (!calculating) { if (!calculating) {
calculating = true calculating = true
service.calculateNonce(item) 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 { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false
@ -81,7 +91,10 @@ class ProofOfWorkService : Service() {
private fun calculateNonce(item: PowItem) { private fun calculateNonce(item: PowItem) {
notification.start(item) notification.start(item)
val startTime = System.currentTimeMillis() val startTime = System.currentTimeMillis()
engine.calculateNonce(item.initialHash, item.targetValue, object : ProofOfWorkEngine.Callback { engine.calculateNonce(
item.initialHash,
item.targetValue,
object : ProofOfWorkEngine.Callback {
override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) { override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) {
notification.finished() notification.finished()
val time = System.currentTimeMillis() - startTime val time = System.currentTimeMillis() - startTime

View File

@ -5,6 +5,7 @@ import android.app.job.JobService
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.support.annotation.RequiresApi import android.support.annotation.RequiresApi
import ch.dissem.apps.abit.util.NetworkUtils
import ch.dissem.apps.abit.util.Preferences import ch.dissem.apps.abit.util.Preferences
/** /**
@ -19,7 +20,7 @@ class StartupNodeOnWifiService : JobService() {
override fun onStartJob(params: JobParameters?): Boolean { override fun onStartJob(params: JobParameters?): Boolean {
val bmc = Singleton.getBitmessageContext(this) val bmc = Singleton.getBitmessageContext(this)
if (Preferences.isFullNodeActive(this) && !bmc.isRunning()) { if (Preferences.isFullNodeActive(this) && !bmc.isRunning()) {
applicationContext.startService(Intent(this, BitmessageService::class.java)) NetworkUtils.doStartBitmessageService(applicationContext)
} }
return true return true
} }

View File

@ -8,6 +8,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.support.annotation.RequiresApi import android.support.annotation.RequiresApi
import android.support.v4.content.ContextCompat
import ch.dissem.apps.abit.MainActivity import ch.dissem.apps.abit.MainActivity
import ch.dissem.apps.abit.dialog.FullNodeDialogActivity import ch.dissem.apps.abit.dialog.FullNodeDialogActivity
import ch.dissem.apps.abit.service.BitmessageService import ch.dissem.apps.abit.service.BitmessageService
@ -23,7 +24,7 @@ object NetworkUtils {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
scheduleNodeStart(ctx) scheduleNodeStart(ctx)
} else { } else {
ctx.startService(Intent(ctx, BitmessageService::class.java)) doStartBitmessageService(ctx)
MainActivity.updateNodeSwitch() MainActivity.updateNodeSwitch()
} }
} else if (ask) { } else if (ask) {
@ -37,11 +38,15 @@ object NetworkUtils {
scheduleNodeStart(ctx) scheduleNodeStart(ctx)
} }
} else { } else {
ctx.startService(Intent(ctx, BitmessageService::class.java)) doStartBitmessageService(ctx)
MainActivity.updateNodeSwitch() MainActivity.updateNodeSwitch()
} }
} }
fun doStartBitmessageService(ctx: Context) {
ContextCompat.startForegroundService(ctx, Intent(ctx, BitmessageService::class.java))
}
fun disableNode(ctx: Context) { fun disableNode(ctx: Context) {
Preferences.setFullNodeActive(ctx, false) Preferences.setFullNodeActive(ctx, false)
ctx.stopService(Intent(ctx, BitmessageService::class.java)) ctx.stopService(Intent(ctx, BitmessageService::class.java))