Increase worker intervals; Remove periodic "muted until" checker; redraw main list on "back button" press; polling now uses since=..

This commit is contained in:
Philipp Heckel 2022-01-02 01:37:09 +01:00
parent 91d13bdd13
commit ae1e439f37
8 changed files with 74 additions and 59 deletions

View file

@ -12,8 +12,8 @@ android {
minSdkVersion 21
targetSdkVersion 30
versionCode 13
versionName "1.5.0"
versionCode 14
versionName "1.5.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View file

@ -26,7 +26,7 @@ class ApiService {
.writeTimeout(15, TimeUnit.SECONDS)
.build()
private val subscriberClient = OkHttpClient.Builder()
.readTimeout(77, TimeUnit.SECONDS) // Assuming that keepalive messages are more frequent than this
.readTimeout(5, TimeUnit.MINUTES) // Assuming that keepalive messages are more frequent than this
.build()
fun publish(baseUrl: String, topic: String, message: String, title: String, priority: Int, tags: List<String>, delay: String) {
@ -58,7 +58,12 @@ class ApiService {
}
fun poll(subscriptionId: Long, baseUrl: String, topic: String): List<Notification> {
val url = topicUrlJsonPoll(baseUrl, topic)
return poll(subscriptionId, baseUrl, topic, 0)
}
fun poll(subscriptionId: Long, baseUrl: String, topic: String, since: Long): List<Notification> {
val sinceVal = if (since == 0L) "all" else since.toString()
val url = topicUrlJsonPoll(baseUrl, topic, sinceVal)
Log.d(TAG, "Polling topic $url")
val request = Request.Builder()

View file

@ -286,8 +286,8 @@ class SubscriberService : Service() {
companion object {
const val TAG = "NtfySubscriberService"
const val AUTO_RESTART_WORKER_VERSION = BuildConfig.VERSION_CODE
const val AUTO_RESTART_WORKER_WORK_NAME_PERIODIC = "NtfyAutoRestartWorkerPeriodic"
const val SERVICE_START_WORKER_VERSION = BuildConfig.VERSION_CODE
const val SERVICE_START_WORKER_WORK_NAME_PERIODIC = "NtfyAutoRestartWorkerPeriodic" // Do not change!
private const val WAKE_LOCK_TAG = "SubscriberService:lock"
private const val NOTIFICATION_CHANNEL_ID = "ntfy-subscriber"

View file

@ -6,7 +6,6 @@ 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.
@ -21,7 +20,7 @@ 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()
val startServiceRequest = OneTimeWorkRequest.Builder(ServiceStartWorker::class.java).build()
workManager.enqueue(startServiceRequest)
}
@ -29,10 +28,10 @@ class SubscriberServiceManager(private val context: Context) {
* Starts or stops the foreground service by figuring out how many instant delivery subscriptions
* exist. If there's > 0, then we need a foreground service.
*/
class RefreshWorker(private val context: Context, params: WorkerParameters) : Worker(context, params) {
class ServiceStartWorker(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})")
Log.d(TAG, "ServiceStartWorker: Failed, no application found (work ID: ${this.id})")
return Result.failure()
}
val app = context.applicationContext as Application
@ -43,7 +42,7 @@ class SubscriberServiceManager(private val context: 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})")
Log.d(TAG, "ServiceStartWorker: Starting foreground service with action $action (work ID: ${this.id})")
Intent(context, SubscriberService::class.java).also {
it.action = action.name
ContextCompat.startForegroundService(context, it)

View file

@ -27,10 +27,7 @@ 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
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.random.Random
@ -116,7 +113,13 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
// Background things
startPeriodicPollWorker()
startPeriodicServiceRefreshWorker()
startPeriodicServiceRestartWorker()
}
override fun onResume() {
super.onResume()
showHideNotificationMenuItems()
redrawList()
}
private fun startPeriodicPollWorker() {
@ -132,75 +135,74 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val work = PeriodicWorkRequestBuilder<PollWorker>(MINIMUM_PERIODIC_WORKER_INTERVAL, TimeUnit.MINUTES)
val work = PeriodicWorkRequestBuilder<PollWorker>(POLL_WORKER_INTERVAL_MINUTES, TimeUnit.MINUTES)
.setConstraints(constraints)
.addTag(PollWorker.TAG)
.addTag(PollWorker.WORK_NAME_PERIODIC)
.build()
Log.d(TAG, "Poll worker: Scheduling period work every ${MINIMUM_PERIODIC_WORKER_INTERVAL} minutes")
Log.d(TAG, "Poll worker: Scheduling period work every $POLL_WORKER_INTERVAL_MINUTES minutes")
workManager!!.enqueueUniquePeriodicWork(PollWorker.WORK_NAME_PERIODIC, workPolicy, work)
}
private fun startPeriodicServiceRefreshWorker() {
private fun startPeriodicServiceRestartWorker() {
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")
val workPolicy = if (workerVersion == SubscriberService.SERVICE_START_WORKER_VERSION) {
Log.d(TAG, "ServiceStartWorker version matches: choosing KEEP as existing work policy")
ExistingPeriodicWorkPolicy.KEEP
} else {
Log.d(TAG, "Auto restart worker version DOES NOT MATCH: choosing REPLACE as existing work policy")
repository.setAutoRestartWorkerVersion(SubscriberService.AUTO_RESTART_WORKER_VERSION)
Log.d(TAG, "ServiceStartWorker version DOES NOT MATCH: choosing REPLACE as existing work policy")
repository.setAutoRestartWorkerVersion(SubscriberService.SERVICE_START_WORKER_VERSION)
ExistingPeriodicWorkPolicy.REPLACE
}
val work = PeriodicWorkRequestBuilder<SubscriberServiceManager.RefreshWorker>(MINIMUM_PERIODIC_WORKER_INTERVAL, TimeUnit.MINUTES)
val work = PeriodicWorkRequestBuilder<SubscriberServiceManager.ServiceStartWorker>(SERVICE_START_WORKER_INTERVAL_MINUTES, TimeUnit.MINUTES)
.addTag(SubscriberService.TAG)
.addTag(SubscriberService.AUTO_RESTART_WORKER_WORK_NAME_PERIODIC)
.addTag(SubscriberService.SERVICE_START_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, "ServiceStartWorker: Scheduling period work every $SERVICE_START_WORKER_INTERVAL_MINUTES minutes")
workManager?.enqueueUniquePeriodicWork(SubscriberService.SERVICE_START_WORKER_WORK_NAME_PERIODIC, workPolicy, work)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main_action_bar, menu)
this.menu = menu
showHideNotificationMenuItems()
startNotificationMutedChecker() // This is done here, because then we know that we've initialized the menu
checkSubscriptionsMuted() // This is done here, because then we know that we've initialized the menu
return true
}
private fun startNotificationMutedChecker() {
private fun checkSubscriptionsMuted(delayMillis: Long = 0L) {
lifecycleScope.launch(Dispatchers.IO) {
delay(5000) // Just to be sure we've initialized all the things, we wait a bit ...
while (isActive) {
Log.d(DetailActivity.TAG, "Checking global and subscription-specific 'muted until' timestamp")
delay(delayMillis) // Just to be sure we've initialized all the things, we wait a bit ...
Log.d(TAG, "Checking global and subscription-specific 'muted until' timestamp")
// Check global
val changed = repository.checkGlobalMutedUntil()
if (changed) {
Log.d(TAG, "Global muted until timestamp expired; updating prefs")
showHideNotificationMenuItems()
}
// Check global
val changed = repository.checkGlobalMutedUntil()
if (changed) {
Log.d(TAG, "Global muted until timestamp expired; updating prefs")
showHideNotificationMenuItems()
}
// Check subscriptions
var rerenderList = false
repository.getSubscriptions().forEach { subscription ->
val mutedUntilExpired = subscription.mutedUntil > 1L && System.currentTimeMillis()/1000 > subscription.mutedUntil
if (mutedUntilExpired) {
Log.d(TAG, "Subscription ${subscription.id}: Muted until timestamp expired, updating subscription")
val newSubscription = subscription.copy(mutedUntil = 0L)
repository.updateSubscription(newSubscription)
rerenderList = true
}
// Check subscriptions
var rerenderList = false
repository.getSubscriptions().forEach { subscription ->
val mutedUntilExpired = subscription.mutedUntil > 1L && System.currentTimeMillis()/1000 > subscription.mutedUntil
if (mutedUntilExpired) {
Log.d(TAG, "Subscription ${subscription.id}: Muted until timestamp expired, updating subscription")
val newSubscription = subscription.copy(mutedUntil = 0L)
repository.updateSubscription(newSubscription)
rerenderList = true
}
if (rerenderList) {
redrawList()
}
delay(60_000)
}
if (rerenderList) {
redrawList()
}
}
}
private fun showHideNotificationMenuItems() {
if (!this::menu.isInitialized) {
return
}
val mutedUntilSeconds = repository.getGlobalMutedUntil()
runOnUiThread {
val notificationsEnabledItem = menu.findItem(R.id.main_menu_notifications_enabled)
@ -502,6 +504,9 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
}
private fun redrawList() {
if (!this::mainList.isInitialized) {
return
}
runOnUiThread {
mainList.adapter = adapter // Oh, what a hack ...
}
@ -516,9 +521,11 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
const val EXTRA_SUBSCRIPTION_MUTED_UNTIL = "subscriptionMutedUntil"
const val ANIMATION_DURATION = 80L
// As per Documentation: The minimum repeat interval that can be defined is 15 minutes
// As per documentation: The minimum repeat interval that can be defined is 15 minutes
// (same as the JobScheduler API), but in practice 15 doesn't work. Using 16 here.
// Thanks to varunon9 (https://gist.github.com/varunon9/f2beec0a743c96708eb0ef971a9ff9cd) for this!
const val MINIMUM_PERIODIC_WORKER_INTERVAL = 16L
const val POLL_WORKER_INTERVAL_MINUTES = 2 * 60L
const val SERVICE_START_WORKER_INTERVAL_MINUTES = 6 * 60L
}
}

View file

@ -25,7 +25,7 @@ class SettingsActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
Log.d(MainActivity.TAG, "Create $this")
Log.d(TAG, "Create $this")
if (savedInstanceState == null) {
supportFragmentManager
@ -185,4 +185,8 @@ class SettingsActivity : AppCompatActivity() {
}
}
}
companion object {
const val TAG = "NtfySettingsActivity"
}
}

View file

@ -12,7 +12,7 @@ import java.util.*
fun topicUrl(baseUrl: String, topic: String) = "${baseUrl}/${topic}"
fun topicUrlUp(baseUrl: String, topic: String) = "${baseUrl}/${topic}?up=1" // UnifiedPush
fun topicUrlJson(baseUrl: String, topic: String, since: String) = "${topicUrl(baseUrl, topic)}/json?since=$since"
fun topicUrlJsonPoll(baseUrl: String, topic: String) = "${topicUrl(baseUrl, topic)}/json?poll=1"
fun topicUrlJsonPoll(baseUrl: String, topic: String, since: String) = "${topicUrl(baseUrl, topic)}/json?poll=1&since=$since"
fun topicShortUrl(baseUrl: String, topic: String) =
topicUrl(baseUrl, topic)
.replace("http://", "")

View file

@ -32,7 +32,7 @@ class PollWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx,
repository.getSubscriptions().forEach{ subscription ->
try {
val notifications = api.poll(subscription.id, subscription.baseUrl, subscription.topic)
val notifications = api.poll(subscription.id, subscription.baseUrl, subscription.topic, since = subscription.lastActive)
val newNotifications = repository
.onlyNewNotifications(subscription.id, notifications)
.map { it.copy(notificationId = Random.nextInt()) }
@ -53,6 +53,6 @@ class PollWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx,
companion object {
const val VERSION = BuildConfig.VERSION_CODE
const val TAG = "NtfyPollWorker"
const val WORK_NAME_PERIODIC = "NtfyPollWorkerPeriodic"
const val WORK_NAME_PERIODIC = "NtfyPollWorkerPeriodic" // Do not change
}
}