Loading app/src/main/java/io/heckel/ntfy/data/Repository.kt +6 −17 Original line number Diff line number Diff line Loading @@ -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") Loading Loading @@ -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) } Loading Loading @@ -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" Loading app/src/main/java/io/heckel/ntfy/msg/NotificationDispatcher.kt +38 −17 Original line number Diff line number Diff line 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 { Loading app/src/main/java/io/heckel/ntfy/msg/SubscriberService.kt +6 −12 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) } } } Loading app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +4 −13 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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) Loading Loading @@ -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) Loading Loading @@ -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) { Loading app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt +4 −2 Original line number Diff line number Diff line Loading @@ -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, Loading @@ -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) } } } Loading Loading
app/src/main/java/io/heckel/ntfy/data/Repository.kt +6 −17 Original line number Diff line number Diff line Loading @@ -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") Loading Loading @@ -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) } Loading Loading @@ -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" Loading
app/src/main/java/io/heckel/ntfy/msg/NotificationDispatcher.kt +38 −17 Original line number Diff line number Diff line 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 { Loading
app/src/main/java/io/heckel/ntfy/msg/SubscriberService.kt +6 −12 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) } } } Loading
app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +4 −13 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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) Loading Loading @@ -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) Loading Loading @@ -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) { Loading
app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt +4 −2 Original line number Diff line number Diff line Loading @@ -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, Loading @@ -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) } } } Loading