diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1beaca1..2fbf176 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -44,17 +44,17 @@
         </activity>
 
         <!-- Subscriber foreground service for hosts other than ntfy.sh -->
-        <service android:name=".msg.SubscriberService"/>
+        <service android:name=".service.SubscriberService"/>
 
         <!-- Subscriber service restart on reboot -->
-        <receiver android:name=".msg.SubscriberService$BootStartReceiver" android:enabled="true">
+        <receiver android:name=".service.SubscriberService$BootStartReceiver" android:enabled="true">
             <intent-filter>
                 <action android:name="android.intent.action.BOOT_COMPLETED"/>
             </intent-filter>
         </receiver>
 
         <!-- Subscriber service restart on destruction -->
-        <receiver android:name=".msg.SubscriberService$AutoRestartReceiver" android:enabled="true"
+        <receiver android:name=".service.SubscriberService$AutoRestartReceiver" android:enabled="true"
                   android:exported="false"/>
 
         <!-- Broadcast receiver to send messages via intents -->
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 aa251cd..18eb5b1 100644
--- a/app/src/main/java/io/heckel/ntfy/msg/NotificationDispatcher.kt
+++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationDispatcher.kt
@@ -35,7 +35,7 @@ class NotificationDispatcher(val context: Context, val repository: Repository) {
     }
 
     private fun checkNotify(subscription: Subscription, notification: Notification, muted: Boolean): Boolean {
-        if (subscription.upAppId != "") {
+        if (subscription.upAppId != null) {
             return false
         }
         val detailsVisible = repository.detailViewSubscriptionId.get() == notification.subscriptionId
diff --git a/app/src/main/java/io/heckel/ntfy/msg/SubscriberConnection.kt b/app/src/main/java/io/heckel/ntfy/service/SubscriberConnection.kt
similarity index 98%
rename from app/src/main/java/io/heckel/ntfy/msg/SubscriberConnection.kt
rename to app/src/main/java/io/heckel/ntfy/service/SubscriberConnection.kt
index e26cd68..e8ae782 100644
--- a/app/src/main/java/io/heckel/ntfy/msg/SubscriberConnection.kt
+++ b/app/src/main/java/io/heckel/ntfy/service/SubscriberConnection.kt
@@ -1,9 +1,10 @@
-package io.heckel.ntfy.msg
+package io.heckel.ntfy.service
 
 import android.util.Log
 import io.heckel.ntfy.data.ConnectionState
 import io.heckel.ntfy.data.Notification
 import io.heckel.ntfy.data.Subscription
+import io.heckel.ntfy.msg.ApiService
 import io.heckel.ntfy.util.topicUrl
 import kotlinx.coroutines.*
 import okhttp3.Call
diff --git a/app/src/main/java/io/heckel/ntfy/msg/SubscriberService.kt b/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt
similarity index 89%
rename from app/src/main/java/io/heckel/ntfy/msg/SubscriberService.kt
rename to app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt
index 90187bd..d000268 100644
--- a/app/src/main/java/io/heckel/ntfy/msg/SubscriberService.kt
+++ b/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt
@@ -1,4 +1,4 @@
-package io.heckel.ntfy.msg
+package io.heckel.ntfy.service
 
 import android.app.*
 import android.content.BroadcastReceiver
@@ -11,8 +11,6 @@ import android.os.SystemClock
 import android.util.Log
 import androidx.core.app.NotificationCompat
 import androidx.core.content.ContextCompat
-import androidx.work.OneTimeWorkRequest
-import androidx.work.WorkManager
 import androidx.work.Worker
 import androidx.work.WorkerParameters
 import io.heckel.ntfy.BuildConfig
@@ -20,6 +18,8 @@ import io.heckel.ntfy.R
 import io.heckel.ntfy.app.Application
 import io.heckel.ntfy.data.ConnectionState
 import io.heckel.ntfy.data.Subscription
+import io.heckel.ntfy.msg.ApiService
+import io.heckel.ntfy.msg.NotificationDispatcher
 import io.heckel.ntfy.ui.MainActivity
 import io.heckel.ntfy.util.topicUrl
 import kotlinx.coroutines.*
@@ -70,8 +70,8 @@ class SubscriberService : Service() {
             val action = intent.action
             Log.d(TAG, "using an intent with action $action")
             when (action) {
-                Actions.START.name -> startService()
-                Actions.STOP.name -> stopService()
+                Action.START.name -> startService()
+                Action.STOP.name -> stopService()
                 else -> Log.e(TAG, "This should never happen. No action in the received intent")
             }
         } else {
@@ -259,13 +259,7 @@ class SubscriberService : Service() {
     class BootStartReceiver : BroadcastReceiver() {
         override fun onReceive(context: Context, intent: Intent) {
             Log.d(TAG, "BootStartReceiver: onReceive called")
-            if (intent.action == Intent.ACTION_BOOT_COMPLETED && readServiceState(context) == ServiceState.STARTED) {
-                Intent(context, SubscriberService::class.java).also {
-                    it.action = Actions.START.name
-                    Log.d(TAG, "BootStartReceiver: Starting subscriber service")
-                    ContextCompat.startForegroundService(context, it)
-                }
-            }
+            SubscriberServiceManager.refresh(context)
         }
     }
 
@@ -276,27 +270,11 @@ class SubscriberService : Service() {
     class AutoRestartReceiver : BroadcastReceiver() {
         override fun onReceive(context: Context, intent: Intent) {
             Log.d(TAG, "AutoRestartReceiver: onReceive called")
-            val workManager = WorkManager.getInstance(context)
-            val startServiceRequest = OneTimeWorkRequest.Builder(AutoRestartWorker::class.java).build()
-            workManager.enqueue(startServiceRequest)
+            SubscriberServiceManager.refresh(context)
         }
     }
 
-    class AutoRestartWorker(private val context: Context, params: WorkerParameters) : Worker(context, params) {
-        override fun doWork(): Result {
-            Log.d(TAG, "AutoRestartReceiver: doWork called for: " + this.getId())
-            if (readServiceState(context) == ServiceState.STARTED) {
-                Intent(context, SubscriberService::class.java).also {
-                    it.action = Actions.START.name
-                    Log.d(TAG, "AutoRestartReceiver: Starting subscriber service")
-                    ContextCompat.startForegroundService(context, it)
-                }
-            }
-            return Result.success()
-        }
-    }
-
-    enum class Actions {
+    enum class Action {
         START,
         STOP
     }
diff --git a/app/src/main/java/io/heckel/ntfy/service/SubscriberServiceManager.kt b/app/src/main/java/io/heckel/ntfy/service/SubscriberServiceManager.kt
new file mode 100644
index 0000000..e3d6174
--- /dev/null
+++ b/app/src/main/java/io/heckel/ntfy/service/SubscriberServiceManager.kt
@@ -0,0 +1,54 @@
+package io.heckel.ntfy.service
+
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import androidx.core.content.ContextCompat
+import androidx.work.*
+import io.heckel.ntfy.app.Application
+import io.heckel.ntfy.up.BroadcastReceiver
+
+/**
+ * This class only manages the SubscriberService, i.e. it starts or stops it.
+ * It's used in multiple activities.
+ */
+class SubscriberServiceManager(private val context: Context) {
+    fun refresh() {
+        Log.d(TAG, "Enqueuing work to refresh subscriber service")
+        val workManager = WorkManager.getInstance(context)
+        val startServiceRequest = OneTimeWorkRequest.Builder(RefreshWorker::class.java).build()
+        workManager.enqueue(startServiceRequest)
+    }
+
+    class RefreshWorker(private val context: Context, params: WorkerParameters) : Worker(context, params) {
+        override fun doWork(): Result {
+            if (context.applicationContext !is Application) {
+                Log.d(TAG, "RefreshWorker: Failed, no application found (work ID: ${this.id})")
+                return Result.failure()
+            }
+            val app = context.applicationContext as Application
+            val subscriptionIdsWithInstantStatus = app.repository.getSubscriptionIdsWithInstantStatus()
+            val instantSubscriptions = subscriptionIdsWithInstantStatus.toList().filter { (_, instant) -> instant }.size
+            val action = if (instantSubscriptions > 0) SubscriberService.Action.START else SubscriberService.Action.STOP
+            val serviceState = SubscriberService.readServiceState(context)
+            if (serviceState == SubscriberService.ServiceState.STOPPED && action == SubscriberService.Action.STOP) {
+                return Result.success()
+            }
+            Log.d(TAG, "RefreshWorker: Starting foreground service with action $action (work ID: ${this.id})")
+            Intent(context, SubscriberService::class.java).also {
+                it.action = action.name
+                ContextCompat.startForegroundService(context, it)
+            }
+            return Result.success()
+        }
+    }
+
+    companion object {
+        const val TAG = "NtfySubscriberMgr"
+
+        fun refresh(context: Context) {
+            val manager = SubscriberServiceManager(context)
+            manager.refresh()
+        }
+    }
+}
diff --git a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt
index 9757c1f..4477a1b 100644
--- a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt
+++ b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt
@@ -4,9 +4,6 @@ import android.app.AlertDialog
 import android.content.ClipData
 import android.content.ClipboardManager
 import android.content.Context
-import android.content.Intent
-import android.content.Intent.ACTION_VIEW
-import android.net.Uri
 import android.os.Bundle
 import android.text.Html
 import android.util.Log
@@ -26,12 +23,12 @@ import io.heckel.ntfy.BuildConfig
 import io.heckel.ntfy.R
 import io.heckel.ntfy.app.Application
 import io.heckel.ntfy.data.Notification
-import io.heckel.ntfy.data.Subscription
 import io.heckel.ntfy.util.topicShortUrl
 import io.heckel.ntfy.util.topicUrl
 import io.heckel.ntfy.firebase.FirebaseMessenger
 import io.heckel.ntfy.msg.ApiService
 import io.heckel.ntfy.msg.NotificationService
+import io.heckel.ntfy.service.SubscriberServiceManager
 import io.heckel.ntfy.util.fadeStatusBarColor
 import io.heckel.ntfy.util.formatDateShort
 import kotlinx.coroutines.*
@@ -45,7 +42,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
     private val repository by lazy { (application as Application).repository }
     private val api = ApiService()
     private val messenger = FirebaseMessenger()
-    private var subscriberManager: SubscriberManager? = null // Context-dependent
+    private var serviceManager: SubscriberServiceManager? = null // Context-dependent
     private var notifier: NotificationService? = null // Context-dependent
     private var appBaseUrl: String? = null // Context-dependent
 
@@ -72,7 +69,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
         Log.d(MainActivity.TAG, "Create $this")
 
         // Dependencies that depend on Context
-        subscriberManager = SubscriberManager(this)
+        serviceManager = SubscriberServiceManager(this)
         notifier = NotificationService(this)
         appBaseUrl = getString(R.string.app_base_url)
 
@@ -149,7 +146,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
 
         // React to changes in fast delivery setting
         repository.getSubscriptionIdsWithInstantStatusLiveData().observe(this) {
-            subscriberManager?.refreshService(it)
+            serviceManager?.refresh()
         }
 
         // Mark this subscription as "open" so we don't receive notifications for it
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 01222fa..9829aa4 100644
--- a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt
+++ b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt
@@ -23,6 +23,8 @@ import io.heckel.ntfy.util.topicShortUrl
 import io.heckel.ntfy.work.PollWorker
 import io.heckel.ntfy.firebase.FirebaseMessenger
 import io.heckel.ntfy.msg.*
+import io.heckel.ntfy.service.SubscriberService
+import io.heckel.ntfy.service.SubscriberServiceManager
 import io.heckel.ntfy.util.fadeStatusBarColor
 import io.heckel.ntfy.util.formatDateShort
 import kotlinx.coroutines.Dispatchers
@@ -52,7 +54,7 @@ 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 subscriberManager: SubscriberManager? = null // Context-dependent
+    private var serviceManager: SubscriberServiceManager? = null // Context-dependent
     private var appBaseUrl: String? = null // Context-dependent
 
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -64,7 +66,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
         // Dependencies that depend on Context
         workManager = WorkManager.getInstance(this)
         dispatcher = NotificationDispatcher(this, repository)
-        subscriberManager = SubscriberManager(this)
+        serviceManager = SubscriberServiceManager(this)
         appBaseUrl = getString(R.string.app_base_url)
 
         // Action bar
@@ -105,7 +107,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
 
         // React to changes in instant delivery setting
         viewModel.listIdsWithInstantStatus().observe(this) {
-            subscriberManager?.refreshService(it)
+            serviceManager?.refresh()
         }
 
         // Create notification channels right away, so we can configure them immediately after installing the app
@@ -116,7 +118,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
 
         // Background things
         startPeriodicPollWorker()
-        startPeriodicAutoRestartWorker()
+        startPeriodicServiceRefreshWorker()
     }
 
     private fun startPeriodicPollWorker() {
@@ -141,7 +143,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
         workManager!!.enqueueUniquePeriodicWork(PollWorker.WORK_NAME_PERIODIC, workPolicy, work)
     }
 
-    private fun startPeriodicAutoRestartWorker() {
+    private fun startPeriodicServiceRefreshWorker() {
         val workerVersion = repository.getAutoRestartWorkerVersion()
         val workPolicy = if (workerVersion == SubscriberService.AUTO_RESTART_WORKER_VERSION) {
             Log.d(TAG, "Auto restart worker version matches: choosing KEEP as existing work policy")
@@ -151,12 +153,12 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
             repository.setAutoRestartWorkerVersion(SubscriberService.AUTO_RESTART_WORKER_VERSION)
             ExistingPeriodicWorkPolicy.REPLACE
         }
-        val work = PeriodicWorkRequestBuilder<SubscriberService.AutoRestartWorker>(MINIMUM_PERIODIC_WORKER_INTERVAL, TimeUnit.MINUTES)
+        val work = PeriodicWorkRequestBuilder<SubscriberServiceManager.RefreshWorker>(MINIMUM_PERIODIC_WORKER_INTERVAL, TimeUnit.MINUTES)
             .addTag(SubscriberService.TAG)
             .addTag(SubscriberService.AUTO_RESTART_WORKER_WORK_NAME_PERIODIC)
             .build()
-        Log.d(TAG, "Auto restart worker: Scheduling period work every ${MINIMUM_PERIODIC_WORKER_INTERVAL} minutes")
-        workManager!!.enqueueUniquePeriodicWork(SubscriberService.AUTO_RESTART_WORKER_WORK_NAME_PERIODIC, workPolicy, work)
+        Log.d(TAG, "Auto restart worker: Scheduling period work every $MINIMUM_PERIODIC_WORKER_INTERVAL minutes")
+        workManager?.enqueueUniquePeriodicWork(SubscriberService.AUTO_RESTART_WORKER_WORK_NAME_PERIODIC, workPolicy, work)
     }
 
     override fun onCreateOptionsMenu(menu: Menu): Boolean {
@@ -323,7 +325,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
 
     private fun displayUnifiedPushToast(subscription: Subscription) {
         runOnUiThread {
-            val appId = subscription.upAppId ?: ""
+            val appId = subscription.upAppId ?: return@runOnUiThread
             val toastMessage = getString(R.string.main_unified_push_toast, appId)
             Toast.makeText(this@MainActivity, toastMessage, Toast.LENGTH_LONG).show()
         }
diff --git a/app/src/main/java/io/heckel/ntfy/ui/SubscriberManager.kt b/app/src/main/java/io/heckel/ntfy/ui/SubscriberManager.kt
deleted file mode 100644
index 141096e..0000000
--- a/app/src/main/java/io/heckel/ntfy/ui/SubscriberManager.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-package io.heckel.ntfy.ui
-
-import android.content.Context
-import android.content.Intent
-import android.os.Build
-import android.util.Log
-import androidx.activity.ComponentActivity
-import androidx.lifecycle.lifecycleScope
-import io.heckel.ntfy.msg.SubscriberService
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.launch
-
-/**
- * This class only manages the SubscriberService, i.e. it starts or stops it.
- * It's used in multiple activities.
- */
-class SubscriberManager(private val context: Context) {
-    fun refreshService(subscriptionIdsWithInstantStatus: Set<Pair<Long, Boolean>>) { // Set<SubscriptionId -> IsInstant>
-        Log.d(MainActivity.TAG, "Triggering subscriber service refresh")
-        GlobalScope.launch(Dispatchers.IO) {
-            val instantSubscriptions = subscriptionIdsWithInstantStatus.toList().filter { (_, instant) -> instant }.size
-            if (instantSubscriptions == 0) {
-                performActionOnSubscriberService(SubscriberService.Actions.STOP)
-            } else {
-                performActionOnSubscriberService(SubscriberService.Actions.START)
-            }
-        }
-    }
-
-    private fun performActionOnSubscriberService(action: SubscriberService.Actions) {
-        val serviceState = SubscriberService.readServiceState(context)
-        if (serviceState == SubscriberService.ServiceState.STOPPED && action == SubscriberService.Actions.STOP) {
-            return
-        }
-        val intent = Intent(context, SubscriberService::class.java)
-        intent.action = action.name
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-            Log.d(MainActivity.TAG, "Performing SubscriberService action: ${action.name} (as foreground service, API >= 26)")
-            context.startForegroundService(intent)
-        } else {
-            Log.d(MainActivity.TAG, "Performing SubscriberService action: ${action.name} (as background service, API >= 26)")
-            context.startService(intent)
-        }
-    }
-}
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 74fba12..345ca31 100644
--- a/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt
+++ b/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt
@@ -7,7 +7,7 @@ import io.heckel.ntfy.R
 import io.heckel.ntfy.app.Application
 import io.heckel.ntfy.data.Repository
 import io.heckel.ntfy.data.Subscription
-import io.heckel.ntfy.ui.SubscriberManager
+import io.heckel.ntfy.service.SubscriberServiceManager
 import io.heckel.ntfy.util.randomString
 import io.heckel.ntfy.util.topicUrlUp
 import kotlinx.coroutines.Dispatchers
@@ -52,6 +52,8 @@ class BroadcastReceiver : android.content.BroadcastReceiver() {
                 }
                 return@launch
             }
+
+            // Add subscription
             val baseUrl = context.getString(R.string.app_base_url) // FIXME
             val topic = UP_PREFIX + randomString(TOPIC_LENGTH)
             val endpoint = topicUrlUp(baseUrl, topic)
@@ -68,13 +70,12 @@ class BroadcastReceiver : android.content.BroadcastReceiver() {
                 lastActive = Date().time/1000
             )
 
-            // Add subscription
             Log.d(TAG, "Adding subscription with for app $appId (connectorToken $connectorToken): $subscription")
             repository.addSubscription(subscription)
             distributor.sendEndpoint(appId, connectorToken, endpoint)
 
             // Refresh (and maybe start) foreground service
-            refreshSubscriberService(app, repository)
+            SubscriberServiceManager.refresh(app)
         }
     }
 
@@ -97,17 +98,10 @@ class BroadcastReceiver : android.content.BroadcastReceiver() {
             existingSubscription.upAppId?.let { appId -> distributor.sendUnregistered(appId, connectorToken) }
 
             // Refresh (and maybe stop) foreground service
-            refreshSubscriberService(app, repository)
+            SubscriberServiceManager.refresh(context)
         }
     }
 
-    private fun refreshSubscriberService(context: Context, repository: Repository) {
-        Log.d(TAG, "Refreshing subscriber service")
-        val subscriptionIdsWithInstantStatus = repository.getSubscriptionIdsWithInstantStatus()
-        val subscriberManager = SubscriberManager(context)
-        subscriberManager.refreshService(subscriptionIdsWithInstantStatus)
-    }
-
     companion object {
         private const val TAG = "NtfyUpBroadcastRecv"
         private const val UP_PREFIX = "up"
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 1df310c..a06ea2c 100644
--- a/app/src/play/java/io/heckel/ntfy/firebase/FirebaseService.kt
+++ b/app/src/play/java/io/heckel/ntfy/firebase/FirebaseService.kt
@@ -8,6 +8,7 @@ import io.heckel.ntfy.R
 import io.heckel.ntfy.app.Application
 import io.heckel.ntfy.data.Notification
 import io.heckel.ntfy.msg.*
+import io.heckel.ntfy.service.SubscriberService
 import io.heckel.ntfy.util.toPriority
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.SupervisorJob