Refactor notification scheduling

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

@ -19,7 +19,6 @@ import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.injection.InjectingTestCase import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule import org.tasks.injection.ProductionModule
import org.tasks.jobs.AlarmEntry import org.tasks.jobs.AlarmEntry
import org.tasks.jobs.NotificationQueue
import org.tasks.makers.TaskMaker.COMPLETION_TIME import org.tasks.makers.TaskMaker.COMPLETION_TIME
import org.tasks.makers.TaskMaker.DELETION_TIME import org.tasks.makers.TaskMaker.DELETION_TIME
import org.tasks.makers.TaskMaker.DUE_DATE import org.tasks.makers.TaskMaker.DUE_DATE
@ -34,7 +33,6 @@ import javax.inject.Inject
class AlarmJobServiceTest : InjectingTestCase() { class AlarmJobServiceTest : InjectingTestCase() {
@Inject lateinit var alarmDao: AlarmDao @Inject lateinit var alarmDao: AlarmDao
@Inject lateinit var taskDao: TaskDao @Inject lateinit var taskDao: TaskDao
@Inject lateinit var jobs: NotificationQueue
@Inject lateinit var alarmService: AlarmService @Inject lateinit var alarmService: AlarmService
@Test @Test
@ -42,7 +40,7 @@ class AlarmJobServiceTest : InjectingTestCase() {
val task = taskDao.createNew(newTask()) val task = taskDao.createNew(newTask())
val alarm = insertAlarm(Alarm(task, DateTime(2017, 9, 24, 19, 57).millis, TYPE_DATE_TIME)) 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 @Test
@ -90,7 +88,7 @@ class AlarmJobServiceTest : InjectingTestCase() {
alarmDao.insert(Alarm(task, DateUtilities.ONE_HOUR, TYPE_RANDOM)) alarmDao.insert(Alarm(task, DateUtilities.ONE_HOUR, TYPE_RANDOM))
val alarm = alarmDao.insert(Alarm(task, now.plusMonths(12).millis, TYPE_SNOOZE)) 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 { private suspend fun insertAlarm(alarm: Alarm): Long {
@ -98,9 +96,13 @@ class AlarmJobServiceTest : InjectingTestCase() {
return alarm.id return alarm.id
} }
private suspend fun verify(vararg alarms: AlarmEntry) { private suspend fun verify(
alarmService.scheduleAllAlarms() 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"/> android:value="org.tasks.dashclock.DashClockSettings"/>
</service> </service>
<service
android:exported="false"
android:name=".jobs.NotificationService"/>
<activity <activity
android:exported="true" android:exported="true"
android:name=".dashclock.DashClockSettings"/> android:name=".dashclock.DashClockSettings"/>
@ -601,6 +597,8 @@
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name="org.tasks.jobs.NotificationReceiver" />
<activity <activity
android:name=".auth.MicrosoftAuthenticationActivity" android:name=".auth.MicrosoftAuthenticationActivity"
android:theme="@style/TranslucentDialog"/> android:theme="@style/TranslucentDialog"/>

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

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

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

@ -100,7 +100,6 @@ class Notifier @Inject constructor(
} }
?: false ?: false
} }
.takeLast(NotificationManager.MAX_NOTIFICATIONS)
if (notifications.isEmpty()) { if (notifications.isEmpty()) {
return 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.caldav.FileStorage
import org.tasks.data.CaldavDao import org.tasks.data.CaldavDao
import org.tasks.data.OpenTaskDao import org.tasks.data.OpenTaskDao
import org.tasks.data.TaskDao
import org.tasks.db.Migrations import org.tasks.db.Migrations
import org.tasks.jobs.WorkManager import org.tasks.jobs.WorkManager
import org.tasks.jobs.WorkManagerImpl import org.tasks.jobs.WorkManagerImpl
@ -53,6 +52,5 @@ internal class ProductionModule {
preferences: Preferences, preferences: Preferences,
caldavDao: CaldavDao, caldavDao: CaldavDao,
openTaskDao: OpenTaskDao, openTaskDao: OpenTaskDao,
taskDao: TaskDao, ): WorkManager = WorkManagerImpl(context, preferences, caldavDao, openTaskDao)
): WorkManager = WorkManagerImpl(context, preferences, caldavDao, openTaskDao, taskDao)
} }

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

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

@ -6,12 +6,11 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import com.todoroo.andlib.utility.AndroidUtilities import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.astrid.alarms.AlarmService
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import org.tasks.R import org.tasks.R
import org.tasks.injection.InjectingJobIntentService import org.tasks.injection.InjectingJobIntentService
import org.tasks.jobs.NotificationQueue import org.tasks.jobs.WorkManager
import org.tasks.notifications.NotificationManager import org.tasks.notifications.NotificationManager
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -19,17 +18,15 @@ import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class NotificationSchedulerIntentService : InjectingJobIntentService() { class NotificationSchedulerIntentService : InjectingJobIntentService() {
@Inject @ApplicationContext lateinit var context: Context @Inject @ApplicationContext lateinit var context: Context
@Inject lateinit var alarmService: AlarmService
@Inject lateinit var notificationQueue: NotificationQueue
@Inject lateinit var notificationManager: NotificationManager @Inject lateinit var notificationManager: NotificationManager
@Inject lateinit var workManager: WorkManager
override suspend fun doWork(intent: Intent) { override suspend fun doWork(intent: Intent) {
Timber.d("onHandleWork(%s)", intent) Timber.d("onHandleWork(%s)", intent)
createNotificationChannels() createNotificationChannels()
notificationQueue.clear()
val cancelExistingNotifications = intent.getBooleanExtra(EXTRA_CANCEL_EXISTING_NOTIFICATIONS, false) val cancelExistingNotifications = intent.getBooleanExtra(EXTRA_CANCEL_EXISTING_NOTIFICATIONS, false)
notificationManager.restoreNotifications(cancelExistingNotifications) notificationManager.restoreNotifications(cancelExistingNotifications)
alarmService.scheduleAllAlarms() workManager.triggerNotifications()
} }
private fun createNotificationChannels() { private fun createNotificationChannels() {
@ -42,9 +39,6 @@ class NotificationSchedulerIntentService : InjectingJobIntentService() {
notificationManager.createNotificationChannel( notificationManager.createNotificationChannel(
createNotificationChannel( createNotificationChannel(
NotificationManager.NOTIFICATION_CHANNEL_TIMERS, R.string.TEA_timer_controls, true)) 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="add_account">أضف حساب</string>
<string name="led_notification">إشعار LED</string> <string name="led_notification">إشعار LED</string>
<string name="wearable_notifications_summary">عرض الإشعارات على الساعة الذكية</string> <string name="wearable_notifications_summary">عرض الإشعارات على الساعة الذكية</string>
<string name="building_notifications">توليد الإخطارات</string>
<string name="bundle_notifications_summary">ادمج عدة إشعارات في شعار واحد</string> <string name="bundle_notifications_summary">ادمج عدة إشعارات في شعار واحد</string>
<string name="notification_troubleshooting_summary">انقر هنا إذا كنت تواجه مشاكل في الإشعارات</string> <string name="notification_troubleshooting_summary">انقر هنا إذا كنت تواجه مشاكل في الإشعارات</string>
<string name="TEA_control_location">الموقع</string> <string name="TEA_control_location">الموقع</string>
@ -413,7 +412,6 @@
<string name="url_required">الرابط مطلوب</string> <string name="url_required">الرابط مطلوب</string>
<string name="location_radius_meters">%s م</string> <string name="location_radius_meters">%s م</string>
<string name="header_spacing">التباعد</string> <string name="header_spacing">التباعد</string>
<string name="miscellaneous">متنوّع</string>
<string name="CFC_tag_text">علامة: \?</string> <string name="CFC_tag_text">علامة: \?</string>
<string name="CFC_importance_text">الأولويّة على الأقل \?</string> <string name="CFC_importance_text">الأولويّة على الأقل \?</string>
<string name="TEA_control_timer">ضوابط المؤقت</string> <string name="TEA_control_timer">ضوابط المؤقت</string>

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

@ -139,7 +139,6 @@
<string name="vibrations">Vibrace</string> <string name="vibrations">Vibrace</string>
<string name="quiet_hours">Období nerušení</string> <string name="quiet_hours">Období nerušení</string>
<string name="backup_directory">Složka pro zálohy</string> <string name="backup_directory">Složka pro zálohy</string>
<string name="miscellaneous">Různé</string>
<string name="enabled">Povoleno</string> <string name="enabled">Povoleno</string>
<string name="font_size">Velikost písma</string> <string name="font_size">Velikost písma</string>
<string name="source_code">Zdrojové kódy</string> <string name="source_code">Zdrojové kódy</string>
@ -492,7 +491,6 @@
<string name="color_wheel">Více barev</string> <string name="color_wheel">Více barev</string>
<string name="invalid_username_or_password">Neplatné uživatelské jméno nebo heslo</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="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="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="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> <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="or_choose_a_location">Eller vælg et sted</string>
<string name="pick_this_location">Vælg dette sted</string> <string name="pick_this_location">Vælg dette sted</string>
<string name="choose_a_location">Vælg 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_departed">Afgået fra %s</string>
<string name="location_arrived">Ankommet til %s</string> <string name="location_arrived">Ankommet til %s</string>
<string name="visit_website">Besøg hjemmeside</string> <string name="visit_website">Besøg hjemmeside</string>
@ -330,7 +329,6 @@
<string name="font_size">Skriftstørrelse</string> <string name="font_size">Skriftstørrelse</string>
<string name="enabled">Aktiveret</string> <string name="enabled">Aktiveret</string>
<string name="subtasks">Underopgaver</string> <string name="subtasks">Underopgaver</string>
<string name="miscellaneous">Diverse</string>
<string name="google_drive_backup">Google Drive-backup</string> <string name="google_drive_backup">Google Drive-backup</string>
<string name="backup_directory">Mappe til sikkerhedskopi</string> <string name="backup_directory">Mappe til sikkerhedskopi</string>
<string name="attachment_directory">Mappe til vedhæftede filer</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="attachment_directory">Ordner für Anhänge</string>
<string name="backup_directory">Sicherungsordner</string> <string name="backup_directory">Sicherungsordner</string>
<string name="google_drive_backup">Google Drive-Sicherung</string> <string name="google_drive_backup">Google Drive-Sicherung</string>
<string name="miscellaneous">Verschiedenes</string>
<string name="enabled">Aktiviert</string> <string name="enabled">Aktiviert</string>
<string name="font_size">Schriftgröße</string> <string name="font_size">Schriftgröße</string>
<string name="row_spacing">Zeilenabstand</string> <string name="row_spacing">Zeilenabstand</string>
@ -376,7 +375,6 @@
<string name="visit_website">Website öffnen</string> <string name="visit_website">Website öffnen</string>
<string name="location_arrived">Angekommen um %s</string> <string name="location_arrived">Angekommen um %s</string>
<string name="location_departed">Abgereist 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="choose_a_location">Ort auswählen</string>
<string name="pick_this_location">Diesen 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> <string name="or_choose_a_location">Oder Ort auswählen</string>

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

@ -246,7 +246,6 @@
<string name="attachment_directory">Carpeta de datos adjuntos</string> <string name="attachment_directory">Carpeta de datos adjuntos</string>
<string name="backup_directory">Carpeta de copias de seguridad</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="google_drive_backup">Copia de seguridad en Google Drive</string>
<string name="miscellaneous">Miscelánea</string>
<string name="enabled">Activado</string> <string name="enabled">Activado</string>
<string name="font_size">Tamaño de letra</string> <string name="font_size">Tamaño de letra</string>
<string name="row_spacing">Espaciado de fila</string> <string name="row_spacing">Espaciado de fila</string>
@ -393,7 +392,6 @@
<string name="visit_website">Visitar página web</string> <string name="visit_website">Visitar página web</string>
<string name="location_arrived">Llegada a %s</string> <string name="location_arrived">Llegada a %s</string>
<string name="location_departed">Salida de %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="choose_a_location">Escoger ubicación</string>
<string name="pick_this_location">Seleccionar esta 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> <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="attachment_directory">Failide kaust</string>
<string name="backup_directory">Varukoopia kaust</string> <string name="backup_directory">Varukoopia kaust</string>
<string name="google_drive_backup">Kopeeri Google Drive-i</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="enabled">Sisse lülitatud</string>
<string name="font_size">Teksti suurus</string> <string name="font_size">Teksti suurus</string>
<string name="row_spacing">Reavahed</string> <string name="row_spacing">Reavahed</string>

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

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

@ -222,7 +222,6 @@
<string name="quiet_hours">Hiljainen aika</string> <string name="quiet_hours">Hiljainen aika</string>
<string name="attachment_directory">Liitehakemisto</string> <string name="attachment_directory">Liitehakemisto</string>
<string name="backup_directory">Varmuuskopioiden hakemisto</string> <string name="backup_directory">Varmuuskopioiden hakemisto</string>
<string name="miscellaneous">Sekalaista</string>
<string name="enabled">Sallittu</string> <string name="enabled">Sallittu</string>
<string name="font_size">Kirjasimen koko</string> <string name="font_size">Kirjasimen koko</string>
<string name="row_spacing">Rivinväli</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="or_choose_a_location">Tai valitse muu sijainti</string>
<string name="pick_this_location">Valitse tämä sijainti</string> <string name="pick_this_location">Valitse tämä sijainti</string>
<string name="choose_a_location">Valitse 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="visit_website">Käy verkkosivulla</string>
<string name="location_remind_departure">Muistuta lähtiessä</string> <string name="location_remind_departure">Muistuta lähtiessä</string>
<string name="location_remind_arrival">Muistuta saapuessa</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="attachment_directory">Dossier des pièces jointes</string>
<string name="backup_directory">Dossier de sauvegarde</string> <string name="backup_directory">Dossier de sauvegarde</string>
<string name="google_drive_backup">Sauvegarde « Google Drive »</string> <string name="google_drive_backup">Sauvegarde « Google Drive »</string>
<string name="miscellaneous">Divers</string>
<string name="enabled">Activé</string> <string name="enabled">Activé</string>
<string name="font_size">Taille de police</string> <string name="font_size">Taille de police</string>
<string name="row_spacing">Espacement des lignes</string> <string name="row_spacing">Espacement des lignes</string>
@ -378,7 +377,6 @@
<string name="visit_website">Visiter le site web</string> <string name="visit_website">Visiter le site web</string>
<string name="location_arrived">Arrivé à %s</string> <string name="location_arrived">Arrivé à %s</string>
<string name="location_departed">Parti %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="choose_a_location">Choisir un emplacement</string>
<string name="pick_this_location">Sélectionner cet emplacement</string> <string name="pick_this_location">Sélectionner cet emplacement</string>
<string name="or_choose_a_location">Ou choisir un 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="quiet_hours">Horario silencioso</string>
<string name="attachment_directory">Cartafol de anexos</string> <string name="attachment_directory">Cartafol de anexos</string>
<string name="backup_directory">Cartafol de copias de seguranza</string> <string name="backup_directory">Cartafol de copias de seguranza</string>
<string name="miscellaneous">Miscelánea</string>
<string name="enabled">Activado</string> <string name="enabled">Activado</string>
<string name="font_size">Tamaño da fonte</string> <string name="font_size">Tamaño da fonte</string>
<string name="row_spacing">Espazado de fila</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="visit_website">Visitar a páxina web</string>
<string name="location_arrived">Chegouse ás %s</string> <string name="location_arrived">Chegouse ás %s</string>
<string name="location_departed">Saiuse á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="choose_a_location">Escolle a posición</string>
<string name="pick_this_location">Escoller esta 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> <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="or_choose_a_location">Ili odaberi jednu lokaciju</string>
<string name="pick_this_location">Odaberi ovu lokaciju</string> <string name="pick_this_location">Odaberi ovu lokaciju</string>
<string name="choose_a_location">Odaberi jednu 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_departed">Otišao/la %s</string>
<string name="location_arrived">Stigao/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> <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="translations">Doprinesi prijevodima</string>
<string name="row_spacing">Razmak između redaka</string> <string name="row_spacing">Razmak između redaka</string>
<string name="header_spacing">Razmak</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="google_drive_backup">Sigurnosna kopija na Google Drive</string>
<string name="backup_directory">Mapa sigurnosnih kopija</string> <string name="backup_directory">Mapa sigurnosnih kopija</string>
<string name="attachment_directory">Mapa priloga</string> <string name="attachment_directory">Mapa priloga</string>

@ -230,7 +230,6 @@
<string name="attachment_directory">Csatolmányok mappája</string> <string name="attachment_directory">Csatolmányok mappája</string>
<string name="backup_directory">Mentési mappa</string> <string name="backup_directory">Mentési mappa</string>
<string name="google_drive_backup">Mentés Google Drive-ba</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="enabled">Engedélyezve</string>
<string name="font_size">Karakter méret</string> <string name="font_size">Karakter méret</string>
<string name="row_spacing">Sorköz</string> <string name="row_spacing">Sorköz</string>
@ -379,7 +378,6 @@
<string name="visit_website">Weboldal megnyitása</string> <string name="visit_website">Weboldal megnyitása</string>
<string name="location_arrived">Megérkezett ekkor: %s</string> <string name="location_arrived">Megérkezett ekkor: %s</string>
<string name="location_departed">Elindult 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="choose_a_location">Hely kiválasztása</string>
<string name="pick_this_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> <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="attachment_directory">Direktori isi lampiran</string>
<string name="backup_directory">Direktori cadangan</string> <string name="backup_directory">Direktori cadangan</string>
<string name="google_drive_backup">Cadangan Google Drive</string> <string name="google_drive_backup">Cadangan Google Drive</string>
<string name="miscellaneous">Lain-lain</string>
<string name="subtasks">Subtugas</string> <string name="subtasks">Subtugas</string>
<string name="enabled">Aktif</string> <string name="enabled">Aktif</string>
<string name="font_size">Ukuran huruf</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="davx5_selection_description">Sinkronkan tugas Anda dengan aplikasi DAVx⁵</string>
<string name="whats_new">Apa yang Baru</string> <string name="whats_new">Apa yang Baru</string>
<string name="missing_permissions">Perizinan hilang</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_departure">Ingatkan pada saat keberangkatan</string>
<string name="location_remind_arrival">Ingatkan saat kedatangan</string> <string name="location_remind_arrival">Ingatkan saat kedatangan</string>
<string name="action_new_task">Tugas Baru</string> <string name="action_new_task">Tugas Baru</string>

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

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

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

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

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

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

@ -230,7 +230,6 @@
<string name="quiet_hours">Rusttijd</string> <string name="quiet_hours">Rusttijd</string>
<string name="attachment_directory">Map voor bijlagen</string> <string name="attachment_directory">Map voor bijlagen</string>
<string name="google_drive_backup">Backup naar Google Drive</string> <string name="google_drive_backup">Backup naar Google Drive</string>
<string name="miscellaneous">Diversen</string>
<string name="enabled">Actief</string> <string name="enabled">Actief</string>
<string name="font_size">Lettergrootte</string> <string name="font_size">Lettergrootte</string>
<string name="row_spacing">Regelafstand</string> <string name="row_spacing">Regelafstand</string>
@ -373,7 +372,6 @@
<string name="visit_website">Website bezoeken</string> <string name="visit_website">Website bezoeken</string>
<string name="location_arrived">Aangekomen op %s</string> <string name="location_arrived">Aangekomen op %s</string>
<string name="location_departed">Vertrokken van %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="choose_a_location">Locatie kiezen</string>
<string name="pick_this_location">Locatie onder naald kiezen</string> <string name="pick_this_location">Locatie onder naald kiezen</string>
<string name="or_choose_a_location">Of kies een locatie</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="attachment_directory">Katalog załączników</string>
<string name="backup_directory">Katalog kopii zapasowych</string> <string name="backup_directory">Katalog kopii zapasowych</string>
<string name="google_drive_backup">Kopia zapasowa na Dysku Google</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="enabled">Włączone</string>
<string name="font_size">Rozmiar czcionki</string> <string name="font_size">Rozmiar czcionki</string>
<string name="row_spacing">Odstęp wierszy</string> <string name="row_spacing">Odstęp wierszy</string>
@ -388,7 +387,6 @@
<string name="visit_website">Wejdź na stronę</string> <string name="visit_website">Wejdź na stronę</string>
<string name="location_arrived">Przybyto do %s</string> <string name="location_arrived">Przybyto do %s</string>
<string name="location_departed">Wyruszono z %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="choose_a_location">Wybierz lokalizację</string>
<string name="pick_this_location">Ustaw wybraną lokalizację</string> <string name="pick_this_location">Ustaw wybraną lokalizację</string>
<string name="or_choose_a_location">Lub wybierz 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="attachment_directory">Pasta de anexo</string>
<string name="backup_directory">Pasta de backup</string> <string name="backup_directory">Pasta de backup</string>
<string name="google_drive_backup">Backup no Google Drive</string> <string name="google_drive_backup">Backup no Google Drive</string>
<string name="miscellaneous">Miscelânea</string>
<string name="enabled">Ativado</string> <string name="enabled">Ativado</string>
<string name="font_size">Tamanho do texto</string> <string name="font_size">Tamanho do texto</string>
<string name="row_spacing">Espaçamento da linha</string> <string name="row_spacing">Espaçamento da linha</string>
@ -391,7 +390,6 @@
<string name="visit_website">Visitar website</string> <string name="visit_website">Visitar website</string>
<string name="location_arrived">Chegou às %s</string> <string name="location_arrived">Chegou às %s</string>
<string name="location_departed">Saiu à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="choose_a_location">Escolher uma localização</string>
<string name="pick_this_location">Selecionar essa 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> <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="quiet_hours">Horas de silêncio</string>
<string name="attachment_directory">Pasta de anexo</string> <string name="attachment_directory">Pasta de anexo</string>
<string name="backup_directory">Pasta de cópias de segurança</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="enabled">Ativo</string>
<string name="font_size">Tamanho das letras</string> <string name="font_size">Tamanho das letras</string>
<string name="row_spacing">Espaçamento entre linhas</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="or_choose_a_location">Ou escolha uma localização</string>
<string name="pick_this_location">Selecionar esta 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="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_departed">Saiu às %s</string>
<string name="location_arrived">Chegou às %s</string> <string name="location_arrived">Chegou às %s</string>
<string name="visit_website">Visitar site web</string> <string name="visit_website">Visitar site web</string>

@ -251,7 +251,6 @@
<string name="font_size">Dimensiunea fontului</string> <string name="font_size">Dimensiunea fontului</string>
<string name="enabled">Activat</string> <string name="enabled">Activat</string>
<string name="subtasks">Sub-sarcini</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="google_drive_backup">Copie de rezervă Google Drive</string>
<string name="backup_directory">Dosar de rezervă</string> <string name="backup_directory">Dosar de rezervă</string>
<string name="attachment_directory">Fișier atașat</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="or_choose_a_location">Sau alege o locație</string>
<string name="pick_this_location">Selectează această 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="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_departed">A plecat %s</string>
<string name="location_arrived">A sosit la %s</string> <string name="location_arrived">A sosit la %s</string>
<string name="visit_website">Vizitează site-ul web</string> <string name="visit_website">Vizitează site-ul web</string>

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

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

@ -233,7 +233,6 @@
<string name="attachment_directory">Priečinok pre prílohy</string> <string name="attachment_directory">Priečinok pre prílohy</string>
<string name="backup_directory">Zalóhovať adresár</string> <string name="backup_directory">Zalóhovať adresár</string>
<string name="google_drive_backup">Kopírovať na Disk Google</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="enabled">Povolené</string>
<string name="font_size">Veľkosť písma</string> <string name="font_size">Veľkosť písma</string>
<string name="row_spacing">Šírka riadkov</string> <string name="row_spacing">Šírka riadkov</string>
@ -380,7 +379,6 @@
<string name="visit_website">Navštíviť stránky</string> <string name="visit_website">Navštíviť stránky</string>
<string name="location_arrived">Príchod o %s</string> <string name="location_arrived">Príchod o %s</string>
<string name="location_departed">Odchod 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="choose_a_location">Vybrať polohu</string>
<string name="pick_this_location">Vybrať túto polohu</string> <string name="pick_this_location">Vybrať túto polohu</string>
<string name="or_choose_a_location">Alebo vybrať polohu ručne</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="quiet_hours">Tyst period</string>
<string name="attachment_directory">Lagringsplats för bilagor</string> <string name="attachment_directory">Lagringsplats för bilagor</string>
<string name="backup_directory">Säkerhetskopieringsmapp</string> <string name="backup_directory">Säkerhetskopieringsmapp</string>
<string name="miscellaneous">Övrigt</string>
<string name="enabled">Aktiverad</string> <string name="enabled">Aktiverad</string>
<string name="font_size">Teckenstorlek</string> <string name="font_size">Teckenstorlek</string>
<string name="customize_edit_screen">Anpassa redigeringsvyn</string> <string name="customize_edit_screen">Anpassa redigeringsvyn</string>
@ -382,7 +381,6 @@
<string name="visit_website">Besök hemsida</string> <string name="visit_website">Besök hemsida</string>
<string name="location_arrived">Anlände vid %s</string> <string name="location_arrived">Anlände vid %s</string>
<string name="location_departed">Avgick %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="choose_a_location">Välj en plats</string>
<string name="pick_this_location">Välj denna plats</string> <string name="pick_this_location">Välj denna plats</string>
<string name="or_choose_a_location">Eller välj en 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="or_choose_a_location">அல்லது இருப்பிடத்தைத் தேர்வுசெய்க</string>
<string name="pick_this_location">இந்த இருப்பிடத்தைத் தேர்ந்தெடுக்கவும்</string> <string name="pick_this_location">இந்த இருப்பிடத்தைத் தேர்ந்தெடுக்கவும்</string>
<string name="choose_a_location">இருப்பிடத்தைத் தேர்வுசெய்க</string> <string name="choose_a_location">இருப்பிடத்தைத் தேர்வுசெய்க</string>
<string name="building_notifications">அறிவிப்புகளை உருவாக்குகிறது</string>
<string name="location_departed">புறப்பட்டது %s</string> <string name="location_departed">புறப்பட்டது %s</string>
<string name="location_arrived">%s இல் வந்து சேர்ந்தது</string> <string name="location_arrived">%s இல் வந்து சேர்ந்தது</string>
<string name="visit_website">வலைத்தளத்தைப் பார்வையிடவும்</string> <string name="visit_website">வலைத்தளத்தைப் பார்வையிடவும்</string>
@ -377,7 +376,6 @@
<string name="font_size">எழுத்துரு அளவு</string> <string name="font_size">எழுத்துரு அளவு</string>
<string name="enabled">இயக்கப்பட்டது</string> <string name="enabled">இயக்கப்பட்டது</string>
<string name="subtasks">துணை பணிகள்</string> <string name="subtasks">துணை பணிகள்</string>
<string name="miscellaneous">இதர</string>
<string name="google_drive_backup">Google Driveல் நகலெடுக்கவும்</string> <string name="google_drive_backup">Google Driveல் நகலெடுக்கவும்</string>
<string name="backup_directory">காப்பு கோப்புறை</string> <string name="backup_directory">காப்பு கோப்புறை</string>
<string name="attachment_directory">இணைப்பு கோப்புறை</string> <string name="attachment_directory">இணைப்பு கோப்புறை</string>

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

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

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

@ -288,7 +288,6 @@
<string name="font_size">فونٹ سائز</string> <string name="font_size">فونٹ سائز</string>
<string name="enabled">فعال</string> <string name="enabled">فعال</string>
<string name="subtasks">ذیلی ٹاسکس</string> <string name="subtasks">ذیلی ٹاسکس</string>
<string name="miscellaneous">متفرقات</string>
<string name="google_drive_backup">گوگل ڈرائیو بیک اپ</string> <string name="google_drive_backup">گوگل ڈرائیو بیک اپ</string>
<string name="backup_directory">بیک اپ فولڈر</string> <string name="backup_directory">بیک اپ فولڈر</string>
<string name="attachment_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="repeats_plural_on_number_of_times">ہر %1$sکو %2$s پر دہراتا ہے، %3$d%4$sپر واقع ہوتا ہے</string>
<string name="pick_this_location">یہ جگہ منتخب کریں</string> <string name="pick_this_location">یہ جگہ منتخب کریں</string>
<string name="choose_a_location">جگہ منتخب کریں</string> <string name="choose_a_location">جگہ منتخب کریں</string>
<string name="building_notifications">نوٹیفیکیشنز بنائیں</string>
<string name="location_departed">%s رخصت ہو گیا</string> <string name="location_departed">%s رخصت ہو گیا</string>
<string name="location_arrived">%s پر پہنچ گیا</string> <string name="location_arrived">%s پر پہنچ گیا</string>
<string name="visit_website">ویب سائٹس کھولیں</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="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="pick_this_location">Chọn vị trí này</string>
<string name="choose_a_location">Chọn vị trí</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_departed">Đã khởi hành %s</string>
<string name="location_arrived">Đã đến %s</string> <string name="location_arrived">Đã đến %s</string>
<string name="visit_website">Đi đến trang web</string> <string name="visit_website">Đi đến trang web</string>
@ -425,7 +424,6 @@
<string name="font_size">Cỡ chữ</string> <string name="font_size">Cỡ chữ</string>
<string name="enabled">Bật</string> <string name="enabled">Bật</string>
<string name="subtasks">Công việc con</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="google_drive_backup">Sao lưu Google Drive</string>
<string name="backup_directory">Thư mục sao lưu</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> <string name="attachment_directory">Thư mục tệp đính kèm</string>

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

@ -131,7 +131,6 @@
<string name="sound">鈴聲</string> <string name="sound">鈴聲</string>
<string name="vibrations">震動</string> <string name="vibrations">震動</string>
<string name="backup_directory">備份資料夾</string> <string name="backup_directory">備份資料夾</string>
<string name="miscellaneous">雜項設定</string>
<string name="enabled">啟用</string> <string name="enabled">啟用</string>
<string name="row_spacing">行距</string> <string name="row_spacing">行距</string>
<string name="translations">貢獻翻譯</string> <string name="translations">貢獻翻譯</string>
@ -379,7 +378,6 @@
<string name="or_choose_a_location">或選擇一個地點</string> <string name="or_choose_a_location">或選擇一個地點</string>
<string name="pick_this_location">選擇此地點</string> <string name="pick_this_location">選擇此地點</string>
<string name="choose_a_location">選擇一個地點</string> <string name="choose_a_location">選擇一個地點</string>
<string name="building_notifications">產生通知</string>
<string name="location_departed">於 %s 離開</string> <string name="location_departed">於 %s 離開</string>
<string name="location_arrived">於 %s 到達</string> <string name="location_arrived">於 %s 到達</string>
<string name="visit_website">訪問網站</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="attachment_directory">Attachment folder</string>
<string name="backup_directory">Backup folder</string> <string name="backup_directory">Backup folder</string>
<string name="google_drive_backup">Google Drive backup</string> <string name="google_drive_backup">Google Drive backup</string>
<string name="miscellaneous">Miscellaneous</string>
<string name="subtasks">Subtasks</string> <string name="subtasks">Subtasks</string>
<string name="enabled">Enabled</string> <string name="enabled">Enabled</string>
<string name="font_size">Font size</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="visit_website">Visit website</string>
<string name="location_arrived">Arrived at %s</string> <string name="location_arrived">Arrived at %s</string>
<string name="location_departed">Departed %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="choose_a_location">Choose a location</string>
<string name="pick_this_location">Select this location</string> <string name="pick_this_location">Select this location</string>
<string name="or_choose_a_location">Or choose a 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