Forward messages, don't show ntfy notification

This commit is contained in:
Philipp Heckel 2021-12-29 21:36:47 +01:00
parent 94e595110d
commit 7e9da28704
9 changed files with 104 additions and 119 deletions

View file

@ -85,16 +85,13 @@ class Repository(private val sharedPrefs: SharedPreferences, private val subscri
@Suppress("RedundantSuspendModifier")
@WorkerThread
suspend fun addNotification(notification: Notification): NotificationAddResult {
suspend fun addNotification(notification: Notification): Boolean {
val maybeExistingNotification = notificationDao.get(notification.id)
if (maybeExistingNotification == null) {
notificationDao.add(notification)
val detailsVisible = detailViewSubscriptionId.get() == notification.subscriptionId
val muted = isMuted(notification.subscriptionId)
val notify = !detailsVisible && !muted
return NotificationAddResult(notification = notification, notify = notify, broadcast = true, muted = muted)
if (maybeExistingNotification != null) {
return false
}
return NotificationAddResult(notification = notification, notify = false, broadcast = false, forward = false, muted = false)
notificationDao.add(notification)
return true
}
@Suppress("RedundantSuspendModifier")
@ -141,7 +138,7 @@ class Repository(private val sharedPrefs: SharedPreferences, private val subscri
return s.mutedUntil == 1L || (s.mutedUntil > 1L && s.mutedUntil > System.currentTimeMillis()/1000)
}
private fun isGlobalMuted(): Boolean {
fun isGlobalMuted(): Boolean {
val mutedUntil = getGlobalMutedUntil()
return mutedUntil == 1L || (mutedUntil > 1L && mutedUntil > System.currentTimeMillis()/1000)
}
@ -228,14 +225,6 @@ class Repository(private val sharedPrefs: SharedPreferences, private val subscri
return connectionStatesLiveData.value!!.getOrElse(subscriptionId) { ConnectionState.NOT_APPLICABLE }
}
data class NotificationAddResult(
val notification: Notification,
val notify: Boolean,
val broadcast: Boolean,
val forward: Boolean, // Forward to UnifiedPush connector
val muted: Boolean,
)
companion object {
const val SHARED_PREFS_ID = "MainPreferences"
const val SHARED_PREFS_POLL_WORKER_VERSION = "PollWorkerVersion"

View file

@ -1,28 +1,49 @@
package io.heckel.ntfy.msg
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.TaskStackBuilder
import android.content.Context
import android.content.Intent
import android.media.RingtoneManager
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import io.heckel.ntfy.R
import io.heckel.ntfy.data.Notification
import io.heckel.ntfy.data.Repository
import io.heckel.ntfy.data.Subscription
import io.heckel.ntfy.ui.DetailActivity
import io.heckel.ntfy.ui.MainActivity
import io.heckel.ntfy.util.formatMessage
import io.heckel.ntfy.util.formatTitle
import io.heckel.ntfy.up.Distributor
class NotificationDispatcher(val context: Context, val repository: Repository) {
private val notifier = NotificationService(context)
private val broadcaster = BroadcastService(context)
private val distributor = Distributor(context)
fun init() {
notifier.createNotificationChannels()
}
class NotificationDispatcher(val context: Context) {
fun dispatch(subscription: Subscription, notification: Notification) {
val muted = checkMuted(subscription)
val notify = checkNotify(subscription, notification, muted)
val broadcast = subscription.upAppId == ""
val distribute = subscription.upAppId != ""
if (notify) {
notifier.send(subscription, notification)
}
if (broadcast) {
broadcaster.send(subscription, notification, muted)
}
if (distribute) {
distributor.sendMessage(subscription.upAppId, subscription.upConnectorToken, notification.message)
}
}
private fun checkNotify(subscription: Subscription, notification: Notification, muted: Boolean): Boolean {
if (subscription.upAppId != "") {
return false
}
val detailsVisible = repository.detailViewSubscriptionId.get() == notification.subscriptionId
return !detailsVisible && !muted
}
private fun checkMuted(subscription: Subscription): Boolean {
if (repository.isGlobalMuted()) {
return true
}
return subscription.mutedUntil == 1L || (subscription.mutedUntil > 1L && subscription.mutedUntil > System.currentTimeMillis()/1000)
}
companion object {

View file

@ -58,10 +58,9 @@ class SubscriberService : Service() {
private var wakeLock: PowerManager.WakeLock? = null
private var isServiceStarted = false
private val repository by lazy { (application as Application).repository }
private val dispatcher by lazy { NotificationDispatcher(this, repository) }
private val connections = ConcurrentHashMap<String, SubscriberConnection>() // Base URL -> Connection
private val api = ApiService()
private val notifier = NotificationService(this)
private val broadcaster = BroadcastService(this)
private var notificationManager: NotificationManager? = null
private var serviceNotification: Notification? = null
@ -201,18 +200,13 @@ class SubscriberService : Service() {
repository.updateState(subscriptionIds, state)
}
private fun onNotificationReceived(subscription: Subscription, n: io.heckel.ntfy.data.Notification) {
private fun onNotificationReceived(subscription: Subscription, notification: io.heckel.ntfy.data.Notification) {
val url = topicUrl(subscription.baseUrl, subscription.topic)
Log.d(TAG, "[$url] Received notification: $n")
Log.d(TAG, "[$url] Received notification: $notification")
GlobalScope.launch(Dispatchers.IO) {
val result = repository.addNotification(n)
if (result.notify) {
Log.d(TAG, "[$url] Showing notification: $n")
notifier.send(subscription, n)
}
if (result.broadcast) {
Log.d(TAG, "[$url] Broadcasting notification: $n")
broadcaster.send(subscription, n, result.muted)
if (repository.addNotification(notification)) {
Log.d(TAG, "[$url] Dispatching notification $notification")
dispatcher.dispatch(subscription, notification)
}
}
}

View file

@ -52,8 +52,6 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
private var actionMode: ActionMode? = null
private var workManager: WorkManager? = null // Context-dependent
private var dispatcher: NotificationDispatcher? = null // Context-dependent
private var notifier: NotificationService? = null // Context-dependent
private var broadcaster: BroadcastService? = null // Context-dependent
private var subscriberManager: SubscriberManager? = null // Context-dependent
private var appBaseUrl: String? = null // Context-dependent
@ -65,9 +63,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
// Dependencies that depend on Context
workManager = WorkManager.getInstance(this)
dispatcher = NotificationDispatcher(this)
notifier = NotificationService(this)
broadcaster = BroadcastService(this)
dispatcher = NotificationDispatcher(this, repository)
subscriberManager = SubscriberManager(this)
appBaseUrl = getString(R.string.app_base_url)
@ -113,7 +109,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
}
// Create notification channels right away, so we can configure them immediately after installing the app
notifier!!.createNotificationChannels()
dispatcher?.init()
// Subscribe to control Firebase channel (so we can re-start the foreground service if it dies)
messenger.subscribe(ApiService.CONTROL_TOPIC)
@ -342,13 +338,8 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
newNotifications.forEach { notification ->
newNotificationsCount++
val notificationWithId = notification.copy(notificationId = Random.nextInt())
val result = repository.addNotification(notificationWithId)
dispatcher?.dispatch()
if (result.notify) {
notifier?.send(subscription, notificationWithId)
}
if (result.broadcast) {
broadcaster?.send(subscription, notification, result.muted)
if (repository.addNotification(notificationWithId)) {
dispatcher?.dispatch(subscription, notificationWithId)
}
}
} catch (e: Exception) {

View file

@ -28,6 +28,7 @@ class BroadcastReceiver : android.content.BroadcastReceiver() {
val topic = connectorToken // FIXME
val app = context!!.applicationContext as Application
val repository = app.repository
val distributor = Distributor(app)
val subscription = Subscription(
id = Random.nextLong(),
baseUrl = baseUrl,
@ -44,14 +45,15 @@ class BroadcastReceiver : android.content.BroadcastReceiver() {
repository.addSubscription(subscription)
}
sendEndpoint(context!!, appId, connectorToken)
distributor.sendEndpoint(appId, connectorToken)
// XXXXXXXXX
}
ACTION_UNREGISTER -> {
val connectorToken = intent.getStringExtra(EXTRA_TOKEN) ?: ""
Log.d(TAG, "Unregister: connectorToken=$connectorToken")
// XXXXXXX
sendUnregistered(context!!, "org.unifiedpush.example", connectorToken)
val distributor = Distributor(context!!)
distributor.sendUnregistered("org.unifiedpush.example", connectorToken)
}
}
}

View file

@ -0,0 +1,36 @@
package io.heckel.ntfy.up
import android.content.Context
import android.content.Intent
import io.heckel.ntfy.R
import io.heckel.ntfy.data.Repository
import io.heckel.ntfy.util.topicUrlUp
class Distributor(val context: Context) {
fun sendMessage(app: String, token: String, message: String) {
val broadcastIntent = Intent()
broadcastIntent.`package` = app
broadcastIntent.action = ACTION_MESSAGE
broadcastIntent.putExtra(EXTRA_TOKEN, token)
broadcastIntent.putExtra(EXTRA_MESSAGE, message)
context.sendBroadcast(broadcastIntent)
}
fun sendEndpoint(app: String, token: String) {
val appBaseUrl = context.getString(R.string.app_base_url) // FIXME
val broadcastIntent = Intent()
broadcastIntent.`package` = app
broadcastIntent.action = ACTION_NEW_ENDPOINT
broadcastIntent.putExtra(EXTRA_TOKEN, token)
broadcastIntent.putExtra(EXTRA_ENDPOINT, topicUrlUp(appBaseUrl, token))
context.sendBroadcast(broadcastIntent)
}
fun sendUnregistered(app: String, token: String) {
val broadcastIntent = Intent()
broadcastIntent.`package` = app
broadcastIntent.action = ACTION_UNREGISTERED
broadcastIntent.putExtra(EXTRA_TOKEN, token)
context.sendBroadcast(broadcastIntent)
}
}

View file

@ -1,34 +0,0 @@
package io.heckel.ntfy.up
import android.content.Context
import android.content.Intent
import io.heckel.ntfy.R
import io.heckel.ntfy.util.topicUrlUp
fun sendMessage(context: Context, app: String, token: String, message: String) {
val broadcastIntent = Intent()
broadcastIntent.`package` = app
broadcastIntent.action = ACTION_MESSAGE
broadcastIntent.putExtra(EXTRA_TOKEN, token)
broadcastIntent.putExtra(EXTRA_MESSAGE, message)
context.sendBroadcast(broadcastIntent)
}
fun sendEndpoint(context: Context, app: String, token: String) {
val appBaseUrl = context.getString(R.string.app_base_url)
val broadcastIntent = Intent()
broadcastIntent.`package` = app
broadcastIntent.action = ACTION_NEW_ENDPOINT
broadcastIntent.putExtra(EXTRA_TOKEN, token)
broadcastIntent.putExtra(EXTRA_ENDPOINT, topicUrlUp(appBaseUrl, token))
context.sendBroadcast(broadcastIntent)
}
fun sendUnregistered(context: Context, app: String, token: String) {
val broadcastIntent = Intent()
broadcastIntent.`package` = app
broadcastIntent.action = ACTION_UNREGISTERED
broadcastIntent.putExtra(EXTRA_TOKEN, token)
context.sendBroadcast(broadcastIntent)
}

View file

@ -7,8 +7,10 @@ import androidx.work.WorkerParameters
import io.heckel.ntfy.BuildConfig
import io.heckel.ntfy.data.Database
import io.heckel.ntfy.data.Repository
import io.heckel.ntfy.firebase.FirebaseService
import io.heckel.ntfy.msg.ApiService
import io.heckel.ntfy.msg.BroadcastService
import io.heckel.ntfy.msg.NotificationDispatcher
import io.heckel.ntfy.msg.NotificationService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -25,8 +27,7 @@ class PollWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx,
val database = Database.getInstance(applicationContext)
val sharedPrefs = applicationContext.getSharedPreferences(Repository.SHARED_PREFS_ID, Context.MODE_PRIVATE)
val repository = Repository.getInstance(sharedPrefs, database.subscriptionDao(), database.notificationDao())
val notifier = NotificationService(applicationContext)
val broadcaster = BroadcastService(applicationContext)
val dispatcher = NotificationDispatcher(applicationContext, repository)
val api = ApiService()
repository.getSubscriptions().forEach{ subscription ->
@ -36,12 +37,8 @@ class PollWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx,
.onlyNewNotifications(subscription.id, notifications)
.map { it.copy(notificationId = Random.nextInt()) }
newNotifications.forEach { notification ->
val result = repository.addNotification(notification)
if (result.notify) {
notifier.send(subscription, notification)
}
if (result.broadcast) {
broadcaster.send(subscription, notification, result.muted)
if (repository.addNotification(notification)) {
dispatcher.dispatch(subscription, notification)
}
}
} catch (e: Exception) {

View file

@ -7,10 +7,7 @@ import com.google.firebase.messaging.RemoteMessage
import io.heckel.ntfy.R
import io.heckel.ntfy.app.Application
import io.heckel.ntfy.data.Notification
import io.heckel.ntfy.msg.ApiService
import io.heckel.ntfy.msg.BroadcastService
import io.heckel.ntfy.msg.NotificationService
import io.heckel.ntfy.msg.SubscriberService
import io.heckel.ntfy.msg.*
import io.heckel.ntfy.util.toPriority
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
@ -19,9 +16,8 @@ import kotlin.random.Random
class FirebaseService : FirebaseMessagingService() {
private val repository by lazy { (application as Application).repository }
private val dispatcher by lazy { NotificationDispatcher(this, repository) }
private val job = SupervisorJob()
private val notifier = NotificationService(this)
private val broadcaster = BroadcastService(this)
private val messenger = FirebaseMessenger()
override fun onMessageReceived(remoteMessage: RemoteMessage) {
@ -81,16 +77,9 @@ class FirebaseService : FirebaseMessagingService() {
tags = tags ?: "",
deleted = false
)
val result = repository.addNotification(notification)
// Send notification (only if it's not already known)
if (result.notify) {
Log.d(TAG, "Sending notification for message: from=${remoteMessage.from}, data=${data}")
notifier.send(subscription, notification)
}
if (result.broadcast) {
Log.d(TAG, "Sending broadcast for message: from=${remoteMessage.from}, data=${data}")
broadcaster.send(subscription, notification, result.muted)
if (repository.addNotification(notification)) {
Log.d(TAG, "Dispatching notification for message: from=${remoteMessage.from}, data=${data}")
dispatcher.dispatch(subscription, notification)
}
}
}