Refactor notification scheduling

* Remove foreground service
* Use expedited work to trigger notifications
* Remove miscellaneous notification channel
pull/2872/head
Alex Baker 2 weeks ago
parent 95c351e9fd
commit 3cd0295b71

@ -19,7 +19,6 @@ import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.jobs.AlarmEntry
import org.tasks.jobs.NotificationQueue
import org.tasks.makers.TaskMaker.COMPLETION_TIME
import org.tasks.makers.TaskMaker.DELETION_TIME
import org.tasks.makers.TaskMaker.DUE_DATE
@ -34,7 +33,6 @@ import javax.inject.Inject
class AlarmJobServiceTest : InjectingTestCase() {
@Inject lateinit var alarmDao: AlarmDao
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var jobs: NotificationQueue
@Inject lateinit var alarmService: AlarmService
@Test
@ -42,7 +40,7 @@ class AlarmJobServiceTest : InjectingTestCase() {
val task = taskDao.createNew(newTask())
val alarm = insertAlarm(Alarm(task, DateTime(2017, 9, 24, 19, 57).millis, TYPE_DATE_TIME))
verify(AlarmEntry(alarm, task, DateTime(2017, 9, 24, 19, 57).millis, TYPE_DATE_TIME))
verify(overdue = listOf(AlarmEntry(alarm, task, DateTime(2017, 9, 24, 19, 57).millis, TYPE_DATE_TIME)))
}
@Test
@ -90,7 +88,7 @@ class AlarmJobServiceTest : InjectingTestCase() {
alarmDao.insert(Alarm(task, DateUtilities.ONE_HOUR, TYPE_RANDOM))
val alarm = alarmDao.insert(Alarm(task, now.plusMonths(12).millis, TYPE_SNOOZE))
verify(AlarmEntry(alarm, task, now.plusMonths(12).millis, TYPE_SNOOZE))
verify(future = listOf(AlarmEntry(alarm, task, now.plusMonths(12).millis, TYPE_SNOOZE)))
}
private suspend fun insertAlarm(alarm: Alarm): Long {
@ -98,9 +96,13 @@ class AlarmJobServiceTest : InjectingTestCase() {
return alarm.id
}
private suspend fun verify(vararg alarms: AlarmEntry) {
alarmService.scheduleAllAlarms()
private suspend fun verify(
overdue: List<AlarmEntry> = emptyList(),
future: List<AlarmEntry> = emptyList(),
) {
val (actualOverdue, actualFuture) = alarmService.getAlarms()
assertEquals(alarms.toList(), jobs.getJobs())
assertEquals(overdue, actualOverdue)
assertEquals(future, actualFuture)
}
}

@ -532,10 +532,6 @@
android:value="org.tasks.dashclock.DashClockSettings"/>
</service>
<service
android:exported="false"
android:name=".jobs.NotificationService"/>
<activity
android:exported="true"
android:name=".dashclock.DashClockSettings"/>
@ -601,6 +597,8 @@
</intent-filter>
</receiver>
<receiver android:name="org.tasks.jobs.NotificationReceiver" />
<activity
android:name=".auth.MicrosoftAuthenticationActivity"
android:theme="@style/TranslucentDialog"/>

@ -5,14 +5,16 @@
*/
package com.todoroo.astrid.alarms
import com.todoroo.astrid.data.Task
import com.todoroo.andlib.utility.DateUtilities
import org.tasks.LocalBroadcastManager
import org.tasks.data.Alarm
import org.tasks.data.Alarm.Companion.TYPE_SNOOZE
import org.tasks.data.AlarmDao
import org.tasks.data.TaskDao
import org.tasks.jobs.NotificationQueue
import org.tasks.jobs.AlarmEntry
import org.tasks.jobs.WorkManager
import org.tasks.notifications.NotificationManager
import timber.log.Timber
import javax.inject.Inject
/**
@ -22,10 +24,10 @@ import javax.inject.Inject
*/
class AlarmService @Inject constructor(
private val alarmDao: AlarmDao,
private val jobs: NotificationQueue,
private val taskDao: TaskDao,
private val localBroadcastManager: LocalBroadcastManager,
private val notificationManager: NotificationManager,
private val workManager: WorkManager,
private val alarmCalculator: AlarmCalculator,
) {
suspend fun getAlarms(taskId: Long): List<Alarm> = alarmDao.getAlarms(taskId)
@ -36,7 +38,6 @@ class AlarmService @Inject constructor(
* @return true if data was changed
*/
suspend fun synchronizeAlarms(taskId: Long, alarms: MutableSet<Alarm>): Boolean {
val task = taskDao.fetch(taskId) ?: return false
var changed = false
for (existing in alarmDao.getAlarms(taskId)) {
if (!alarms.removeIf {
@ -55,51 +56,42 @@ class AlarmService @Inject constructor(
changed = true
}
if (changed) {
scheduleAlarms(task)
workManager.triggerNotifications()
localBroadcastManager.broadcastRefreshList()
}
return changed
}
suspend fun scheduleAllAlarms() {
alarmDao
.getActiveAlarms()
.groupBy { it.task }
.forEach { (taskId, alarms) ->
val task = taskDao.fetch(taskId) ?: return@forEach
scheduleAlarms(task, alarms)
}
}
fun cancelAlarms(taskId: Long) {
jobs.cancelForTask(taskId)
}
suspend fun snooze(time: Long, taskIds: List<Long>) {
notificationManager.cancel(taskIds)
alarmDao.getSnoozed(taskIds).let { alarmDao.delete(it) }
taskIds.map { Alarm(it, time, TYPE_SNOOZE) }.let { alarmDao.insert(it) }
taskDao.touch(taskIds)
scheduleAlarms(taskIds)
workManager.triggerNotifications()
}
suspend fun scheduleAlarms(taskIds: List<Long>) {
taskDao.fetch(taskIds).forEach { scheduleAlarms(it) }
}
/** Schedules alarms for a single task */
suspend fun scheduleAlarms(task: Task) {
scheduleAlarms(task, alarmDao.getActiveAlarms(task.id))
}
private fun scheduleAlarms(task: Task, alarms: List<Alarm>) {
jobs.cancelForTask(task.id)
val alarmEntries = alarms.mapNotNull {
alarmCalculator.toAlarmEntry(task, it)
}
val next =
alarmEntries.find { it.type == TYPE_SNOOZE } ?: alarmEntries.minByOrNull { it.time }
next?.let { jobs.add(it) }
suspend fun getAlarms(): Pair<List<AlarmEntry>, List<AlarmEntry>> {
val start = DateUtilities.now()
val overdue = ArrayList<AlarmEntry>()
val future = ArrayList<AlarmEntry>()
alarmDao.getActiveAlarms()
.groupBy { it.task }
.forEach { (taskId, alarms) ->
val task = taskDao.fetch(taskId) ?: return@forEach
val alarmEntries = alarms.mapNotNull {
alarmCalculator.toAlarmEntry(task, it)
}
val (now, later) = alarmEntries.partition { it.time <= DateUtilities.now() }
later
.find { it.type == TYPE_SNOOZE }
?.let { future.add(it) }
?: run {
now.firstOrNull()?.let { overdue.add(it) }
later.minByOrNull { it.time }?.let { future.add(it) }
}
}
Timber.d("took ${DateUtilities.now() - start}ms overdue=${overdue.size} future=${future.size}")
return overdue to future
}
companion object {

@ -5,7 +5,6 @@
*/
package com.todoroo.astrid.dao
import com.todoroo.astrid.alarms.AlarmService
import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.data.Task
import com.todoroo.astrid.timers.TimerPlugin
@ -28,7 +27,6 @@ class TaskDao @Inject constructor(
private val geofenceApi: GeofenceApi,
private val timerPlugin: TimerPlugin,
private val syncAdapters: SyncAdapters,
private val alarmService: AlarmService,
private val workManager: WorkManager,
) {
@ -123,7 +121,7 @@ class TaskDao @Inject constructor(
if (completionDateModified || deletionDateModified) {
geofenceApi.update(task.id)
}
alarmService.scheduleAlarms(task)
workManager.triggerNotifications()
workManager.scheduleRefresh()
if (!task.isSuppressRefresh()) {
localBroadcastManager.broadcastRefresh()

@ -2,7 +2,6 @@ package com.todoroo.astrid.service
import android.content.Context
import androidx.room.withTransaction
import com.todoroo.astrid.alarms.AlarmService
import com.todoroo.astrid.dao.Database
import com.todoroo.astrid.data.Task
import com.todoroo.astrid.timers.TimerPlugin
@ -22,7 +21,6 @@ import org.tasks.db.SuspendDbUtils.chunkedMap
import org.tasks.files.FileHelper
import org.tasks.location.GeofenceApi
import org.tasks.notifications.NotificationManager
import org.tasks.preferences.Preferences
import org.tasks.sync.SyncAdapters
import javax.inject.Inject
@ -32,13 +30,11 @@ class TaskDeleter @Inject constructor(
private val deletionDao: DeletionDao,
private val taskDao: TaskDao,
private val localBroadcastManager: LocalBroadcastManager,
private val preferences: Preferences,
private val syncAdapters: SyncAdapters,
private val vtodoCache: VtodoCache,
private val notificationManager: NotificationManager,
private val geofenceApi: GeofenceApi,
private val timerPlugin: TimerPlugin,
private val alarmService: AlarmService,
private val userActivityDao: UserActivityDao,
private val locationDao: LocationDao,
) {
@ -96,7 +92,6 @@ class TaskDeleter @Inject constructor(
throw IllegalStateException()
}
tasks.forEach { task ->
alarmService.cancelAlarms(task)
notificationManager.cancel(task)
locationDao.getGeofencesForTask(task).forEach {
locationDao.delete(it)

@ -100,7 +100,6 @@ class Notifier @Inject constructor(
}
?: false
}
.takeLast(NotificationManager.MAX_NOTIFICATIONS)
if (notifications.isEmpty()) {
return

@ -1,73 +0,0 @@
package org.tasks.injection
import android.app.Notification
import android.app.Service
import android.content.Intent
import androidx.core.app.NotificationCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import org.tasks.R
import org.tasks.analytics.Firebase
import org.tasks.notifications.NotificationManager
import javax.inject.Inject
abstract class InjectingService : Service() {
private val job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.Default + job)
@Inject lateinit var firebase: Firebase
override fun onCreate() {
super.onCreate()
startForeground()
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
startForeground()
scope.launch {
try {
doWork()
} catch (e: Exception) {
firebase.reportException(e)
} finally {
done(startId)
}
}
return START_NOT_STICKY
}
private fun done(startId: Int) {
scheduleNext()
stopSelf(startId)
}
override fun onDestroy() {
super.onDestroy()
stopForeground(true)
job.cancel()
}
private fun startForeground() {
startForeground(notificationId, buildNotification())
}
protected abstract val notificationId: Int
protected abstract val notificationBody: Int
private fun buildNotification(): Notification {
return NotificationCompat.Builder(
this, NotificationManager.NOTIFICATION_CHANNEL_MISCELLANEOUS)
.setSound(null)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setSmallIcon(R.drawable.ic_check_white_24dp)
.setContentTitle(getString(R.string.app_name))
.setContentText(getString(notificationBody))
.build()
}
protected open fun scheduleNext() {}
protected abstract suspend fun doWork()
}

@ -13,7 +13,6 @@ import org.tasks.R
import org.tasks.caldav.FileStorage
import org.tasks.data.CaldavDao
import org.tasks.data.OpenTaskDao
import org.tasks.data.TaskDao
import org.tasks.db.Migrations
import org.tasks.jobs.WorkManager
import org.tasks.jobs.WorkManagerImpl
@ -53,6 +52,5 @@ internal class ProductionModule {
preferences: Preferences,
caldavDao: CaldavDao,
openTaskDao: OpenTaskDao,
taskDao: TaskDao,
): WorkManager = WorkManagerImpl(context, preferences, caldavDao, openTaskDao, taskDao)
): WorkManager = WorkManagerImpl(context, preferences, caldavDao, openTaskDao)
}

@ -1,90 +0,0 @@
package org.tasks.jobs
import com.google.common.collect.Ordering
import com.google.common.collect.TreeMultimap
import com.google.common.primitives.Ints
import org.tasks.preferences.Preferences
import org.tasks.time.DateTime
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class NotificationQueue @Inject constructor(
private val preferences: Preferences,
private val workManager: WorkManager
) {
private val jobs =
TreeMultimap.create<Long, AlarmEntry>(Ordering.natural()) { l, r ->
Ints.compare(l.hashCode(), r.hashCode())
}
@Synchronized
fun add(entry: AlarmEntry) = add(listOf(entry))
@Synchronized
fun add(entries: Iterable<AlarmEntry>) {
val originalFirstTime = firstTime()
entries.forEach { jobs.put(it.time, it) }
if (originalFirstTime != firstTime()) {
scheduleNext(true)
}
}
@Synchronized
fun clear() {
jobs.clear()
workManager.cancelNotifications()
}
fun cancelForTask(taskId: Long) {
val firstTime = firstTime()
jobs.values().filter { it.taskId == taskId }.forEach { remove(listOf(it)) }
if (firstTime != firstTime()) {
scheduleNext(true)
}
}
@get:Synchronized
val overdueJobs: List<AlarmEntry>
get() = jobs.keySet()
.headSet(DateTime().startOfMinute().plusMinutes(1).millis)
.flatMap { jobs[it] }
@Synchronized
fun scheduleNext() = scheduleNext(false)
private fun scheduleNext(cancelCurrent: Boolean) {
if (jobs.isEmpty) {
if (cancelCurrent) {
workManager.cancelNotifications()
}
} else {
workManager.scheduleNotification(nextScheduledTime())
}
}
private fun firstTime() = if (jobs.isEmpty) 0L else jobs.asMap().firstKey()
fun nextScheduledTime(): Long {
val next = firstTime()
return if (next > 0) preferences.adjustForQuietHours(next) else 0
}
fun size() = jobs.size()
fun getJobs() = jobs.values().toList()
fun isEmpty() = jobs.isEmpty
@Synchronized
fun remove(entries: List<AlarmEntry>): Boolean {
var success = true
for (entry in entries) {
success = success and (!jobs.containsEntry(entry.time, entry) || jobs.remove(
entry.time,
entry
))
}
return success
}
}

@ -0,0 +1,16 @@
package org.tasks.jobs
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class NotificationReceiver: BroadcastReceiver() {
@Inject lateinit var workManager: WorkManager
override fun onReceive(context: Context?, intent: Intent?) {
workManager.triggerNotifications(expedited = true)
}
}

@ -1,50 +0,0 @@
package org.tasks.jobs
import android.content.Intent
import android.os.IBinder
import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.astrid.alarms.AlarmService
import dagger.hilt.android.AndroidEntryPoint
import org.tasks.Notifier
import org.tasks.R
import org.tasks.data.Alarm.Companion.TYPE_SNOOZE
import org.tasks.data.AlarmDao
import org.tasks.injection.InjectingService
import org.tasks.preferences.Preferences
import javax.inject.Inject
@AndroidEntryPoint
class NotificationService : InjectingService() {
@Inject lateinit var preferences: Preferences
@Inject lateinit var notifier: Notifier
@Inject lateinit var notificationQueue: NotificationQueue
@Inject lateinit var alarmDao: AlarmDao
@Inject lateinit var alarmService: AlarmService
override fun onBind(intent: Intent): IBinder? = null
override val notificationId = -1
override val notificationBody = R.string.building_notifications
override suspend fun doWork() {
AndroidUtilities.assertNotMainThread()
if (!preferences.isCurrentlyQuietHours) {
val overdueJobs = notificationQueue.overdueJobs
if (!notificationQueue.remove(overdueJobs)) {
throw RuntimeException("Failed to remove jobs from queue")
}
notifier.triggerNotifications(overdueJobs.map { it.toNotification() })
overdueJobs
.filter { it.type == TYPE_SNOOZE }
.takeIf { it.isNotEmpty() }
?.map { it.id }
?.let { alarmDao.deleteByIds(it) }
overdueJobs
.map { it.taskId }
.let { alarmService.scheduleAlarms(it) }
}
}
override fun scheduleNext() = notificationQueue.scheduleNext()
}

@ -0,0 +1,64 @@
package org.tasks.jobs
import android.content.Context
import androidx.hilt.work.HiltWorker
import androidx.work.WorkerParameters
import com.todoroo.andlib.utility.DateUtilities.now
import com.todoroo.astrid.alarms.AlarmService
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import org.tasks.Notifier
import org.tasks.analytics.Firebase
import org.tasks.data.Alarm.Companion.TYPE_SNOOZE
import org.tasks.data.AlarmDao
import org.tasks.date.DateTimeUtils.toDateTime
import org.tasks.preferences.Preferences
import timber.log.Timber
@HiltWorker
class NotificationWork @AssistedInject constructor(
@Assisted context: Context,
@Assisted workerParams: WorkerParameters,
firebase: Firebase,
private val workManager: WorkManager,
private val alarmService: AlarmService,
private val alarmDao: AlarmDao,
private val preferences: Preferences,
private val notifier: Notifier,
) : RepeatingWorker(context, workerParams, firebase) {
private var nextAlarm: Long = 0
override suspend fun run(): Result {
if (preferences.isCurrentlyQuietHours) {
nextAlarm = preferences.adjustForQuietHours(now())
return Result.success()
}
repeat(3) {
val (overdue, future) = alarmService.getAlarms()
if (overdue.isNotEmpty()) {
overdue
.sortedBy { it.time }
.also { alarms ->
alarms
.filter { it.type == TYPE_SNOOZE }
.map { it.id }
.let { alarmDao.deleteByIds(it) }
}
.map { it.toNotification() }
.let { notifier.triggerNotifications(it) }
} else {
nextAlarm = future.minOfOrNull { it.time } ?: 0
Timber.d("nextAlarm=${nextAlarm.toDateTime()}")
return Result.success()
}
}
firebase.reportException(IllegalStateException("Should have returned already"))
return Result.failure()
}
override suspend fun scheduleNext() {
if (nextAlarm > 0) {
workManager.scheduleNotification(preferences.adjustForQuietHours(nextAlarm))
}
}
}

@ -23,6 +23,8 @@ interface WorkManager {
suspend fun scheduleRefresh(timestamp: Long = now() + 5_000)
fun triggerNotifications(expedited: Boolean = false)
fun scheduleNotification(scheduledTime: Long)
fun scheduleBackup()
@ -31,8 +33,6 @@ interface WorkManager {
fun scheduleDriveUpload(uri: Uri, purge: Boolean)
fun cancelNotifications()
fun updatePurchases()
companion object {
@ -44,5 +44,6 @@ interface WorkManager {
const val TAG_REMOTE_CONFIG = "tag_remote_config"
const val TAG_MIGRATE_LOCAL = "tag_migrate_local"
const val TAG_UPDATE_PURCHASES = "tag_update_purchases"
const val TAG_NOTIFICATIONS = "tag_notifications"
}
}

@ -12,6 +12,7 @@ import androidx.work.ExistingWorkPolicy.APPEND_OR_REPLACE
import androidx.work.ExistingWorkPolicy.REPLACE
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequest
import androidx.work.OutOfQuotaPolicy
import androidx.work.PeriodicWorkRequest
import androidx.work.WorkContinuation
import androidx.work.WorkInfo
@ -20,7 +21,7 @@ import androidx.work.Worker
import androidx.work.workDataOf
import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.andlib.utility.AndroidUtilities.atLeastS
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.andlib.utility.DateUtilities.now
import com.todoroo.astrid.data.Task
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -34,7 +35,6 @@ import org.tasks.data.CaldavAccount.Companion.TYPE_TASKS
import org.tasks.data.CaldavDao
import org.tasks.data.OpenTaskDao
import org.tasks.data.Place
import org.tasks.data.TaskDao
import org.tasks.date.DateTimeUtils.midnight
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.jobs.DriveUploader.Companion.EXTRA_PURGE
@ -46,6 +46,7 @@ import org.tasks.jobs.WorkManager.Companion.REMOTE_CONFIG_INTERVAL_HOURS
import org.tasks.jobs.WorkManager.Companion.TAG_BACKGROUND_SYNC
import org.tasks.jobs.WorkManager.Companion.TAG_BACKUP
import org.tasks.jobs.WorkManager.Companion.TAG_MIGRATE_LOCAL
import org.tasks.jobs.WorkManager.Companion.TAG_NOTIFICATIONS
import org.tasks.jobs.WorkManager.Companion.TAG_REFRESH
import org.tasks.jobs.WorkManager.Companion.TAG_REMOTE_CONFIG
import org.tasks.jobs.WorkManager.Companion.TAG_SYNC
@ -63,7 +64,6 @@ class WorkManagerImpl(
private val preferences: Preferences,
private val caldavDao: CaldavDao,
private val openTaskDao: OpenTaskDao,
private val taskDao: TaskDao,
): WorkManager {
private val throttle = Throttle(200, 60000, "WORK")
private val alarmManager: AlarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
@ -142,9 +142,19 @@ class WorkManagerImpl(
override suspend fun scheduleRefresh(timestamp: Long) =
enqueueUnique(TAG_REFRESH, RefreshWork::class.java, timestamp)
override fun triggerNotifications(expedited: Boolean) {
enqueueUnique(
TAG_NOTIFICATIONS,
NotificationWork::class.java,
time = if (expedited) 0 else now() + 5_000,
expedited = expedited,
)
}
override fun scheduleNotification(scheduledTime: Long) {
val time = max(DateUtilities.now(), scheduledTime)
val time = max(now(), scheduledTime)
if (time < DateTimeUtils.currentTimeMillis()) {
val intent = notificationIntent
if (AndroidUtilities.atLeastOreo()) {
context.startForegroundService(intent)
@ -199,21 +209,25 @@ class WorkManagerImpl(
private val networkConstraints: Constraints
get() = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
override fun cancelNotifications() {
alarmManager.cancel(notificationPendingIntent)
}
override fun updatePurchases() =
enqueueUnique(TAG_UPDATE_PURCHASES, UpdatePurchaseWork::class.java)
@SuppressLint("EnqueueWork")
private fun enqueueUnique(key: String, c: Class<out Worker?>, time: Long = 0) {
val delay = time - DateUtilities.now()
private fun enqueueUnique(
key: String,
c: Class<out Worker?>,
time: Long = 0,
expedited: Boolean = false,
) {
val delay = time - now()
val builder = OneTimeWorkRequest.Builder(c)
if (delay > 0) {
builder.setInitialDelay(delay, TimeUnit.MILLISECONDS)
}
Timber.d("$key: ${DateTimeUtils.printTimestamp(time)} (${DateTimeUtils.printDuration(delay)})")
if (expedited) {
builder.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
}
Timber.d("$key: ${DateTimeUtils.printTimestamp(delay)} (${DateTimeUtils.printDuration(delay)})")
enqueue(workManager.beginUniqueWork(key, REPLACE, builder.build()))
}
@ -230,26 +244,15 @@ class WorkManagerImpl(
}
private val notificationIntent: Intent
get() = Intent(context, NotificationService::class.java)
get() = Intent(context, NotificationReceiver::class.java)
private val notificationPendingIntent: PendingIntent
get() {
return if (AndroidUtilities.atLeastOreo()) {
PendingIntent.getForegroundService(
context,
0,
notificationIntent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
} else {
PendingIntent.getService(
context,
0,
notificationIntent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
}
}
get() = PendingIntent.getBroadcast(
context,
0,
notificationIntent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
private suspend fun getSyncJob() = withContext(Dispatchers.IO) {
workManager.getWorkInfosForUniqueWork(TAG_SYNC).get()

@ -55,12 +55,14 @@ class NotificationManager @Inject constructor(
}
@SuppressLint("CheckResult")
suspend fun cancel(ids: Iterable<Long>) {
suspend fun cancel(ids: Iterable<Long>, delete: Boolean = true) {
for (id in ids) {
notificationManager.cancel(id.toInt())
}
queue.remove(ids)
notificationDao.deleteAll(ids.toList())
if (delete) {
notificationDao.deleteAll(ids.toList())
}
notifyTasks(emptyList(), alert = false, nonstop = false, fiveTimes = false)
}
@ -210,7 +212,7 @@ class NotificationManager @Inject constructor(
)
val evicted = queue.add(notificationId)
if (evicted.size > 0) {
cancel(evicted)
cancel(evicted, delete = false)
}
for (i in 0 until ringTimes) {
if (i > 0) {
@ -403,7 +405,6 @@ class NotificationManager @Inject constructor(
const val NOTIFICATION_CHANNEL_DEFAULT = "notifications"
const val NOTIFICATION_CHANNEL_TASKER = "notifications_tasker"
const val NOTIFICATION_CHANNEL_TIMERS = "notifications_timers"
const val NOTIFICATION_CHANNEL_MISCELLANEOUS = "notifications_miscellaneous"
const val MAX_NOTIFICATIONS = 21
const val EXTRA_NOTIFICATION_ID = "extra_notification_id"
const val SUMMARY_NOTIFICATION_ID = 0

@ -6,12 +6,11 @@ import android.content.Context
import android.content.Intent
import android.os.Build
import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.astrid.alarms.AlarmService
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.qualifiers.ApplicationContext
import org.tasks.R
import org.tasks.injection.InjectingJobIntentService
import org.tasks.jobs.NotificationQueue
import org.tasks.jobs.WorkManager
import org.tasks.notifications.NotificationManager
import timber.log.Timber
import javax.inject.Inject
@ -19,17 +18,15 @@ import javax.inject.Inject
@AndroidEntryPoint
class NotificationSchedulerIntentService : InjectingJobIntentService() {
@Inject @ApplicationContext lateinit var context: Context
@Inject lateinit var alarmService: AlarmService
@Inject lateinit var notificationQueue: NotificationQueue
@Inject lateinit var notificationManager: NotificationManager
@Inject lateinit var workManager: WorkManager
override suspend fun doWork(intent: Intent) {
Timber.d("onHandleWork(%s)", intent)
createNotificationChannels()
notificationQueue.clear()
val cancelExistingNotifications = intent.getBooleanExtra(EXTRA_CANCEL_EXISTING_NOTIFICATIONS, false)
notificationManager.restoreNotifications(cancelExistingNotifications)
alarmService.scheduleAllAlarms()
workManager.triggerNotifications()
}
private fun createNotificationChannels() {
@ -42,9 +39,6 @@ class NotificationSchedulerIntentService : InjectingJobIntentService() {
notificationManager.createNotificationChannel(
createNotificationChannel(
NotificationManager.NOTIFICATION_CHANNEL_TIMERS, R.string.TEA_timer_controls, true))
notificationManager.createNotificationChannel(
createNotificationChannel(
NotificationManager.NOTIFICATION_CHANNEL_MISCELLANEOUS, R.string.miscellaneous, false))
}
}

@ -157,7 +157,6 @@
<string name="add_account">أضف حساب</string>
<string name="led_notification">إشعار LED</string>
<string name="wearable_notifications_summary">عرض الإشعارات على الساعة الذكية</string>
<string name="building_notifications">توليد الإخطارات</string>
<string name="bundle_notifications_summary">ادمج عدة إشعارات في شعار واحد</string>
<string name="notification_troubleshooting_summary">انقر هنا إذا كنت تواجه مشاكل في الإشعارات</string>
<string name="TEA_control_location">الموقع</string>
@ -413,7 +412,6 @@
<string name="url_required">الرابط مطلوب</string>
<string name="location_radius_meters">%s م</string>
<string name="header_spacing">التباعد</string>
<string name="miscellaneous">متنوّع</string>
<string name="CFC_tag_text">علامة: \?</string>
<string name="CFC_importance_text">الأولويّة على الأقل \?</string>
<string name="TEA_control_timer">ضوابط المؤقت</string>

@ -237,7 +237,6 @@
<string name="attachment_directory">Папка за прикачени файлове</string>
<string name="backup_directory">Папка за резервни копия</string>
<string name="google_drive_backup">Резервно копие в Google Drive</string>
<string name="miscellaneous">Разни</string>
<string name="enabled">Включено</string>
<string name="font_size">Размер на шрифта</string>
<string name="row_spacing">Междуредие</string>
@ -385,7 +384,6 @@
<string name="visit_website">Отваряне на страницата</string>
<string name="location_arrived">Пристигнахте в %s</string>
<string name="location_departed">Отпътувахте от %s</string>
<string name="building_notifications">Създаване на известия</string>
<string name="choose_a_location">Изберете местоположение</string>
<string name="pick_this_location">Изберете това местоположение</string>
<string name="or_choose_a_location">Или изберете местоположение</string>

@ -139,7 +139,6 @@
<string name="vibrations">Vibrace</string>
<string name="quiet_hours">Období nerušení</string>
<string name="backup_directory">Složka pro zálohy</string>
<string name="miscellaneous">Různé</string>
<string name="enabled">Povoleno</string>
<string name="font_size">Velikost písma</string>
<string name="source_code">Zdrojové kódy</string>
@ -492,7 +491,6 @@
<string name="color_wheel">Více barev</string>
<string name="invalid_username_or_password">Neplatné uživatelské jméno nebo heslo</string>
<string name="davx5_selection_description">Synchronizujte své úkoly pomocí aplikace DAVx⁵</string>
<string name="building_notifications">Vytvářejí se oznámení</string>
<string name="got_it">Rozumím!</string>
<string name="support_development_subscribe">Odemkněte si další funkce a podpořte software s otevřeným zdrojovým kódem</string>
<string name="enjoying_tasks">Líbí se vám Tasks\?</string>

@ -182,7 +182,6 @@
<string name="or_choose_a_location">Eller vælg et sted</string>
<string name="pick_this_location">Vælg dette sted</string>
<string name="choose_a_location">Vælg sted</string>
<string name="building_notifications">Genererer notifikationer</string>
<string name="location_departed">Afgået fra %s</string>
<string name="location_arrived">Ankommet til %s</string>
<string name="visit_website">Besøg hjemmeside</string>
@ -330,7 +329,6 @@
<string name="font_size">Skriftstørrelse</string>
<string name="enabled">Aktiveret</string>
<string name="subtasks">Underopgaver</string>
<string name="miscellaneous">Diverse</string>
<string name="google_drive_backup">Google Drive-backup</string>
<string name="backup_directory">Mappe til sikkerhedskopi</string>
<string name="attachment_directory">Mappe til vedhæftede filer</string>

@ -230,7 +230,6 @@
<string name="attachment_directory">Ordner für Anhänge</string>
<string name="backup_directory">Sicherungsordner</string>
<string name="google_drive_backup">Google Drive-Sicherung</string>
<string name="miscellaneous">Verschiedenes</string>
<string name="enabled">Aktiviert</string>
<string name="font_size">Schriftgröße</string>
<string name="row_spacing">Zeilenabstand</string>
@ -376,7 +375,6 @@
<string name="visit_website">Website öffnen</string>
<string name="location_arrived">Angekommen um %s</string>
<string name="location_departed">Abgereist um %s</string>
<string name="building_notifications">Benachrichtigungen generieren</string>
<string name="choose_a_location">Ort auswählen</string>
<string name="pick_this_location">Diesen Ort auswählen</string>
<string name="or_choose_a_location">Oder Ort auswählen</string>

@ -265,7 +265,6 @@
<string name="row_spacing">Intervica spaco</string>
<string name="header_spacing">Interspaco</string>
<string name="enabled">Ŝaltita</string>
<string name="miscellaneous">Diversaj</string>
<string name="backup_directory">Dosierujo de savkopioj</string>
<string name="quiet_hours">Silentaj horoj</string>
<string name="vibrations">Vibradoj</string>
@ -474,7 +473,6 @@
<string name="help">Helpo</string>
<string name="about">Pri</string>
<string name="enter_tag_name">Entajpu etikedonomon</string>
<string name="building_notifications">Generas sciigojn</string>
<string name="whats_new">Novaĵoj</string>
<string name="action_create_new_task">Krei novan taskon</string>
<string name="show_description">Vidigi priskribon</string>

@ -246,7 +246,6 @@
<string name="attachment_directory">Carpeta de datos adjuntos</string>
<string name="backup_directory">Carpeta de copias de seguridad</string>
<string name="google_drive_backup">Copia de seguridad en Google Drive</string>
<string name="miscellaneous">Miscelánea</string>
<string name="enabled">Activado</string>
<string name="font_size">Tamaño de letra</string>
<string name="row_spacing">Espaciado de fila</string>
@ -393,7 +392,6 @@
<string name="visit_website">Visitar página web</string>
<string name="location_arrived">Llegada a %s</string>
<string name="location_departed">Salida de %s</string>
<string name="building_notifications">Generando notificaciones</string>
<string name="choose_a_location">Escoger ubicación</string>
<string name="pick_this_location">Seleccionar esta ubicación</string>
<string name="or_choose_a_location">O elegir una ubicación</string>

@ -136,7 +136,6 @@
<string name="attachment_directory">Failide kaust</string>
<string name="backup_directory">Varukoopia kaust</string>
<string name="google_drive_backup">Kopeeri Google Drive-i</string>
<string name="miscellaneous">Varia</string>
<string name="enabled">Sisse lülitatud</string>
<string name="font_size">Teksti suurus</string>
<string name="row_spacing">Reavahed</string>

@ -233,7 +233,6 @@
<string name="attachment_directory">Eranskinen karpeta</string>
<string name="backup_directory">Babes-kopien karpeta</string>
<string name="google_drive_backup">Google Drive babeskopia</string>
<string name="miscellaneous">Denetarik</string>
<string name="enabled">Gaituta</string>
<string name="font_size">Letraren tamaina</string>
<string name="row_spacing">Errenkaden banatze espazioa</string>
@ -384,7 +383,6 @@
<string name="visit_website">Bisitatu webgunea</string>
<string name="location_arrived">%s(e)tan helduta</string>
<string name="location_departed">%s(e)tan aterata</string>
<string name="building_notifications">Jakinarazpenak sortzen</string>
<string name="choose_a_location">Hautatu kokaleku bat</string>
<string name="pick_this_location">Hautatu kokaleku hau</string>
<string name="or_choose_a_location">Edo hautatu kokaleku bat</string>

@ -149,7 +149,6 @@
<string name="quiet_hours">ساعات سکوت</string>
<string name="attachment_directory">پوشه پیوست</string>
<string name="backup_directory">پشتیبان گیری از پوشه</string>
<string name="miscellaneous">سایر</string>
<string name="enabled">فعال شده</string>
<string name="font_size">اندازه فونت</string>
<string name="row_spacing">فاصله سطرها</string>

@ -222,7 +222,6 @@
<string name="quiet_hours">Hiljainen aika</string>
<string name="attachment_directory">Liitehakemisto</string>
<string name="backup_directory">Varmuuskopioiden hakemisto</string>
<string name="miscellaneous">Sekalaista</string>
<string name="enabled">Sallittu</string>
<string name="font_size">Kirjasimen koko</string>
<string name="row_spacing">Rivinväli</string>
@ -379,7 +378,6 @@
<string name="or_choose_a_location">Tai valitse muu sijainti</string>
<string name="pick_this_location">Valitse tämä sijainti</string>
<string name="choose_a_location">Valitse sijainti</string>
<string name="building_notifications">Ilmoitusten muodostaminen</string>
<string name="visit_website">Käy verkkosivulla</string>
<string name="location_remind_departure">Muistuta lähtiessä</string>
<string name="location_remind_arrival">Muistuta saapuessa</string>

@ -232,7 +232,6 @@
<string name="attachment_directory">Dossier des pièces jointes</string>
<string name="backup_directory">Dossier de sauvegarde</string>
<string name="google_drive_backup">Sauvegarde « Google Drive »</string>
<string name="miscellaneous">Divers</string>
<string name="enabled">Activé</string>
<string name="font_size">Taille de police</string>
<string name="row_spacing">Espacement des lignes</string>
@ -378,7 +377,6 @@
<string name="visit_website">Visiter le site web</string>
<string name="location_arrived">Arrivé à %s</string>
<string name="location_departed">Parti %s</string>
<string name="building_notifications">Générer des notifications</string>
<string name="choose_a_location">Choisir un emplacement</string>
<string name="pick_this_location">Sélectionner cet emplacement</string>
<string name="or_choose_a_location">Ou choisir un emplacement</string>

@ -162,7 +162,6 @@
<string name="quiet_hours">Horario silencioso</string>
<string name="attachment_directory">Cartafol de anexos</string>
<string name="backup_directory">Cartafol de copias de seguranza</string>
<string name="miscellaneous">Miscelánea</string>
<string name="enabled">Activado</string>
<string name="font_size">Tamaño da fonte</string>
<string name="row_spacing">Espazado de fila</string>
@ -343,7 +342,6 @@
<string name="visit_website">Visitar a páxina web</string>
<string name="location_arrived">Chegouse ás %s</string>
<string name="location_departed">Saiuse ás %s</string>
<string name="building_notifications">Xerando as notificacións</string>
<string name="choose_a_location">Escolle a posición</string>
<string name="pick_this_location">Escoller esta posición</string>
<string name="or_choose_a_location">Ou escolle unha posición</string>

@ -284,7 +284,6 @@
<string name="or_choose_a_location">Ili odaberi jednu lokaciju</string>
<string name="pick_this_location">Odaberi ovu lokaciju</string>
<string name="choose_a_location">Odaberi jednu lokaciju</string>
<string name="building_notifications">Generiranje obavijesti</string>
<string name="location_departed">Otišao/la %s</string>
<string name="location_arrived">Stigao/la %s</string>
<string name="linkify_description">Dodaj poveznice na web-stranice, adrese i telefonske brojeve</string>
@ -425,7 +424,6 @@
<string name="translations">Doprinesi prijevodima</string>
<string name="row_spacing">Razmak između redaka</string>
<string name="header_spacing">Razmak</string>
<string name="miscellaneous">Razno</string>
<string name="google_drive_backup">Sigurnosna kopija na Google Drive</string>
<string name="backup_directory">Mapa sigurnosnih kopija</string>
<string name="attachment_directory">Mapa priloga</string>

@ -230,7 +230,6 @@
<string name="attachment_directory">Csatolmányok mappája</string>
<string name="backup_directory">Mentési mappa</string>
<string name="google_drive_backup">Mentés Google Drive-ba</string>
<string name="miscellaneous">Egyéb</string>
<string name="enabled">Engedélyezve</string>
<string name="font_size">Karakter méret</string>
<string name="row_spacing">Sorköz</string>
@ -379,7 +378,6 @@
<string name="visit_website">Weboldal megnyitása</string>
<string name="location_arrived">Megérkezett ekkor: %s</string>
<string name="location_departed">Elindult ekkor: %s</string>
<string name="building_notifications">Értesítések létrehozása</string>
<string name="choose_a_location">Hely kiválasztása</string>
<string name="pick_this_location">Hely kiválasztása</string>
<string name="or_choose_a_location">Vagy másik hely keresése</string>

@ -167,7 +167,6 @@
<string name="attachment_directory">Direktori isi lampiran</string>
<string name="backup_directory">Direktori cadangan</string>
<string name="google_drive_backup">Cadangan Google Drive</string>
<string name="miscellaneous">Lain-lain</string>
<string name="subtasks">Subtugas</string>
<string name="enabled">Aktif</string>
<string name="font_size">Ukuran huruf</string>
@ -376,7 +375,6 @@
<string name="davx5_selection_description">Sinkronkan tugas Anda dengan aplikasi DAVx⁵</string>
<string name="whats_new">Apa yang Baru</string>
<string name="missing_permissions">Perizinan hilang</string>
<string name="building_notifications">Menghasilkan notifikasi</string>
<string name="location_remind_departure">Ingatkan pada saat keberangkatan</string>
<string name="location_remind_arrival">Ingatkan saat kedatangan</string>
<string name="action_new_task">Tugas Baru</string>

@ -247,7 +247,6 @@
<string name="attachment_directory">Cartella degli allegati</string>
<string name="backup_directory">Cartella di backup</string>
<string name="google_drive_backup">Backup su Google Drive</string>
<string name="miscellaneous">Varie</string>
<string name="enabled">Attiva</string>
<string name="font_size">Dimensione carattere</string>
<string name="row_spacing">Interlinea</string>
@ -394,7 +393,6 @@
<string name="visit_website">Visita il sito web</string>
<string name="location_arrived">Arrivato alle %s</string>
<string name="location_departed">Partito alle %s</string>
<string name="building_notifications">Gestione notifiche</string>
<string name="choose_a_location">Scegli una posizione</string>
<string name="pick_this_location">Seleziona questa posizione</string>
<string name="or_choose_a_location">O scegli una posizione</string>

@ -263,7 +263,6 @@
<string name="attachment_directory">תיקיית קבצים מצורפים</string>
<string name="backup_directory">תיקיית גיבוי</string>
<string name="google_drive_backup">גיבוי Google Drive</string>
<string name="miscellaneous">שונות</string>
<string name="enabled">אפשר</string>
<string name="font_size">גודל גופן</string>
<string name="row_spacing">מרווח שורה</string>
@ -413,7 +412,6 @@
<string name="visit_website">מעבר לאתר האינטרנט</string>
<string name="location_arrived">הגיע ב-%s</string>
<string name="location_departed">עזב ב-%s</string>
<string name="building_notifications">מייצר התראות</string>
<string name="choose_a_location">בחרו מיקום</string>
<string name="pick_this_location">בחרו את המיקום הזה</string>
<string name="or_choose_a_location">או בחרו מיקום</string>

@ -234,7 +234,6 @@
<string name="attachment_directory">添付フォルダー</string>
<string name="backup_directory">フォルダーをバックアップ</string>
<string name="google_drive_backup">Google ドライブにバックアップ</string>
<string name="miscellaneous">その他</string>
<string name="enabled">有効</string>
<string name="font_size">フォントサイズ</string>
<string name="row_spacing">行間隔</string>
@ -382,7 +381,6 @@
<string name="visit_website">Webサイトを参照</string>
<string name="location_arrived">%s に到着</string>
<string name="location_departed">%s に出発</string>
<string name="building_notifications">通知を生成しています</string>
<string name="choose_a_location">場所を選択</string>
<string name="pick_this_location">この場所を選択</string>
<string name="or_choose_a_location">または場所を選ぶ</string>

@ -231,7 +231,6 @@
<string name="quiet_hours">무음 시간대</string>
<string name="attachment_directory">첨부파일 위치</string>
<string name="backup_directory">백업 위치</string>
<string name="miscellaneous">기타</string>
<string name="enabled">활성화됨</string>
<string name="font_size">글자 크기</string>
<string name="row_spacing">줄 간격</string>
@ -489,7 +488,6 @@
<string name="auto_dismiss_datetime_list">할일 목록</string>
<string name="auto_dismiss_datetime">일시 선택상자 닫기</string>
<string name="whats_new">새 기능</string>
<string name="building_notifications">알림 생성하기</string>
<string name="widget_due_date_after_title">제목 다음</string>
<string name="action_new_task">새 할일</string>
<string name="widget_due_date_hidden">숨기기</string>

@ -252,7 +252,6 @@
<string name="attachment_directory">Pridedamo failo aplankas</string>
<string name="backup_directory">Atsarginių kopijų aplankas</string>
<string name="google_drive_backup">Google Drive atsarginė kopija</string>
<string name="miscellaneous">Įvairūs</string>
<string name="enabled">Įjungta</string>
<string name="font_size">Šrifto dydis</string>
<string name="row_spacing">Eilutės plotis</string>
@ -400,7 +399,6 @@
<string name="visit_website">Aplankyti svetainę</string>
<string name="location_arrived">Atvykus į %s</string>
<string name="location_departed">Išvykus iš %s</string>
<string name="building_notifications">Generuojami pranešimai</string>
<string name="choose_a_location">Pasirinkti vietą</string>
<string name="pick_this_location">Pasirinkti šią vietą</string>
<string name="or_choose_a_location">Arba pasirinkti vietą</string>

@ -267,7 +267,6 @@
<string name="attachment_directory">Vedleggsmappe</string>
<string name="backup_directory">Sikkerhetskopimappe</string>
<string name="google_drive_backup">Google Drive-sikkerhetskopi</string>
<string name="miscellaneous">Ymse</string>
<string name="enabled">Påskrudd</string>
<string name="font_size">Skriftstørrelse</string>
<string name="customize_edit_screen">Tilpass redigeringsskjerm</string>
@ -384,7 +383,6 @@
<string name="visit_website">Besøk nettside</string>
<string name="location_arrived">Ankom %s</string>
<string name="location_departed">Forlot %s</string>
<string name="building_notifications">Oppretter merknader</string>
<string name="choose_a_location">Velg et sted</string>
<string name="pick_this_location">Velg dette stedet</string>
<string name="or_choose_a_location">Eller velg et sted</string>

@ -230,7 +230,6 @@
<string name="quiet_hours">Rusttijd</string>
<string name="attachment_directory">Map voor bijlagen</string>
<string name="google_drive_backup">Backup naar Google Drive</string>
<string name="miscellaneous">Diversen</string>
<string name="enabled">Actief</string>
<string name="font_size">Lettergrootte</string>
<string name="row_spacing">Regelafstand</string>
@ -373,7 +372,6 @@
<string name="visit_website">Website bezoeken</string>
<string name="location_arrived">Aangekomen op %s</string>
<string name="location_departed">Vertrokken van %s</string>
<string name="building_notifications">Meldingen aanmaken</string>
<string name="choose_a_location">Locatie kiezen</string>
<string name="pick_this_location">Locatie onder naald kiezen</string>
<string name="or_choose_a_location">Of kies een locatie</string>

@ -242,7 +242,6 @@
<string name="attachment_directory">Katalog załączników</string>
<string name="backup_directory">Katalog kopii zapasowych</string>
<string name="google_drive_backup">Kopia zapasowa na Dysku Google</string>
<string name="miscellaneous">Różne</string>
<string name="enabled">Włączone</string>
<string name="font_size">Rozmiar czcionki</string>
<string name="row_spacing">Odstęp wierszy</string>
@ -388,7 +387,6 @@
<string name="visit_website">Wejdź na stronę</string>
<string name="location_arrived">Przybyto do %s</string>
<string name="location_departed">Wyruszono z %s</string>
<string name="building_notifications">Generowanie powiadomień</string>
<string name="choose_a_location">Wybierz lokalizację</string>
<string name="pick_this_location">Ustaw wybraną lokalizację</string>
<string name="or_choose_a_location">Lub wybierz lokalizację</string>

@ -243,7 +243,6 @@
<string name="attachment_directory">Pasta de anexo</string>
<string name="backup_directory">Pasta de backup</string>
<string name="google_drive_backup">Backup no Google Drive</string>
<string name="miscellaneous">Miscelânea</string>
<string name="enabled">Ativado</string>
<string name="font_size">Tamanho do texto</string>
<string name="row_spacing">Espaçamento da linha</string>
@ -391,7 +390,6 @@
<string name="visit_website">Visitar website</string>
<string name="location_arrived">Chegou às %s</string>
<string name="location_departed">Saiu às %s</string>
<string name="building_notifications">Gerando notificações</string>
<string name="choose_a_location">Escolher uma localização</string>
<string name="pick_this_location">Selecionar essa localização</string>
<string name="or_choose_a_location">Ou escolha uma localização</string>

@ -221,7 +221,6 @@
<string name="quiet_hours">Horas de silêncio</string>
<string name="attachment_directory">Pasta de anexo</string>
<string name="backup_directory">Pasta de cópias de segurança</string>
<string name="miscellaneous">Outras</string>
<string name="enabled">Ativo</string>
<string name="font_size">Tamanho das letras</string>
<string name="row_spacing">Espaçamento entre linhas</string>
@ -347,7 +346,6 @@
<string name="or_choose_a_location">Ou escolha uma localização</string>
<string name="pick_this_location">Selecionar esta localização</string>
<string name="choose_a_location">Escolher uma localização</string>
<string name="building_notifications">A gerar notificações</string>
<string name="location_departed">Saiu às %s</string>
<string name="location_arrived">Chegou às %s</string>
<string name="visit_website">Visitar site web</string>

@ -251,7 +251,6 @@
<string name="font_size">Dimensiunea fontului</string>
<string name="enabled">Activat</string>
<string name="subtasks">Sub-sarcini</string>
<string name="miscellaneous">Diverse</string>
<string name="google_drive_backup">Copie de rezervă Google Drive</string>
<string name="backup_directory">Dosar de rezervă</string>
<string name="attachment_directory">Fișier atașat</string>
@ -609,7 +608,6 @@
<string name="or_choose_a_location">Sau alege o locație</string>
<string name="pick_this_location">Selectează această locație</string>
<string name="choose_a_location">Alege o locație</string>
<string name="building_notifications">Generarea de notificări</string>
<string name="location_departed">A plecat %s</string>
<string name="location_arrived">A sosit la %s</string>
<string name="visit_website">Vizitează site-ul web</string>

@ -248,7 +248,6 @@
<string name="attachment_directory">Папка вложений</string>
<string name="backup_directory">Папка резервных копий</string>
<string name="google_drive_backup">Резервное копирование в Google Drive</string>
<string name="miscellaneous">Прочие настройки</string>
<string name="enabled">Включено</string>
<string name="font_size">Размер шрифта</string>
<string name="row_spacing">Межстрочный интервал</string>
@ -412,7 +411,6 @@
<string name="url">URL</string>
<string name="error_adding_account">Ошибка: %s</string>
<string name="list_separator_with_space">", "</string>
<string name="building_notifications">Генерация уведомлений</string>
<string name="SSD_sort_my_order">Мой порядок</string>
<plurals name="subtask_count">
<item quantity="one">%d подзадача</item>

@ -510,7 +510,6 @@
<string name="or_choose_a_location">නැතහොත් ස්ථානයක් තෝරන්න</string>
<string name="pick_this_location">මෙම ස්ථානය තෝරන්න</string>
<string name="choose_a_location">ස්ථානයක් තෝරන්න</string>
<string name="building_notifications">දැනුම්දීම් ජනනය කරමින් ඇත</string>
<string name="location_departed">%s පිටත් විය</string>
<string name="visit_website">වෙබ් අඩවියට පිවිසෙන්න</string>
<string name="location_remind_departure">පිටත්වීමේදී මතක් කරන්න</string>
@ -617,7 +616,6 @@
<string name="font_size">අකුරු ප්‍රමාණය</string>
<string name="enabled">සක්‍රීයයි</string>
<string name="subtasks">උප කාර්යයන්</string>
<string name="miscellaneous">විවිධ</string>
<string name="google_drive_backup">ගූගල් ඩ්‍රයිව් උපස්ථය</string>
<string name="backup_directory">උපස්ථ ෆෝල්ඩරය</string>
<string name="attachment_directory">ඇමුණුම් ෆෝල්ඩරය</string>

@ -233,7 +233,6 @@
<string name="attachment_directory">Priečinok pre prílohy</string>
<string name="backup_directory">Zalóhovať adresár</string>
<string name="google_drive_backup">Kopírovať na Disk Google</string>
<string name="miscellaneous">Rôzne</string>
<string name="enabled">Povolené</string>
<string name="font_size">Veľkosť písma</string>
<string name="row_spacing">Šírka riadkov</string>
@ -380,7 +379,6 @@
<string name="visit_website">Navštíviť stránky</string>
<string name="location_arrived">Príchod o %s</string>
<string name="location_departed">Odchod o %s</string>
<string name="building_notifications">Všeobecné upozornenia</string>
<string name="choose_a_location">Vybrať polohu</string>
<string name="pick_this_location">Vybrať túto polohu</string>
<string name="or_choose_a_location">Alebo vybrať polohu ručne</string>

@ -156,7 +156,6 @@
<string name="quiet_hours">Tyst period</string>
<string name="attachment_directory">Lagringsplats för bilagor</string>
<string name="backup_directory">Säkerhetskopieringsmapp</string>
<string name="miscellaneous">Övrigt</string>
<string name="enabled">Aktiverad</string>
<string name="font_size">Teckenstorlek</string>
<string name="customize_edit_screen">Anpassa redigeringsvyn</string>
@ -382,7 +381,6 @@
<string name="visit_website">Besök hemsida</string>
<string name="location_arrived">Anlände vid %s</string>
<string name="location_departed">Avgick %s</string>
<string name="building_notifications">Genererar aviseringar</string>
<string name="choose_a_location">Välj en plats</string>
<string name="pick_this_location">Välj denna plats</string>
<string name="or_choose_a_location">Eller välj en plats</string>

@ -221,7 +221,6 @@
<string name="or_choose_a_location">அல்லது இருப்பிடத்தைத் தேர்வுசெய்க</string>
<string name="pick_this_location">இந்த இருப்பிடத்தைத் தேர்ந்தெடுக்கவும்</string>
<string name="choose_a_location">இருப்பிடத்தைத் தேர்வுசெய்க</string>
<string name="building_notifications">அறிவிப்புகளை உருவாக்குகிறது</string>
<string name="location_departed">புறப்பட்டது %s</string>
<string name="location_arrived">%s இல் வந்து சேர்ந்தது</string>
<string name="visit_website">வலைத்தளத்தைப் பார்வையிடவும்</string>
@ -377,7 +376,6 @@
<string name="font_size">எழுத்துரு அளவு</string>
<string name="enabled">இயக்கப்பட்டது</string>
<string name="subtasks">துணை பணிகள்</string>
<string name="miscellaneous">இதர</string>
<string name="google_drive_backup">Google Driveல் நகலெடுக்கவும்</string>
<string name="backup_directory">காப்பு கோப்புறை</string>
<string name="attachment_directory">இணைப்பு கோப்புறை</string>

@ -194,7 +194,6 @@
<string name="or_choose_a_location">หรือเลือกตําแหน่งที่ตั้ง</string>
<string name="pick_this_location">เลือกตําแหน่งที่ตั้งนี้</string>
<string name="choose_a_location">เลือกตําแหน่งที่ตั้ง</string>
<string name="building_notifications">การสร้างการแจ้งเตือน</string>
<string name="location_departed">ออกเดินทาง %s</string>
<string name="location_arrived">มาถึง %s</string>
<string name="visit_website">เยี่ยมชมเว็บไซต์</string>
@ -363,7 +362,6 @@
<string name="font_size">ขนาดแบบอักษร</string>
<string name="enabled">เปิด</string>
<string name="subtasks">งานย่อย</string>
<string name="miscellaneous">เบ็ดเตล็ด</string>
<string name="google_drive_backup">การสํารองข้อมูลของ Google ไดรฟ์</string>
<string name="backup_directory">โฟลเดอร์สำรอง</string>
<string name="attachment_directory">โฟลเดอร์สิ่งที่แนบมา</string>

@ -233,7 +233,6 @@
<string name="attachment_directory">Ek klasörü</string>
<string name="backup_directory">Yedekleme klasörü</string>
<string name="google_drive_backup">Google Drive yedeği</string>
<string name="miscellaneous">Türlü</string>
<string name="enabled">Etkin</string>
<string name="font_size">Yazı tipi boyutu</string>
<string name="row_spacing">Satır aralığı</string>
@ -381,7 +380,6 @@
<string name="visit_website">Web siteyi ziyaret et</string>
<string name="location_arrived">Varıldı: %s</string>
<string name="location_departed">Kalkıldı: %s</string>
<string name="building_notifications">Bildirimler oluşturuluyor</string>
<string name="choose_a_location">Konum seç</string>
<string name="pick_this_location">Bu konumu seç</string>
<string name="or_choose_a_location">Ya da konum seç</string>

@ -248,7 +248,6 @@
<string name="attachment_directory">Тека з прикріпленими файлами</string>
<string name="backup_directory">Тека з резервними копіями</string>
<string name="google_drive_backup">Резервне копіювання до Google Drive</string>
<string name="miscellaneous">Різне</string>
<string name="enabled">Увімкнено</string>
<string name="font_size">Розмір шрифту</string>
<string name="row_spacing">Відстань між рядками</string>
@ -397,7 +396,6 @@
<string name="visit_website">Відвідати сторінку</string>
<string name="location_arrived">Прибув до %s</string>
<string name="location_departed">Відправився з %s</string>
<string name="building_notifications">Генерація нагадувань</string>
<string name="choose_a_location">Обрати місце</string>
<string name="pick_this_location">Обрати це місце</string>
<string name="or_choose_a_location">Або вказати інше місце</string>

@ -288,7 +288,6 @@
<string name="font_size">فونٹ سائز</string>
<string name="enabled">فعال</string>
<string name="subtasks">ذیلی ٹاسکس</string>
<string name="miscellaneous">متفرقات</string>
<string name="google_drive_backup">گوگل ڈرائیو بیک اپ</string>
<string name="backup_directory">بیک اپ فولڈر</string>
<string name="attachment_directory">اٹیچ منٹ فولڈر</string>
@ -438,7 +437,6 @@
<string name="repeats_plural_on_number_of_times">ہر %1$sکو %2$s پر دہراتا ہے، %3$d%4$sپر واقع ہوتا ہے</string>
<string name="pick_this_location">یہ جگہ منتخب کریں</string>
<string name="choose_a_location">جگہ منتخب کریں</string>
<string name="building_notifications">نوٹیفیکیشنز بنائیں</string>
<string name="location_departed">%s رخصت ہو گیا</string>
<string name="location_arrived">%s پر پہنچ گیا</string>
<string name="visit_website">ویب سائٹس کھولیں</string>

@ -247,7 +247,6 @@
<string name="or_choose_a_location">Hoặc chọn vị trí</string>
<string name="pick_this_location">Chọn vị trí này</string>
<string name="choose_a_location">Chọn vị trí</string>
<string name="building_notifications">Đang tạo các thông báo</string>
<string name="location_departed">Đã khởi hành %s</string>
<string name="location_arrived">Đã đến %s</string>
<string name="visit_website">Đi đến trang web</string>
@ -425,7 +424,6 @@
<string name="font_size">Cỡ chữ</string>
<string name="enabled">Bật</string>
<string name="subtasks">Công việc con</string>
<string name="miscellaneous">Khác</string>
<string name="google_drive_backup">Sao lưu Google Drive</string>
<string name="backup_directory">Thư mục sao lưu</string>
<string name="attachment_directory">Thư mục tệp đính kèm</string>

@ -216,7 +216,6 @@
<string name="quiet_hours">静音时间</string>
<string name="attachment_directory">附件文件夹</string>
<string name="backup_directory">备份文件夹</string>
<string name="miscellaneous">杂项</string>
<string name="enabled">启用</string>
<string name="font_size">字体大小</string>
<string name="row_spacing">行间距</string>
@ -374,7 +373,6 @@
<string name="visit_website">访问网站</string>
<string name="location_arrived">到达 %s</string>
<string name="location_departed">离开 %s</string>
<string name="building_notifications">生成通知</string>
<string name="choose_a_location">选择一个位置</string>
<string name="pick_this_location">选中这个位置</string>
<string name="or_choose_a_location">或选择一个位置</string>

@ -131,7 +131,6 @@
<string name="sound">鈴聲</string>
<string name="vibrations">震動</string>
<string name="backup_directory">備份資料夾</string>
<string name="miscellaneous">雜項設定</string>
<string name="enabled">啟用</string>
<string name="row_spacing">行距</string>
<string name="translations">貢獻翻譯</string>
@ -379,7 +378,6 @@
<string name="or_choose_a_location">或選擇一個地點</string>
<string name="pick_this_location">選擇此地點</string>
<string name="choose_a_location">選擇一個地點</string>
<string name="building_notifications">產生通知</string>
<string name="location_departed">於 %s 離開</string>
<string name="location_arrived">於 %s 到達</string>
<string name="visit_website">訪問網站</string>

@ -309,7 +309,6 @@ File %1$s contained %2$s.\n\n
<string name="attachment_directory">Attachment folder</string>
<string name="backup_directory">Backup folder</string>
<string name="google_drive_backup">Google Drive backup</string>
<string name="miscellaneous">Miscellaneous</string>
<string name="subtasks">Subtasks</string>
<string name="enabled">Enabled</string>
<string name="font_size">Font size</string>
@ -506,7 +505,6 @@ File %1$s contained %2$s.\n\n
<string name="visit_website">Visit website</string>
<string name="location_arrived">Arrived at %s</string>
<string name="location_departed">Departed %s</string>
<string name="building_notifications">Generating notifications</string>
<string name="choose_a_location">Choose a location</string>
<string name="pick_this_location">Select this location</string>
<string name="or_choose_a_location">Or choose a location</string>

@ -1,263 +0,0 @@
package org.tasks.jobs
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.mockito.AdditionalAnswers
import org.mockito.ArgumentMatchers
import org.mockito.Mockito
import org.tasks.Freeze.Companion.freezeAt
import org.tasks.data.Alarm.Companion.TYPE_DATE_TIME
import org.tasks.data.Alarm.Companion.TYPE_SNOOZE
import org.tasks.preferences.Preferences
import org.tasks.time.DateTime
import org.tasks.time.DateTimeUtils
import java.util.concurrent.TimeUnit
class NotificationQueueTest {
private lateinit var queue: NotificationQueue
private lateinit var workManager: WorkManager
private lateinit var preferences: Preferences
@Before
fun before() {
preferences = Mockito.mock(Preferences::class.java)
Mockito.`when`(preferences.adjustForQuietHours(ArgumentMatchers.anyLong()))
.then(AdditionalAnswers.returnsFirstArg<Any>())
workManager = Mockito.mock(WorkManager::class.java)
queue = NotificationQueue(preferences, workManager)
}
@After
fun after() {
Mockito.verifyNoMoreInteractions(workManager)
}
@Test
fun removeAlarmDoesntAffectOtherAlarm() {
val now = DateTimeUtils.currentTimeMillis()
queue.add(AlarmEntry(1, 1, now, TYPE_DATE_TIME))
Mockito.verify(workManager).scheduleNotification(now)
queue.add(AlarmEntry(2, 2, now, TYPE_DATE_TIME))
queue.remove(listOf(AlarmEntry(1, 1, now, TYPE_DATE_TIME)))
freezeAt(now) {
assertEquals(
listOf(AlarmEntry(2, 2, now, TYPE_DATE_TIME)),
queue.overdueJobs
)
}
}
@Test
fun removeByTaskDoesntAffectOtherAlarm() {
val now = DateTimeUtils.currentTimeMillis()
queue.add(AlarmEntry(1, 1, now, TYPE_DATE_TIME))
Mockito.verify(workManager).scheduleNotification(now)
queue.add(AlarmEntry(2, 2, now, TYPE_DATE_TIME))
queue.cancelForTask(1)
freezeAt(now) {
assertEquals(
listOf(AlarmEntry(2, 2, now, TYPE_DATE_TIME)),
queue.overdueJobs
)
}
}
@Test
fun rescheduleForFirstJob() {
queue.add(AlarmEntry(1, 2, 3, TYPE_DATE_TIME))
Mockito.verify(workManager).scheduleNotification(3)
}
@Test
fun dontRescheduleForLaterJobs() {
queue.add(AlarmEntry(1, 2, 3, TYPE_DATE_TIME))
queue.add(AlarmEntry(2, 3, 4, TYPE_DATE_TIME))
Mockito.verify(workManager).scheduleNotification(3)
}
@Test
fun rescheduleForNewerJob() {
queue.add(AlarmEntry(1, 1, 2, TYPE_DATE_TIME))
queue.add(AlarmEntry(1, 1, 1, TYPE_DATE_TIME))
val order = Mockito.inOrder(workManager)
order.verify(workManager).scheduleNotification(2)
order.verify(workManager).scheduleNotification(1)
}
@Test
fun rescheduleWhenCancelingOnlyJob() {
queue.add(AlarmEntry(1, 1, 2, TYPE_DATE_TIME))
queue.cancelForTask(1)
val order = Mockito.inOrder(workManager)
order.verify(workManager).scheduleNotification(2)
order.verify(workManager).cancelNotifications()
}
@Test
fun rescheduleWhenCancelingFirstJob() {
queue.add(AlarmEntry(1, 1, 1, 0))
queue.add(AlarmEntry(2, 2, 2, 0))
queue.cancelForTask(1)
val order = Mockito.inOrder(workManager)
order.verify(workManager).scheduleNotification(1)
order.verify(workManager).scheduleNotification(2)
}
@Test
fun dontRescheduleWhenCancelingLaterJob() {
queue.add(AlarmEntry(1, 1, 1, 0))
queue.add(AlarmEntry(2, 2, 2, 0))
queue.cancelForTask(2)
Mockito.verify(workManager).scheduleNotification(1)
}
@Test
fun nextScheduledTimeIsZeroWhenQueueIsEmpty() {
Mockito.`when`(preferences.adjustForQuietHours(ArgumentMatchers.anyLong()))
.thenReturn(1234L)
assertEquals(0, queue.nextScheduledTime())
}
@Test
fun adjustNextScheduledTimeForQuietHours() {
Mockito.`when`(preferences.adjustForQuietHours(ArgumentMatchers.anyLong()))
.thenReturn(1234L)
queue.add(AlarmEntry(1, 1, 1, TYPE_DATE_TIME))
Mockito.verify(workManager).scheduleNotification(1234)
}
@Test
fun overdueJobsAreReturned() {
val now = DateTimeUtils.currentTimeMillis()
queue.add(AlarmEntry(1, 1, now, TYPE_DATE_TIME))
queue.add(AlarmEntry(2, 1, now + ONE_MINUTE, TYPE_DATE_TIME))
Mockito.verify(workManager).scheduleNotification(now)
freezeAt(now) {
assertEquals(
listOf(AlarmEntry(1, 1, now, TYPE_DATE_TIME)), queue.overdueJobs
)
}
}
@Test
fun twoOverdueJobsAtSameTimeReturned() {
val now = DateTimeUtils.currentTimeMillis()
queue.add(AlarmEntry(1, 1, now, TYPE_DATE_TIME))
queue.add(AlarmEntry(2, 2, now, TYPE_DATE_TIME))
Mockito.verify(workManager).scheduleNotification(now)
freezeAt(now) {
assertEquals(
setOf(
AlarmEntry(1, 1, now, TYPE_DATE_TIME),
AlarmEntry(2, 2, now, TYPE_DATE_TIME)
),
queue.overdueJobs.toSet()
)
}
}
@Test
fun twoOverdueJobsAtDifferentTimes() {
val now = DateTimeUtils.currentTimeMillis()
queue.add(AlarmEntry(1, 1, now, TYPE_DATE_TIME))
queue.add(AlarmEntry(2, 2, now + ONE_MINUTE, TYPE_DATE_TIME))
Mockito.verify(workManager).scheduleNotification(now)
freezeAt(now + 2 * ONE_MINUTE) {
assertEquals(
listOf(
AlarmEntry(1, 1, now, TYPE_DATE_TIME),
AlarmEntry(2, 2, now + ONE_MINUTE, TYPE_DATE_TIME)
),
queue.overdueJobs
)
}
}
@Test
fun overdueJobsAreRemoved() {
val now = DateTimeUtils.currentTimeMillis()
queue.add(AlarmEntry(1, 1, now, TYPE_DATE_TIME))
queue.add(AlarmEntry(2, 2, now + ONE_MINUTE, TYPE_DATE_TIME))
Mockito.verify(workManager).scheduleNotification(now)
freezeAt(now) {
queue.remove(queue.overdueJobs)
}
assertEquals(
listOf(AlarmEntry(2, 2, now + ONE_MINUTE, TYPE_DATE_TIME)), queue.getJobs()
)
}
@Test
fun multipleOverduePeriodsLapsed() {
val now = DateTimeUtils.currentTimeMillis()
queue.add(AlarmEntry(1, 1, now, TYPE_DATE_TIME))
queue.add(AlarmEntry(2, 2, now + ONE_MINUTE, TYPE_DATE_TIME))
queue.add(AlarmEntry(3, 3, now + 2 * ONE_MINUTE, TYPE_DATE_TIME))
Mockito.verify(workManager).scheduleNotification(now)
freezeAt(now + ONE_MINUTE) {
queue.remove(queue.overdueJobs)
}
assertEquals(
listOf(AlarmEntry(3, 3, now + 2 * ONE_MINUTE, TYPE_DATE_TIME)), queue.getJobs()
)
}
@Test
fun clearShouldCancelExisting() {
queue.add(AlarmEntry(1, 1, 1, 0))
queue.clear()
val order = Mockito.inOrder(workManager)
order.verify(workManager).scheduleNotification(1)
order.verify(workManager).cancelNotifications()
assertEquals(0, queue.size())
}
@Test
fun ignoreInvalidCancelForByAlarm() {
val now = DateTimeUtils.currentTimeMillis()
queue.add(AlarmEntry(1, 1, now, TYPE_DATE_TIME))
queue.remove(listOf(AlarmEntry(2, 2, now, TYPE_DATE_TIME)))
Mockito.verify(workManager).scheduleNotification(now)
}
@Test
fun ignoreInvalidCancelForTask() {
val now = DateTimeUtils.currentTimeMillis()
queue.add(AlarmEntry(1, 1, now, TYPE_DATE_TIME))
queue.cancelForTask(2)
Mockito.verify(workManager).scheduleNotification(now)
}
@Test
fun allDuringSameMinuteAreOverdue() {
val now = DateTime(2017, 9, 3, 0, 14, 6, 455)
val due = DateTime(2017, 9, 3, 0, 14, 0, 0)
val snooze = DateTime(2017, 9, 3, 0, 14, 59, 999)
queue.add(AlarmEntry(1, 1, due.millis, TYPE_DATE_TIME))
queue.add(AlarmEntry(2, 2, snooze.millis, TYPE_SNOOZE))
queue.add(AlarmEntry(3, 3, due.plusMinutes(1).millis, TYPE_DATE_TIME))
Mockito.verify(workManager).scheduleNotification(due.millis)
freezeAt(now) {
val overdueJobs = queue.overdueJobs
assertEquals(
listOf(
AlarmEntry(1, 1, due.millis, TYPE_DATE_TIME),
AlarmEntry(2, 2, snooze.millis, TYPE_SNOOZE)
),
overdueJobs
)
queue.remove(overdueJobs)
assertEquals(
listOf(AlarmEntry(3, 3, due.plusMinutes(1).millis, TYPE_DATE_TIME)),
queue.getJobs()
)
}
}
companion object {
private val ONE_MINUTE = TimeUnit.MINUTES.toMillis(1)
}
}
Loading…
Cancel
Save