Increase worker intervals; Remove periodic "muted until" checker; redraw main list on "back button" press; polling now uses since=..
This commit is contained in:
parent
91d13bdd13
commit
ae1e439f37
8 changed files with 74 additions and 59 deletions
|
@ -12,8 +12,8 @@ android {
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
|
|
||||||
versionCode 13
|
versionCode 14
|
||||||
versionName "1.5.0"
|
versionName "1.5.1"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ class ApiService {
|
||||||
.writeTimeout(15, TimeUnit.SECONDS)
|
.writeTimeout(15, TimeUnit.SECONDS)
|
||||||
.build()
|
.build()
|
||||||
private val subscriberClient = OkHttpClient.Builder()
|
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()
|
.build()
|
||||||
|
|
||||||
fun publish(baseUrl: String, topic: String, message: String, title: String, priority: Int, tags: List<String>, delay: String) {
|
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> {
|
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")
|
Log.d(TAG, "Polling topic $url")
|
||||||
|
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
|
|
|
@ -286,8 +286,8 @@ class SubscriberService : Service() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "NtfySubscriberService"
|
const val TAG = "NtfySubscriberService"
|
||||||
const val AUTO_RESTART_WORKER_VERSION = BuildConfig.VERSION_CODE
|
const val SERVICE_START_WORKER_VERSION = BuildConfig.VERSION_CODE
|
||||||
const val AUTO_RESTART_WORKER_WORK_NAME_PERIODIC = "NtfyAutoRestartWorkerPeriodic"
|
const val SERVICE_START_WORKER_WORK_NAME_PERIODIC = "NtfyAutoRestartWorkerPeriodic" // Do not change!
|
||||||
|
|
||||||
private const val WAKE_LOCK_TAG = "SubscriberService:lock"
|
private const val WAKE_LOCK_TAG = "SubscriberService:lock"
|
||||||
private const val NOTIFICATION_CHANNEL_ID = "ntfy-subscriber"
|
private const val NOTIFICATION_CHANNEL_ID = "ntfy-subscriber"
|
||||||
|
|
|
@ -6,7 +6,6 @@ import android.util.Log
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.work.*
|
import androidx.work.*
|
||||||
import io.heckel.ntfy.app.Application
|
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.
|
* 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() {
|
fun refresh() {
|
||||||
Log.d(TAG, "Enqueuing work to refresh subscriber service")
|
Log.d(TAG, "Enqueuing work to refresh subscriber service")
|
||||||
val workManager = WorkManager.getInstance(context)
|
val workManager = WorkManager.getInstance(context)
|
||||||
val startServiceRequest = OneTimeWorkRequest.Builder(RefreshWorker::class.java).build()
|
val startServiceRequest = OneTimeWorkRequest.Builder(ServiceStartWorker::class.java).build()
|
||||||
workManager.enqueue(startServiceRequest)
|
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
|
* 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.
|
* 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 {
|
override fun doWork(): Result {
|
||||||
if (context.applicationContext !is Application) {
|
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()
|
return Result.failure()
|
||||||
}
|
}
|
||||||
val app = context.applicationContext as Application
|
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) {
|
if (serviceState == SubscriberService.ServiceState.STOPPED && action == SubscriberService.Action.STOP) {
|
||||||
return Result.success()
|
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 {
|
Intent(context, SubscriberService::class.java).also {
|
||||||
it.action = action.name
|
it.action = action.name
|
||||||
ContextCompat.startForegroundService(context, it)
|
ContextCompat.startForegroundService(context, it)
|
||||||
|
|
|
@ -27,10 +27,7 @@ import io.heckel.ntfy.service.SubscriberService
|
||||||
import io.heckel.ntfy.service.SubscriberServiceManager
|
import io.heckel.ntfy.service.SubscriberServiceManager
|
||||||
import io.heckel.ntfy.util.fadeStatusBarColor
|
import io.heckel.ntfy.util.fadeStatusBarColor
|
||||||
import io.heckel.ntfy.util.formatDateShort
|
import io.heckel.ntfy.util.formatDateShort
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.isActive
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
@ -116,7 +113,13 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
|
||||||
|
|
||||||
// Background things
|
// Background things
|
||||||
startPeriodicPollWorker()
|
startPeriodicPollWorker()
|
||||||
startPeriodicServiceRefreshWorker()
|
startPeriodicServiceRestartWorker()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
showHideNotificationMenuItems()
|
||||||
|
redrawList()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startPeriodicPollWorker() {
|
private fun startPeriodicPollWorker() {
|
||||||
|
@ -132,75 +135,74 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
|
||||||
val constraints = Constraints.Builder()
|
val constraints = Constraints.Builder()
|
||||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||||
.build()
|
.build()
|
||||||
val work = PeriodicWorkRequestBuilder<PollWorker>(MINIMUM_PERIODIC_WORKER_INTERVAL, TimeUnit.MINUTES)
|
val work = PeriodicWorkRequestBuilder<PollWorker>(POLL_WORKER_INTERVAL_MINUTES, TimeUnit.MINUTES)
|
||||||
.setConstraints(constraints)
|
.setConstraints(constraints)
|
||||||
.addTag(PollWorker.TAG)
|
.addTag(PollWorker.TAG)
|
||||||
.addTag(PollWorker.WORK_NAME_PERIODIC)
|
.addTag(PollWorker.WORK_NAME_PERIODIC)
|
||||||
.build()
|
.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)
|
workManager!!.enqueueUniquePeriodicWork(PollWorker.WORK_NAME_PERIODIC, workPolicy, work)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startPeriodicServiceRefreshWorker() {
|
private fun startPeriodicServiceRestartWorker() {
|
||||||
val workerVersion = repository.getAutoRestartWorkerVersion()
|
val workerVersion = repository.getAutoRestartWorkerVersion()
|
||||||
val workPolicy = if (workerVersion == SubscriberService.AUTO_RESTART_WORKER_VERSION) {
|
val workPolicy = if (workerVersion == SubscriberService.SERVICE_START_WORKER_VERSION) {
|
||||||
Log.d(TAG, "Auto restart worker version matches: choosing KEEP as existing work policy")
|
Log.d(TAG, "ServiceStartWorker version matches: choosing KEEP as existing work policy")
|
||||||
ExistingPeriodicWorkPolicy.KEEP
|
ExistingPeriodicWorkPolicy.KEEP
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "Auto restart worker version DOES NOT MATCH: choosing REPLACE as existing work policy")
|
Log.d(TAG, "ServiceStartWorker version DOES NOT MATCH: choosing REPLACE as existing work policy")
|
||||||
repository.setAutoRestartWorkerVersion(SubscriberService.AUTO_RESTART_WORKER_VERSION)
|
repository.setAutoRestartWorkerVersion(SubscriberService.SERVICE_START_WORKER_VERSION)
|
||||||
ExistingPeriodicWorkPolicy.REPLACE
|
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.TAG)
|
||||||
.addTag(SubscriberService.AUTO_RESTART_WORKER_WORK_NAME_PERIODIC)
|
.addTag(SubscriberService.SERVICE_START_WORKER_WORK_NAME_PERIODIC)
|
||||||
.build()
|
.build()
|
||||||
Log.d(TAG, "Auto restart worker: Scheduling period work every $MINIMUM_PERIODIC_WORKER_INTERVAL minutes")
|
Log.d(TAG, "ServiceStartWorker: Scheduling period work every $SERVICE_START_WORKER_INTERVAL_MINUTES minutes")
|
||||||
workManager?.enqueueUniquePeriodicWork(SubscriberService.AUTO_RESTART_WORKER_WORK_NAME_PERIODIC, workPolicy, work)
|
workManager?.enqueueUniquePeriodicWork(SubscriberService.SERVICE_START_WORKER_WORK_NAME_PERIODIC, workPolicy, work)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.menu_main_action_bar, menu)
|
menuInflater.inflate(R.menu.menu_main_action_bar, menu)
|
||||||
this.menu = menu
|
this.menu = menu
|
||||||
showHideNotificationMenuItems()
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startNotificationMutedChecker() {
|
private fun checkSubscriptionsMuted(delayMillis: Long = 0L) {
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
delay(5000) // Just to be sure we've initialized all the things, we wait a bit ...
|
delay(delayMillis) // Just to be sure we've initialized all the things, we wait a bit ...
|
||||||
while (isActive) {
|
Log.d(TAG, "Checking global and subscription-specific 'muted until' timestamp")
|
||||||
Log.d(DetailActivity.TAG, "Checking global and subscription-specific 'muted until' timestamp")
|
|
||||||
|
|
||||||
// Check global
|
// Check global
|
||||||
val changed = repository.checkGlobalMutedUntil()
|
val changed = repository.checkGlobalMutedUntil()
|
||||||
if (changed) {
|
if (changed) {
|
||||||
Log.d(TAG, "Global muted until timestamp expired; updating prefs")
|
Log.d(TAG, "Global muted until timestamp expired; updating prefs")
|
||||||
showHideNotificationMenuItems()
|
showHideNotificationMenuItems()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check subscriptions
|
// Check subscriptions
|
||||||
var rerenderList = false
|
var rerenderList = false
|
||||||
repository.getSubscriptions().forEach { subscription ->
|
repository.getSubscriptions().forEach { subscription ->
|
||||||
val mutedUntilExpired = subscription.mutedUntil > 1L && System.currentTimeMillis()/1000 > subscription.mutedUntil
|
val mutedUntilExpired = subscription.mutedUntil > 1L && System.currentTimeMillis()/1000 > subscription.mutedUntil
|
||||||
if (mutedUntilExpired) {
|
if (mutedUntilExpired) {
|
||||||
Log.d(TAG, "Subscription ${subscription.id}: Muted until timestamp expired, updating subscription")
|
Log.d(TAG, "Subscription ${subscription.id}: Muted until timestamp expired, updating subscription")
|
||||||
val newSubscription = subscription.copy(mutedUntil = 0L)
|
val newSubscription = subscription.copy(mutedUntil = 0L)
|
||||||
repository.updateSubscription(newSubscription)
|
repository.updateSubscription(newSubscription)
|
||||||
rerenderList = true
|
rerenderList = true
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (rerenderList) {
|
}
|
||||||
redrawList()
|
if (rerenderList) {
|
||||||
}
|
redrawList()
|
||||||
|
|
||||||
delay(60_000)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showHideNotificationMenuItems() {
|
private fun showHideNotificationMenuItems() {
|
||||||
|
if (!this::menu.isInitialized) {
|
||||||
|
return
|
||||||
|
}
|
||||||
val mutedUntilSeconds = repository.getGlobalMutedUntil()
|
val mutedUntilSeconds = repository.getGlobalMutedUntil()
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
val notificationsEnabledItem = menu.findItem(R.id.main_menu_notifications_enabled)
|
val notificationsEnabledItem = menu.findItem(R.id.main_menu_notifications_enabled)
|
||||||
|
@ -502,6 +504,9 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun redrawList() {
|
private fun redrawList() {
|
||||||
|
if (!this::mainList.isInitialized) {
|
||||||
|
return
|
||||||
|
}
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
mainList.adapter = adapter // Oh, what a hack ...
|
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 EXTRA_SUBSCRIPTION_MUTED_UNTIL = "subscriptionMutedUntil"
|
||||||
const val ANIMATION_DURATION = 80L
|
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.
|
// (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!
|
// 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ class SettingsActivity : AppCompatActivity() {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_settings)
|
setContentView(R.layout.activity_settings)
|
||||||
|
|
||||||
Log.d(MainActivity.TAG, "Create $this")
|
Log.d(TAG, "Create $this")
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
supportFragmentManager
|
supportFragmentManager
|
||||||
|
@ -185,4 +185,8 @@ class SettingsActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = "NtfySettingsActivity"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import java.util.*
|
||||||
fun topicUrl(baseUrl: String, topic: String) = "${baseUrl}/${topic}"
|
fun topicUrl(baseUrl: String, topic: String) = "${baseUrl}/${topic}"
|
||||||
fun topicUrlUp(baseUrl: String, topic: String) = "${baseUrl}/${topic}?up=1" // UnifiedPush
|
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 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) =
|
fun topicShortUrl(baseUrl: String, topic: String) =
|
||||||
topicUrl(baseUrl, topic)
|
topicUrl(baseUrl, topic)
|
||||||
.replace("http://", "")
|
.replace("http://", "")
|
||||||
|
|
|
@ -32,7 +32,7 @@ class PollWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx,
|
||||||
|
|
||||||
repository.getSubscriptions().forEach{ subscription ->
|
repository.getSubscriptions().forEach{ subscription ->
|
||||||
try {
|
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
|
val newNotifications = repository
|
||||||
.onlyNewNotifications(subscription.id, notifications)
|
.onlyNewNotifications(subscription.id, notifications)
|
||||||
.map { it.copy(notificationId = Random.nextInt()) }
|
.map { it.copy(notificationId = Random.nextInt()) }
|
||||||
|
@ -53,6 +53,6 @@ class PollWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx,
|
||||||
companion object {
|
companion object {
|
||||||
const val VERSION = BuildConfig.VERSION_CODE
|
const val VERSION = BuildConfig.VERSION_CODE
|
||||||
const val TAG = "NtfyPollWorker"
|
const val TAG = "NtfyPollWorker"
|
||||||
const val WORK_NAME_PERIODIC = "NtfyPollWorkerPeriodic"
|
const val WORK_NAME_PERIODIC = "NtfyPollWorkerPeriodic" // Do not change
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue