From 7e9da287049656f9ca879b71bf1c2d97b268e6a5 Mon Sep 17 00:00:00 2001 From: Philipp Heckel Date: Wed, 29 Dec 2021 21:36:47 +0100 Subject: [PATCH] Forward messages, don't show ntfy notification --- .../java/io/heckel/ntfy/data/Repository.kt | 23 ++------ .../heckel/ntfy/msg/NotificationDispatcher.kt | 55 +++++++++++++------ .../io/heckel/ntfy/msg/SubscriberService.kt | 18 ++---- .../java/io/heckel/ntfy/ui/MainActivity.kt | 17 ++---- .../io/heckel/ntfy/up/BroadcastReceiver.kt | 6 +- .../java/io/heckel/ntfy/up/Distributor.kt | 36 ++++++++++++ .../io/heckel/ntfy/up/DistributorUtils.kt | 34 ------------ .../java/io/heckel/ntfy/work/PollWorker.kt | 13 ++--- .../heckel/ntfy/firebase/FirebaseService.kt | 21 ++----- 9 files changed, 104 insertions(+), 119 deletions(-) create mode 100644 app/src/main/java/io/heckel/ntfy/up/Distributor.kt delete mode 100644 app/src/main/java/io/heckel/ntfy/up/DistributorUtils.kt diff --git a/app/src/main/java/io/heckel/ntfy/data/Repository.kt b/app/src/main/java/io/heckel/ntfy/data/Repository.kt index 5caacd4..e953911 100644 --- a/app/src/main/java/io/heckel/ntfy/data/Repository.kt +++ b/app/src/main/java/io/heckel/ntfy/data/Repository.kt @@ -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" diff --git a/app/src/main/java/io/heckel/ntfy/msg/NotificationDispatcher.kt b/app/src/main/java/io/heckel/ntfy/msg/NotificationDispatcher.kt index f981e05..d4bade2 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/NotificationDispatcher.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationDispatcher.kt @@ -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 { diff --git a/app/src/main/java/io/heckel/ntfy/msg/SubscriberService.kt b/app/src/main/java/io/heckel/ntfy/msg/SubscriberService.kt index 8a0add5..90187bd 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/SubscriberService.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/SubscriberService.kt @@ -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() // 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) } } } diff --git a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt index a4d03b6..7bd46c6 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt @@ -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) { diff --git a/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt b/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt index 1fa8c61..7e56035 100644 --- a/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt +++ b/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt @@ -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) } } } diff --git a/app/src/main/java/io/heckel/ntfy/up/Distributor.kt b/app/src/main/java/io/heckel/ntfy/up/Distributor.kt new file mode 100644 index 0000000..975ab58 --- /dev/null +++ b/app/src/main/java/io/heckel/ntfy/up/Distributor.kt @@ -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) + } +} diff --git a/app/src/main/java/io/heckel/ntfy/up/DistributorUtils.kt b/app/src/main/java/io/heckel/ntfy/up/DistributorUtils.kt deleted file mode 100644 index a7ba475..0000000 --- a/app/src/main/java/io/heckel/ntfy/up/DistributorUtils.kt +++ /dev/null @@ -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) -} - diff --git a/app/src/main/java/io/heckel/ntfy/work/PollWorker.kt b/app/src/main/java/io/heckel/ntfy/work/PollWorker.kt index 8828e23..43e8003 100644 --- a/app/src/main/java/io/heckel/ntfy/work/PollWorker.kt +++ b/app/src/main/java/io/heckel/ntfy/work/PollWorker.kt @@ -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) { diff --git a/app/src/play/java/io/heckel/ntfy/firebase/FirebaseService.kt b/app/src/play/java/io/heckel/ntfy/firebase/FirebaseService.kt index 61caac9..1df310c 100644 --- a/app/src/play/java/io/heckel/ntfy/firebase/FirebaseService.kt +++ b/app/src/play/java/io/heckel/ntfy/firebase/FirebaseService.kt @@ -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) } } }