mirror of https://github.com/tasks/tasks
Convert random and snooze reminders to alarms
Display snooze time in edit screenpull/1769/head
parent
67899e6fff
commit
cb834a9818
@ -1,156 +0,0 @@
|
|||||||
package com.todoroo.astrid.reminders
|
|
||||||
|
|
||||||
import com.natpryce.makeiteasy.MakeItEasy.with
|
|
||||||
import com.todoroo.andlib.utility.DateUtilities
|
|
||||||
import com.todoroo.astrid.data.Task
|
|
||||||
import dagger.hilt.android.testing.HiltAndroidTest
|
|
||||||
import dagger.hilt.android.testing.UninstallModules
|
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
import org.junit.Assert.assertTrue
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
|
||||||
import org.tasks.Freeze.Companion.freezeClock
|
|
||||||
import org.tasks.data.TaskDao
|
|
||||||
import org.tasks.date.DateTimeUtils.newDateTime
|
|
||||||
import org.tasks.injection.InjectingTestCase
|
|
||||||
import org.tasks.injection.ProductionModule
|
|
||||||
import org.tasks.jobs.NotificationQueue
|
|
||||||
import org.tasks.jobs.ReminderEntry
|
|
||||||
import org.tasks.makers.TaskMaker.CREATION_TIME
|
|
||||||
import org.tasks.makers.TaskMaker.DUE_TIME
|
|
||||||
import org.tasks.makers.TaskMaker.ID
|
|
||||||
import org.tasks.makers.TaskMaker.RANDOM_REMINDER_PERIOD
|
|
||||||
import org.tasks.makers.TaskMaker.REMINDERS
|
|
||||||
import org.tasks.makers.TaskMaker.REMINDER_LAST
|
|
||||||
import org.tasks.makers.TaskMaker.SNOOZE_TIME
|
|
||||||
import org.tasks.makers.TaskMaker.newTask
|
|
||||||
import org.tasks.preferences.Preferences
|
|
||||||
import org.tasks.reminders.Random
|
|
||||||
import org.tasks.time.DateTime
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@UninstallModules(ProductionModule::class)
|
|
||||||
@HiltAndroidTest
|
|
||||||
class ReminderServiceTest : InjectingTestCase() {
|
|
||||||
@Inject lateinit var preferences: Preferences
|
|
||||||
@Inject lateinit var taskDao: TaskDao
|
|
||||||
@Inject lateinit var jobs: NotificationQueue
|
|
||||||
|
|
||||||
private lateinit var service: ReminderService
|
|
||||||
private lateinit var random: RandomStub
|
|
||||||
|
|
||||||
@Before
|
|
||||||
override fun setUp() {
|
|
||||||
super.setUp()
|
|
||||||
random = RandomStub()
|
|
||||||
preferences.clear()
|
|
||||||
service = ReminderService(jobs, random, taskDao)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun ignoreStaleSnoozeTime() {
|
|
||||||
val task = newTask(
|
|
||||||
with(ID, 1L),
|
|
||||||
with(DUE_TIME, newDateTime()),
|
|
||||||
with(SNOOZE_TIME, newDateTime().minusMinutes(5)),
|
|
||||||
with(REMINDER_LAST, newDateTime().minusMinutes(4))
|
|
||||||
)
|
|
||||||
|
|
||||||
service.scheduleAlarm(task)
|
|
||||||
|
|
||||||
assertTrue(jobs.isEmpty())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun dontIgnoreMissedSnoozeTime() {
|
|
||||||
val dueDate = newDateTime()
|
|
||||||
val task = newTask(
|
|
||||||
with(ID, 1L),
|
|
||||||
with(DUE_TIME, dueDate),
|
|
||||||
with(SNOOZE_TIME, dueDate.minusMinutes(4)),
|
|
||||||
with(REMINDER_LAST, dueDate.minusMinutes(5)),
|
|
||||||
with(REMINDERS, Task.NOTIFY_AT_DEADLINE))
|
|
||||||
|
|
||||||
service.scheduleAlarm(task)
|
|
||||||
|
|
||||||
verify(ReminderEntry(1, task.reminderSnooze, ReminderService.TYPE_SNOOZE))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun scheduleInitialRandomReminder() {
|
|
||||||
random.seed = 0.3865f
|
|
||||||
|
|
||||||
freezeClock {
|
|
||||||
val now = newDateTime()
|
|
||||||
val task = newTask(
|
|
||||||
with(ID, 1L),
|
|
||||||
with(REMINDER_LAST, null as DateTime?),
|
|
||||||
with(CREATION_TIME, now.minusDays(1)),
|
|
||||||
with(RANDOM_REMINDER_PERIOD, DateUtilities.ONE_WEEK))
|
|
||||||
|
|
||||||
service.scheduleAlarm(task)
|
|
||||||
|
|
||||||
verify(ReminderEntry(1L, now.minusDays(1).millis + 584206592, ReminderService.TYPE_RANDOM))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun scheduleNextRandomReminder() {
|
|
||||||
random.seed = 0.3865f
|
|
||||||
|
|
||||||
freezeClock {
|
|
||||||
val now = newDateTime()
|
|
||||||
val task = newTask(
|
|
||||||
with(ID, 1L),
|
|
||||||
with(REMINDER_LAST, now.minusDays(1)),
|
|
||||||
with(CREATION_TIME, now.minusDays(30)),
|
|
||||||
with(RANDOM_REMINDER_PERIOD, DateUtilities.ONE_WEEK))
|
|
||||||
|
|
||||||
service.scheduleAlarm(task)
|
|
||||||
|
|
||||||
verify(ReminderEntry(1L, now.minusDays(1).millis + 584206592, ReminderService.TYPE_RANDOM))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun scheduleOverdueRandomReminder() {
|
|
||||||
random.seed = 0.3865f
|
|
||||||
|
|
||||||
freezeClock {
|
|
||||||
val now = newDateTime()
|
|
||||||
val task = newTask(
|
|
||||||
with(ID, 1L),
|
|
||||||
with(REMINDER_LAST, now.minusDays(14)),
|
|
||||||
with(CREATION_TIME, now.minusDays(30)),
|
|
||||||
with(RANDOM_REMINDER_PERIOD, DateUtilities.ONE_WEEK))
|
|
||||||
|
|
||||||
service.scheduleAlarm(task)
|
|
||||||
|
|
||||||
verify(ReminderEntry(1L, now.millis + 10148400, ReminderService.TYPE_RANDOM))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun snoozeOverridesAll() {
|
|
||||||
val now = newDateTime()
|
|
||||||
val task = newTask(
|
|
||||||
with(ID, 1L),
|
|
||||||
with(DUE_TIME, now),
|
|
||||||
with(SNOOZE_TIME, now.plusMonths(12)),
|
|
||||||
with(REMINDERS, Task.NOTIFY_AT_DEADLINE or Task.NOTIFY_AFTER_DEADLINE),
|
|
||||||
with(RANDOM_REMINDER_PERIOD, DateUtilities.ONE_HOUR))
|
|
||||||
|
|
||||||
service.scheduleAlarm(task)
|
|
||||||
|
|
||||||
verify(ReminderEntry(1, now.plusMonths(12).millis, ReminderService.TYPE_SNOOZE))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun verify(vararg reminders: ReminderEntry) = assertEquals(reminders.toList(), jobs.getJobs())
|
|
||||||
|
|
||||||
internal class RandomStub : Random() {
|
|
||||||
var seed = 1.0f
|
|
||||||
|
|
||||||
override fun nextFloat() = seed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package org.tasks.makers
|
||||||
|
|
||||||
|
import com.natpryce.makeiteasy.Instantiator
|
||||||
|
import com.natpryce.makeiteasy.Property
|
||||||
|
import com.natpryce.makeiteasy.Property.newProperty
|
||||||
|
import com.natpryce.makeiteasy.PropertyLookup
|
||||||
|
import com.natpryce.makeiteasy.PropertyValue
|
||||||
|
import org.tasks.data.Alarm.Companion.TYPE_DATE_TIME
|
||||||
|
import org.tasks.date.DateTimeUtils.newDateTime
|
||||||
|
import org.tasks.jobs.AlarmEntry
|
||||||
|
import org.tasks.makers.Maker.make
|
||||||
|
import org.tasks.time.DateTime
|
||||||
|
|
||||||
|
object AlarmEntryMaker {
|
||||||
|
val ID: Property<AlarmEntry, Long> = newProperty()
|
||||||
|
val TASK: Property<AlarmEntry, Long> = newProperty()
|
||||||
|
val TIME: Property<AlarmEntry, DateTime> = newProperty()
|
||||||
|
val TYPE: Property<AlarmEntry, Int> = newProperty()
|
||||||
|
|
||||||
|
private val instantiator = Instantiator { lookup: PropertyLookup<AlarmEntry> ->
|
||||||
|
AlarmEntry(
|
||||||
|
lookup.valueOf(ID, 0L),
|
||||||
|
lookup.valueOf(TASK, 0L),
|
||||||
|
lookup.valueOf(TIME, newDateTime()).millis,
|
||||||
|
lookup.valueOf(TYPE, TYPE_DATE_TIME)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun newAlarmEntry(vararg properties: PropertyValue<in AlarmEntry?, *>): AlarmEntry {
|
||||||
|
return make(instantiator, *properties)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,91 @@
|
|||||||
|
package com.todoroo.astrid.alarms
|
||||||
|
|
||||||
|
import com.todoroo.andlib.utility.DateUtilities
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
import org.tasks.data.Alarm
|
||||||
|
import org.tasks.jobs.AlarmEntry
|
||||||
|
import org.tasks.preferences.Preferences
|
||||||
|
import org.tasks.reminders.Random
|
||||||
|
import org.tasks.time.DateTimeUtils.withMillisOfDay
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class AlarmCalculator(
|
||||||
|
private val random: Random,
|
||||||
|
private val defaultTimeProvider: () -> Int,
|
||||||
|
){
|
||||||
|
@Inject
|
||||||
|
internal constructor(
|
||||||
|
preferences: Preferences
|
||||||
|
) : this(Random(), { preferences.defaultDueTime })
|
||||||
|
|
||||||
|
fun toAlarmEntry(task: Task, alarm: Alarm): AlarmEntry? {
|
||||||
|
val trigger = when (alarm.type) {
|
||||||
|
Alarm.TYPE_SNOOZE,
|
||||||
|
Alarm.TYPE_DATE_TIME ->
|
||||||
|
alarm.time
|
||||||
|
Alarm.TYPE_REL_START ->
|
||||||
|
when {
|
||||||
|
task.hasStartTime() ->
|
||||||
|
task.hideUntil + alarm.time
|
||||||
|
task.hasStartDate() ->
|
||||||
|
task.hideUntil.withMillisOfDay(defaultTimeProvider()) + alarm.time
|
||||||
|
else ->
|
||||||
|
AlarmService.NO_ALARM
|
||||||
|
}
|
||||||
|
Alarm.TYPE_REL_END ->
|
||||||
|
when {
|
||||||
|
task.hasDueTime() ->
|
||||||
|
task.dueDate + alarm.time
|
||||||
|
task.hasDueDate() ->
|
||||||
|
task.dueDate.withMillisOfDay(defaultTimeProvider()) + alarm.time
|
||||||
|
else ->
|
||||||
|
AlarmService.NO_ALARM
|
||||||
|
}
|
||||||
|
Alarm.TYPE_RANDOM ->
|
||||||
|
calculateNextRandomReminder(random, task, alarm.time)
|
||||||
|
else ->
|
||||||
|
AlarmService.NO_ALARM
|
||||||
|
}
|
||||||
|
return when {
|
||||||
|
trigger <= AlarmService.NO_ALARM ->
|
||||||
|
null
|
||||||
|
trigger > task.reminderLast || alarm.type == Alarm.TYPE_SNOOZE ->
|
||||||
|
AlarmEntry(alarm.id, alarm.task, trigger, alarm.type)
|
||||||
|
alarm.repeat > 0 -> {
|
||||||
|
val past = (task.reminderLast - trigger) / alarm.interval
|
||||||
|
val next = trigger + (past + 1) * alarm.interval
|
||||||
|
if (past < alarm.repeat && next > task.reminderLast) {
|
||||||
|
AlarmEntry(alarm.id, alarm.task, next, alarm.type)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else ->
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Schedules alarms for a single task */
|
||||||
|
/**
|
||||||
|
* Calculate the next alarm time for random reminders.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* We take the last reminder time and add approximately the reminder period. If it's still in
|
||||||
|
* the past, we set it to some time in the near future.
|
||||||
|
*/
|
||||||
|
private fun calculateNextRandomReminder(random: Random, task: Task, reminderPeriod: Long): Long {
|
||||||
|
if (reminderPeriod > 0) {
|
||||||
|
var `when` = task.reminderLast
|
||||||
|
if (`when` == 0L) {
|
||||||
|
`when` = task.creationDate
|
||||||
|
}
|
||||||
|
`when` += (reminderPeriod * (0.85f + 0.3f * random.nextFloat())).toLong()
|
||||||
|
if (`when` < DateUtilities.now()) {
|
||||||
|
`when` =
|
||||||
|
DateUtilities.now() + ((0.5f + 6 * random.nextFloat()) * DateUtilities.ONE_HOUR).toLong()
|
||||||
|
}
|
||||||
|
return `when`
|
||||||
|
}
|
||||||
|
return AlarmService.NO_ALARM
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,114 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2012 Todoroo Inc
|
|
||||||
*
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
package com.todoroo.astrid.reminders
|
|
||||||
|
|
||||||
import com.todoroo.andlib.utility.DateUtilities
|
|
||||||
import com.todoroo.astrid.data.Task
|
|
||||||
import org.tasks.data.TaskDao
|
|
||||||
import org.tasks.jobs.NotificationQueue
|
|
||||||
import org.tasks.jobs.ReminderEntry
|
|
||||||
import org.tasks.reminders.Random
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class ReminderService internal constructor(
|
|
||||||
private val jobs: NotificationQueue,
|
|
||||||
private val random: Random,
|
|
||||||
private val taskDao: TaskDao,
|
|
||||||
) {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
internal constructor(
|
|
||||||
notificationQueue: NotificationQueue,
|
|
||||||
taskDao: TaskDao
|
|
||||||
) : this(notificationQueue, Random(), taskDao)
|
|
||||||
|
|
||||||
suspend fun scheduleAlarm(id: Long) = scheduleAllAlarms(listOf(id))
|
|
||||||
|
|
||||||
suspend fun scheduleAllAlarms(taskIds: List<Long>) = scheduleAlarms(taskDao.fetch(taskIds))
|
|
||||||
|
|
||||||
suspend fun scheduleAllAlarms() = scheduleAlarms(taskDao.getTasksWithReminders())
|
|
||||||
|
|
||||||
fun scheduleAlarm(task: Task) = scheduleAlarms(listOf(task))
|
|
||||||
|
|
||||||
private fun scheduleAlarms(tasks: List<Task>) =
|
|
||||||
tasks
|
|
||||||
.mapNotNull { getReminderEntry(it) }
|
|
||||||
.let { jobs.add(it) }
|
|
||||||
|
|
||||||
fun cancelReminder(taskId: Long) {
|
|
||||||
jobs.cancelReminder(taskId)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getReminderEntry(task: Task?): ReminderEntry? {
|
|
||||||
if (task == null || !task.isSaved) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
val taskId = task.id
|
|
||||||
|
|
||||||
// Make sure no alarms are scheduled other than the next one. When that one is shown, it
|
|
||||||
// will schedule the next one after it, and so on and so forth.
|
|
||||||
cancelReminder(taskId)
|
|
||||||
if (task.isCompleted || task.isDeleted) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// snooze reminder
|
|
||||||
val whenSnooze = calculateNextSnoozeReminder(task)
|
|
||||||
|
|
||||||
// random reminders
|
|
||||||
val whenRandom = calculateNextRandomReminder(task)
|
|
||||||
|
|
||||||
// snooze trumps all
|
|
||||||
return when {
|
|
||||||
whenSnooze != NO_ALARM -> ReminderEntry(taskId, whenSnooze, TYPE_SNOOZE)
|
|
||||||
whenRandom != NO_ALARM -> ReminderEntry(taskId, whenRandom, TYPE_RANDOM)
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun calculateNextSnoozeReminder(task: Task): Long {
|
|
||||||
return if (task.reminderSnooze > task.reminderLast) {
|
|
||||||
task.reminderSnooze
|
|
||||||
} else NO_ALARM
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the next alarm time for random reminders.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* We take the last reminder time and add approximately the reminder period. If it's still in
|
|
||||||
* the past, we set it to some time in the near future.
|
|
||||||
*/
|
|
||||||
private fun calculateNextRandomReminder(task: Task): Long {
|
|
||||||
val reminderPeriod = task.reminderPeriod
|
|
||||||
if (reminderPeriod > 0) {
|
|
||||||
var `when` = task.reminderLast
|
|
||||||
if (`when` == 0L) {
|
|
||||||
`when` = task.creationDate
|
|
||||||
}
|
|
||||||
`when` += (reminderPeriod * (0.85f + 0.3f * random.nextFloat())).toLong()
|
|
||||||
if (`when` < DateUtilities.now()) {
|
|
||||||
`when` = DateUtilities.now() + ((0.5f + 6 * random.nextFloat()) * DateUtilities.ONE_HOUR).toLong()
|
|
||||||
}
|
|
||||||
return `when`
|
|
||||||
}
|
|
||||||
return NO_ALARM
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val TYPE_DUE = 0
|
|
||||||
const val TYPE_OVERDUE = 1
|
|
||||||
const val TYPE_RANDOM = 2
|
|
||||||
const val TYPE_SNOOZE = 3
|
|
||||||
const val TYPE_ALARM = 4
|
|
||||||
const val TYPE_GEOFENCE_ENTER = 5
|
|
||||||
const val TYPE_GEOFENCE_EXIT = 6
|
|
||||||
const val TYPE_START = 7
|
|
||||||
private const val NO_ALARM = Long.MAX_VALUE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
package org.tasks.jobs;
|
|
||||||
|
|
||||||
import org.tasks.notifications.Notification;
|
|
||||||
|
|
||||||
public interface NotificationQueueEntry {
|
|
||||||
|
|
||||||
long getId();
|
|
||||||
|
|
||||||
long getTime();
|
|
||||||
|
|
||||||
Notification toNotification();
|
|
||||||
}
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
package org.tasks.jobs;
|
|
||||||
|
|
||||||
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
|
|
||||||
import static org.tasks.time.DateTimeUtils.printTimestamp;
|
|
||||||
|
|
||||||
import org.tasks.notifications.Notification;
|
|
||||||
|
|
||||||
public class ReminderEntry implements NotificationQueueEntry {
|
|
||||||
|
|
||||||
private final long taskId;
|
|
||||||
private final long time;
|
|
||||||
private final int type;
|
|
||||||
|
|
||||||
public ReminderEntry(long taskId, long time, int type) {
|
|
||||||
this.taskId = taskId;
|
|
||||||
this.time = time;
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getId() {
|
|
||||||
return taskId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getTime() {
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Notification toNotification() {
|
|
||||||
Notification notification = new Notification();
|
|
||||||
notification.setTaskId(taskId);
|
|
||||||
notification.setType(type);
|
|
||||||
notification.setTimestamp(currentTimeMillis());
|
|
||||||
return notification;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (o == null || getClass() != o.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ReminderEntry reminderEntry = (ReminderEntry) o;
|
|
||||||
|
|
||||||
if (taskId != reminderEntry.taskId) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (time != reminderEntry.time) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return type == reminderEntry.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = (int) (taskId ^ (taskId >>> 32));
|
|
||||||
result = 31 * result + (int) (time ^ (time >>> 32));
|
|
||||||
result = 31 * result + type;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "ReminderEntry{" + "taskId=" + taskId + ", time=" + printTimestamp(time) + ", type=" + type + '}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,364 @@
|
|||||||
|
package com.todoroo.astrid.alarms
|
||||||
|
|
||||||
|
import com.natpryce.makeiteasy.MakeItEasy.with
|
||||||
|
import com.todoroo.andlib.utility.DateUtilities.ONE_WEEK
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
import com.todoroo.astrid.data.Task.Companion.HIDE_UNTIL_DUE
|
||||||
|
import com.todoroo.astrid.data.Task.Companion.HIDE_UNTIL_DUE_TIME
|
||||||
|
import com.todoroo.astrid.data.Task.Companion.URGENCY_SPECIFIC_DAY_TIME
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNull
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.tasks.Freeze.Companion.freezeAt
|
||||||
|
import org.tasks.data.Alarm
|
||||||
|
import org.tasks.data.Alarm.Companion.TYPE_DATE_TIME
|
||||||
|
import org.tasks.data.Alarm.Companion.TYPE_RANDOM
|
||||||
|
import org.tasks.data.Alarm.Companion.TYPE_REL_END
|
||||||
|
import org.tasks.data.Alarm.Companion.TYPE_REL_START
|
||||||
|
import org.tasks.data.Alarm.Companion.TYPE_SNOOZE
|
||||||
|
import org.tasks.data.Alarm.Companion.whenDue
|
||||||
|
import org.tasks.data.Alarm.Companion.whenOverdue
|
||||||
|
import org.tasks.data.Alarm.Companion.whenStarted
|
||||||
|
import org.tasks.date.DateTimeUtils.newDateTime
|
||||||
|
import org.tasks.date.DateTimeUtils.toDateTime
|
||||||
|
import org.tasks.makers.AlarmEntryMaker.TIME
|
||||||
|
import org.tasks.makers.AlarmEntryMaker.TYPE
|
||||||
|
import org.tasks.makers.AlarmEntryMaker.newAlarmEntry
|
||||||
|
import org.tasks.makers.TaskMaker.CREATION_TIME
|
||||||
|
import org.tasks.makers.TaskMaker.DUE_DATE
|
||||||
|
import org.tasks.makers.TaskMaker.DUE_TIME
|
||||||
|
import org.tasks.makers.TaskMaker.HIDE_TYPE
|
||||||
|
import org.tasks.makers.TaskMaker.REMINDER_LAST
|
||||||
|
import org.tasks.makers.TaskMaker.newTask
|
||||||
|
import org.tasks.reminders.Random
|
||||||
|
import org.tasks.time.DateTime
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import java.util.concurrent.TimeUnit.DAYS
|
||||||
|
import java.util.concurrent.TimeUnit.MINUTES
|
||||||
|
|
||||||
|
class AlarmCalculatorTest {
|
||||||
|
private lateinit var random: RandomStub
|
||||||
|
private lateinit var alarmCalculator: AlarmCalculator
|
||||||
|
private val now = newDateTime()
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
random = RandomStub()
|
||||||
|
alarmCalculator = AlarmCalculator(random) {
|
||||||
|
TimeUnit.HOURS.toMillis(13).toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun ignoreOldReminder() {
|
||||||
|
assertNull(
|
||||||
|
alarmCalculator.toAlarmEntry(
|
||||||
|
newTask(with(REMINDER_LAST, now)),
|
||||||
|
Alarm(0L, now.millis, TYPE_DATE_TIME)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun dateTimeReminder() {
|
||||||
|
val alarm = alarmCalculator.toAlarmEntry(
|
||||||
|
newTask(with(REMINDER_LAST, now)),
|
||||||
|
Alarm(0L, now.millis + 1, TYPE_DATE_TIME)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(newAlarmEntry(with(TIME, now.plusMillis(1)), with(TYPE, TYPE_DATE_TIME)), alarm)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun dontIgnoreOldSnooze() {
|
||||||
|
val alarm = alarmCalculator.toAlarmEntry(
|
||||||
|
newTask(with(REMINDER_LAST, now)),
|
||||||
|
Alarm(0L, now.millis, TYPE_SNOOZE)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
newAlarmEntry(with(TIME, now), with(TYPE, TYPE_SNOOZE)),
|
||||||
|
alarm
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun scheduleReminderAtDefaultDue() {
|
||||||
|
val alarm = alarmCalculator.toAlarmEntry(newTask(with(DUE_DATE, now)), whenDue(0L))
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
newAlarmEntry(
|
||||||
|
with(TIME, now.startOfDay().withHourOfDay(13)),
|
||||||
|
with(TYPE, TYPE_REL_END)
|
||||||
|
),
|
||||||
|
alarm
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun scheduleReminderAtDefaultDueTime() = runBlocking {
|
||||||
|
val alarm = alarmCalculator.toAlarmEntry(newTask(with(DUE_TIME, now)), whenDue(0L))
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
newAlarmEntry(
|
||||||
|
with(TIME, now.startOfMinute().plusMillis(1000)), with(TYPE, TYPE_REL_END)
|
||||||
|
),
|
||||||
|
alarm
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun scheduleReminderAtDefaultStart() = runBlocking {
|
||||||
|
val alarm = alarmCalculator.toAlarmEntry(
|
||||||
|
newTask(with(DUE_DATE, now), with(HIDE_TYPE, HIDE_UNTIL_DUE)),
|
||||||
|
whenStarted(0L)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
newAlarmEntry(
|
||||||
|
with(TIME, now.startOfDay().withHourOfDay(13)),
|
||||||
|
with(TYPE, TYPE_REL_START)
|
||||||
|
),
|
||||||
|
alarm
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun scheduleReminerAtDefaultStartTime() = runBlocking {
|
||||||
|
val alarm = alarmCalculator.toAlarmEntry(
|
||||||
|
newTask(with(DUE_TIME, now), with(HIDE_TYPE, HIDE_UNTIL_DUE_TIME)),
|
||||||
|
whenStarted(0L)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
newAlarmEntry(
|
||||||
|
with(TIME, now.startOfMinute().plusMillis(1000)),
|
||||||
|
with(TYPE, TYPE_REL_START)
|
||||||
|
),
|
||||||
|
alarm
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun scheduleRelativeAfterDue() {
|
||||||
|
val alarm = alarmCalculator.toAlarmEntry(
|
||||||
|
newTask(with(DUE_DATE, now)),
|
||||||
|
Alarm(0L, DAYS.toMillis(1), TYPE_REL_END)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
newAlarmEntry(
|
||||||
|
with(TIME, now.plusDays(1).startOfDay().withHourOfDay(13)),
|
||||||
|
with(TYPE, TYPE_REL_END)
|
||||||
|
),
|
||||||
|
alarm
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun scheduleRelativeAfterDueTime() = runBlocking {
|
||||||
|
val alarm = alarmCalculator.toAlarmEntry(
|
||||||
|
newTask(with(DUE_TIME, now)),
|
||||||
|
Alarm(0, DAYS.toMillis(1), TYPE_REL_END)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
newAlarmEntry(
|
||||||
|
with(TIME, now.plusDays(1).startOfMinute().plusMillis(1000)),
|
||||||
|
with(TYPE, TYPE_REL_END)
|
||||||
|
),
|
||||||
|
alarm
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun scheduleRelativeAfterStart() = runBlocking {
|
||||||
|
val alarm = alarmCalculator.toAlarmEntry(
|
||||||
|
newTask(with(DUE_DATE, now), with(HIDE_TYPE, HIDE_UNTIL_DUE)),
|
||||||
|
Alarm(0, DAYS.toMillis(1), TYPE_REL_START)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
newAlarmEntry(
|
||||||
|
with(TIME, now.plusDays(1).startOfDay().withHourOfDay(13)),
|
||||||
|
with(TYPE, TYPE_REL_START)
|
||||||
|
),
|
||||||
|
alarm
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun scheduleRelativeAfterStartTime() = runBlocking {
|
||||||
|
val alarm = alarmCalculator.toAlarmEntry(
|
||||||
|
newTask(with(DUE_TIME, now), with(HIDE_TYPE, HIDE_UNTIL_DUE_TIME)),
|
||||||
|
Alarm(0, DAYS.toMillis(1), TYPE_REL_START)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
newAlarmEntry(
|
||||||
|
with(TIME, now.plusDays(1).startOfMinute().plusMillis(1000)),
|
||||||
|
with(TYPE, TYPE_REL_START)
|
||||||
|
),
|
||||||
|
alarm
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun scheduleFirstRepeatReminder() = runBlocking {
|
||||||
|
val alarm = alarmCalculator.toAlarmEntry(
|
||||||
|
newTask(with(DUE_TIME, now), with(REMINDER_LAST, now.plusMinutes(4))),
|
||||||
|
Alarm(0, 0, TYPE_REL_END, 1, MINUTES.toMillis(5))
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
newAlarmEntry(
|
||||||
|
with(TIME, now.plusMinutes(5).startOfMinute().plusMillis(1000)),
|
||||||
|
with(TYPE, TYPE_REL_END)
|
||||||
|
),
|
||||||
|
alarm
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun scheduleSecondRepeatReminder() = runBlocking {
|
||||||
|
val alarm = alarmCalculator.toAlarmEntry(
|
||||||
|
newTask(with(DUE_TIME, now), with(REMINDER_LAST, now.plusMinutes(6))),
|
||||||
|
Alarm(0, 0, TYPE_REL_END, 2, MINUTES.toMillis(5))
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
newAlarmEntry(
|
||||||
|
with(TIME, now.plusMinutes(10).startOfMinute().plusMillis(1000)),
|
||||||
|
with(TYPE, TYPE_REL_END)
|
||||||
|
),
|
||||||
|
alarm
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun terminateRepeatReminder() = runBlocking {
|
||||||
|
val alarm = alarmCalculator.toAlarmEntry(
|
||||||
|
newTask(with(DUE_TIME, now), with(REMINDER_LAST, now.plusMinutes(10))),
|
||||||
|
Alarm(0L, 0, TYPE_REL_END, 2, MINUTES.toMillis(5))
|
||||||
|
)
|
||||||
|
|
||||||
|
assertNull(alarm)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun dontScheduleRelativeEndWithNoEnd() = runBlocking {
|
||||||
|
assertNull(alarmCalculator.toAlarmEntry(newTask(), whenDue(0L)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun dontScheduleRelativeStartWithNoStart() = runBlocking {
|
||||||
|
assertNull(
|
||||||
|
alarmCalculator.toAlarmEntry(
|
||||||
|
newTask(with(DUE_DATE, newDateTime())),
|
||||||
|
whenStarted(0L)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun reminderOverdueEveryDay() = runBlocking {
|
||||||
|
val dueDate =
|
||||||
|
Task.createDueDate(URGENCY_SPECIFIC_DAY_TIME, DateTime(2022, 1, 30, 13, 30).millis)
|
||||||
|
.toDateTime()
|
||||||
|
val alarm = alarmCalculator.toAlarmEntry(
|
||||||
|
newTask(with(DUE_TIME, dueDate), with(REMINDER_LAST, dueDate.plusDays(6))),
|
||||||
|
whenOverdue(0L)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
newAlarmEntry(with(TIME, dueDate.plusDays(7)), with(TYPE, TYPE_REL_END)),
|
||||||
|
alarm
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun endDailyOverdueReminder() = runBlocking {
|
||||||
|
val dueDate =
|
||||||
|
Task.createDueDate(URGENCY_SPECIFIC_DAY_TIME, DateTime(2022, 1, 30, 13, 30).millis)
|
||||||
|
.toDateTime()
|
||||||
|
val alarm = alarmCalculator.toAlarmEntry(
|
||||||
|
newTask(with(DUE_TIME, dueDate), with(REMINDER_LAST, dueDate.plusDays(7))),
|
||||||
|
whenOverdue(0L)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertNull(alarm)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun scheduleOverdueRandomReminder() {
|
||||||
|
random.seed = 0.3865f
|
||||||
|
freezeAt(now) {
|
||||||
|
val alarm = alarmCalculator.toAlarmEntry(
|
||||||
|
newTask(
|
||||||
|
with(REMINDER_LAST, now.minusDays(14)),
|
||||||
|
with(CREATION_TIME, now.minusDays(30)),
|
||||||
|
),
|
||||||
|
Alarm(0L, ONE_WEEK, TYPE_RANDOM)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
newAlarmEntry(with(TIME, now.plusMillis(10148400)), with(TYPE, TYPE_RANDOM)),
|
||||||
|
alarm
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun scheduleInitialRandomReminder() {
|
||||||
|
random.seed = 0.3865f
|
||||||
|
|
||||||
|
freezeAt(now) {
|
||||||
|
val alarm = alarmCalculator.toAlarmEntry(
|
||||||
|
newTask(
|
||||||
|
with(REMINDER_LAST, null as DateTime?),
|
||||||
|
with(CREATION_TIME, now.minusDays(1)),
|
||||||
|
),
|
||||||
|
Alarm(0L, ONE_WEEK, TYPE_RANDOM)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
newAlarmEntry(
|
||||||
|
with(TIME, now.minusDays(1).plusMillis(584206592)),
|
||||||
|
with(TYPE, TYPE_RANDOM)
|
||||||
|
),
|
||||||
|
alarm
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun scheduleNextRandomReminder() {
|
||||||
|
random.seed = 0.3865f
|
||||||
|
|
||||||
|
freezeAt(now) {
|
||||||
|
val alarm = alarmCalculator.toAlarmEntry(
|
||||||
|
newTask(
|
||||||
|
with(REMINDER_LAST, now.minusDays(1)),
|
||||||
|
with(CREATION_TIME, now.minusDays(30)),
|
||||||
|
),
|
||||||
|
Alarm(0L, ONE_WEEK, TYPE_RANDOM)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
newAlarmEntry(
|
||||||
|
with(TIME, now.minusDays(1).plusMillis(584206592)),
|
||||||
|
with(TYPE, TYPE_RANDOM)
|
||||||
|
),
|
||||||
|
alarm
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class RandomStub : Random() {
|
||||||
|
var seed = 1.0f
|
||||||
|
|
||||||
|
override fun nextFloat() = seed
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue