diff --git a/src/androidTest/java/com/todoroo/astrid/reminders/ReminderServiceTest.java b/src/androidTest/java/com/todoroo/astrid/reminders/ReminderServiceTest.java index d6abdd085..dc1887410 100644 --- a/src/androidTest/java/com/todoroo/astrid/reminders/ReminderServiceTest.java +++ b/src/androidTest/java/com/todoroo/astrid/reminders/ReminderServiceTest.java @@ -6,9 +6,9 @@ import com.todoroo.astrid.data.Task; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.tasks.Snippet; import org.tasks.injection.InjectingTestCase; @@ -16,29 +16,30 @@ import org.tasks.injection.TestComponent; import org.tasks.jobs.JobQueue; import org.tasks.jobs.Reminder; import org.tasks.preferences.Preferences; +import org.tasks.reminders.Random; import org.tasks.time.DateTime; -import java.util.concurrent.TimeUnit; - import javax.inject.Inject; import static com.natpryce.makeiteasy.MakeItEasy.with; +import static com.todoroo.andlib.utility.DateUtilities.ONE_HOUR; +import static com.todoroo.andlib.utility.DateUtilities.ONE_WEEK; import static com.todoroo.astrid.data.Task.NOTIFY_AFTER_DEADLINE; import static com.todoroo.astrid.data.Task.NOTIFY_AT_DEADLINE; -import static com.todoroo.astrid.reminders.ReminderService.TYPE_RANDOM; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertTrue; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; import static org.tasks.Freeze.freezeClock; import static org.tasks.date.DateTimeUtils.newDateTime; import static org.tasks.makers.TaskMaker.COMPLETION_TIME; +import static org.tasks.makers.TaskMaker.CREATION_TIME; import static org.tasks.makers.TaskMaker.DELETION_TIME; import static org.tasks.makers.TaskMaker.DUE_DATE; import static org.tasks.makers.TaskMaker.DUE_TIME; import static org.tasks.makers.TaskMaker.ID; +import static org.tasks.makers.TaskMaker.PRIORITY; import static org.tasks.makers.TaskMaker.RANDOM_REMINDER_PERIOD; import static org.tasks.makers.TaskMaker.REMINDERS; import static org.tasks.makers.TaskMaker.REMINDER_LAST; @@ -52,12 +53,15 @@ public class ReminderServiceTest extends InjectingTestCase { @Inject Preferences preferences; private ReminderService service; + private Random random; private JobQueue jobs; @Before public void before() { jobs = mock(JobQueue.class); - service = new ReminderService(preferences, jobs); + random = mock(Random.class); + when(random.nextFloat()).thenReturn(1.0f); + service = new ReminderService(preferences, jobs, random); } @After @@ -164,23 +168,6 @@ public class ReminderServiceTest extends InjectingTestCase { verify(jobs).cancel(1); } - @Test - public void snoozeOverridesAll() { - DateTime now = newDateTime(); - Task task = newTask( - with(ID, 1L), - with(DUE_TIME, now), - with(SNOOZE_TIME, now.plusMonths(12)), - with(REMINDERS, NOTIFY_AT_DEADLINE | NOTIFY_AFTER_DEADLINE), - with(RANDOM_REMINDER_PERIOD, TimeUnit.HOURS.toMillis(1))); - - service.scheduleAlarm(null, task); - - InOrder order = inOrder(jobs); - order.verify(jobs).cancel(1); - order.verify(jobs).add(new Reminder(1, now.plusMonths(12).getMillis(), ReminderService.TYPE_SNOOZE)); - } - @Test public void ignoreLapsedSnoozeTime() { Task task = newTask( @@ -197,119 +184,170 @@ public class ReminderServiceTest extends InjectingTestCase { } @Test - public void scheduleRandomReminder() { + public void scheduleInitialRandomReminder() { + freezeClock().thawAfter(new Snippet() {{ + DateTime now = newDateTime(); + when(random.nextFloat()).thenReturn(0.3865f); + Task task = newTask( + with(ID, 1L), + with(REMINDER_LAST, (DateTime) null), + with(CREATION_TIME, now.minusDays(1)), + with(RANDOM_REMINDER_PERIOD, ONE_WEEK)); + + service.scheduleAlarm(null, task); + + InOrder order = inOrder(jobs); + order.verify(jobs).cancel(1); + order.verify(jobs).add(new Reminder(1L, now.minusDays(1).getMillis() + 584206592, ReminderService.TYPE_RANDOM)); + }}); + } + + @Test + public void scheduleNextRandomReminder() { + freezeClock().thawAfter(new Snippet() {{ + DateTime now = newDateTime(); + when(random.nextFloat()).thenReturn(0.3865f); + Task task = newTask( + with(ID, 1L), + with(REMINDER_LAST, now.minusDays(1)), + with(CREATION_TIME, now.minusDays(30)), + with(RANDOM_REMINDER_PERIOD, ONE_WEEK)); + + service.scheduleAlarm(null, task); + + InOrder order = inOrder(jobs); + order.verify(jobs).cancel(1); + order.verify(jobs).add(new Reminder(1L, now.minusDays(1).getMillis() + 584206592, ReminderService.TYPE_RANDOM)); + }}); + } + + @Test + public void scheduleOverdueRandomReminder() { + freezeClock().thawAfter(new Snippet() {{ + DateTime now = newDateTime(); + when(random.nextFloat()).thenReturn(0.3865f); + Task task = newTask( + with(ID, 1L), + with(REMINDER_LAST, now.minusDays(14)), + with(CREATION_TIME, now.minusDays(30)), + with(RANDOM_REMINDER_PERIOD, ONE_WEEK)); + + service.scheduleAlarm(null, task); + + InOrder order = inOrder(jobs); + order.verify(jobs).cancel(1); + order.verify(jobs).add(new Reminder(1L, now.getMillis() + 10148400, ReminderService.TYPE_RANDOM)); + }}); + } + + @Test + public void scheduleOverdueForFutureDueDate() { + freezeClock().thawAfter(new Snippet() {{ + when(random.nextFloat()).thenReturn(0.3865f); + Task task = newTask( + with(ID, 1L), + with(DUE_TIME, newDateTime().plusMinutes(5)), + with(REMINDERS, NOTIFY_AFTER_DEADLINE)); + + service.scheduleAlarm(null, task); + + InOrder order = inOrder(jobs); + order.verify(jobs).cancel(1); + order.verify(jobs).add(new Reminder(1L, task.getDueDate() + 4582800, ReminderService.TYPE_OVERDUE)); + }}); + } + + @Test + public void scheduleOverdueForPastDueDateWithNoReminderPastDueDate() { + freezeClock().thawAfter(new Snippet() {{ + DateTime now = newDateTime(); + Task task = newTask( + with(ID, 1L), + with(DUE_TIME, now.minusMinutes(5)), + with(REMINDERS, NOTIFY_AFTER_DEADLINE)); + + service.scheduleAlarm(null, task); + + InOrder order = inOrder(jobs); + order.verify(jobs).cancel(1); + order.verify(jobs).add(new Reminder(1L, currentTimeMillis(), ReminderService.TYPE_OVERDUE)); + }}); + } + + @Test + public void scheduleOverdueForPastDueDateLastReminderSixHoursAgo() { freezeClock().thawAfter(new Snippet() {{ Task task = newTask( with(ID, 1L), - with(RANDOM_REMINDER_PERIOD, TimeUnit.DAYS.toMillis(7))); + with(DUE_TIME, newDateTime().minusHours(12)), + with(REMINDER_LAST, newDateTime().minusHours(6)), + with(REMINDERS, NOTIFY_AFTER_DEADLINE)); + service.scheduleAlarm(null, task); - ArgumentCaptor captor = ArgumentCaptor.forClass(Reminder.class); InOrder order = inOrder(jobs); order.verify(jobs).cancel(1); - order.verify(jobs).add(captor.capture()); + order.verify(jobs).add(new Reminder(1L, currentTimeMillis(), ReminderService.TYPE_OVERDUE)); + }}); + } - Reminder reminder = captor.getValue(); - assertTrue(reminder.getTime() > currentTimeMillis()); - assertTrue(reminder.getTime() < currentTimeMillis() + 1.2 * TimeUnit.DAYS.toMillis(7)); - assertEquals(TYPE_RANDOM, reminder.getType()); + @Test + public void scheduleOverdueForPastDueDateLastReminderWithinSixHours() { + freezeClock().thawAfter(new Snippet() {{ + when(random.nextFloat()).thenReturn(0.3865f); + Task task = newTask( + with(ID, 1L), + with(DUE_TIME, newDateTime().minusHours(12)), + with(PRIORITY, 2), + with(REMINDER_LAST, newDateTime().minusHours(3)), + with(REMINDERS, NOTIFY_AFTER_DEADLINE)); + + service.scheduleAlarm(null, task); + + InOrder order = inOrder(jobs); + order.verify(jobs).cancel(1); + order.verify(jobs).add(new Reminder(1L, currentTimeMillis() + 22748400, ReminderService.TYPE_OVERDUE)); }}); } -// @Test -// public void testOverdue() { -// // test due date in the future -// reminderService.setScheduler(new AlarmExpected() { -// @Override -// public void createAlarm(Task task, long time, int type) { -// if (time == ReminderService.NO_ALARM) -// return; -// super.createAlarm(task, time, type); -// assertTrue(time > task.getDueDate()); -// assertTrue(time < task.getDueDate() + DateUtilities.ONE_DAY); -// assertEquals(type, ReminderService.TYPE_OVERDUE); -// } -// }); -// final Task task = new Task(); -// task.setTitle("water"); -// task.setDueDate(DateUtilities.now() + DateUtilities.ONE_DAY); -// task.setReminderFlags(Task.NOTIFY_AFTER_DEADLINE); -// taskDao.save(task); -// -// // test due date in the past -// task.setDueDate(DateUtilities.now() - DateUtilities.ONE_DAY); -// reminderService.setScheduler(new AlarmExpected() { -// @Override -// public void createAlarm(Task task, long time, int type) { -// if (time == ReminderService.NO_ALARM) -// return; -// super.createAlarm(task, time, type); -// assertTrue(time > DateUtilities.now() - 1000L); -// assertTrue(time < DateUtilities.now() + 2 * DateUtilities.ONE_DAY); -// assertEquals(type, ReminderService.TYPE_OVERDUE); -// } -// }); -// taskDao.save(task); -// assertTrue(((AlarmExpected) reminderService.getScheduler()).alarmCreated); -// -// // test due date in the past, but recently notified -// task.setReminderLast(DateUtilities.now()); -// reminderService.setScheduler(new AlarmExpected() { -// @Override -// public void createAlarm(Task task, long time, int type) { -// if (time == ReminderService.NO_ALARM) -// return; -// super.createAlarm(task, time, type); -// assertTrue(time > DateUtilities.now() + DateUtilities.ONE_HOUR); -// assertTrue(time < DateUtilities.now() + DateUtilities.ONE_DAY); -// assertEquals(type, ReminderService.TYPE_OVERDUE); -// } -// }); -// taskDao.save(task); -// assertTrue(((AlarmExpected) reminderService.getScheduler()).alarmCreated); -// } -// -// @Test -// public void testMultipleReminders() { -// // test due date in the future, enable random -// final Task task = new Task(); -// task.setTitle("water"); -// task.setDueDate(DateUtilities.now() + DateUtilities.ONE_WEEK); -// task.setReminderFlags(Task.NOTIFY_AT_DEADLINE); -// task.setReminderPeriod(DateUtilities.ONE_HOUR); -// reminderService.setScheduler(new AlarmExpected() { -// @Override -// public void createAlarm(Task task, long time, int type) { -// if (time == ReminderService.NO_ALARM) -// return; -// super.createAlarm(task, time, type); -// assertTrue(time > DateUtilities.now()); -// assertTrue(time < DateUtilities.now() + DateUtilities.ONE_DAY); -// assertEquals(type, ReminderService.TYPE_RANDOM); -// } -// }); -// taskDao.save(task); -// assertTrue(((AlarmExpected) reminderService.getScheduler()).alarmCreated); -// -// // now set the due date in the past -// task.setDueDate(DateUtilities.now() - DateUtilities.ONE_WEEK); -// ((AlarmExpected) reminderService.getScheduler()).alarmCreated = false; -// reminderService.scheduleAlarm(taskDao, task); -// assertTrue(((AlarmExpected) reminderService.getScheduler()).alarmCreated); -// -// // now set the due date before the random -// task.setDueDate(DateUtilities.now() + DateUtilities.ONE_HOUR); -// reminderService.setScheduler(new AlarmExpected() { -// @Override -// public void createAlarm(Task task, long time, int type) { -// if (time == ReminderService.NO_ALARM) -// return; -// super.createAlarm(task, time, type); -// assertEquals((long) task.getDueDate(), time); -// assertEquals(type, ReminderService.TYPE_DUE); -// } -// }); -// taskDao.save(task); -// assertTrue(((AlarmExpected) reminderService.getScheduler()).alarmCreated); -// } + @Test + public void snoozeOverridesAll() { + DateTime now = newDateTime(); + Task task = newTask( + with(ID, 1L), + with(DUE_TIME, now), + with(SNOOZE_TIME, now.plusMonths(12)), + with(REMINDERS, NOTIFY_AT_DEADLINE | NOTIFY_AFTER_DEADLINE), + with(RANDOM_REMINDER_PERIOD, ONE_HOUR)); + + service.scheduleAlarm(null, task); + + InOrder order = inOrder(jobs); + order.verify(jobs).cancel(1); + order.verify(jobs).add(new Reminder(1, now.plusMonths(12).getMillis(), ReminderService.TYPE_SNOOZE)); + } + + @Test + @Ignore + public void randomReminderBeforeDueAndOverdue() { + + } + + @Test + @Ignore + public void randomReminderAfterDue() { + + } + + @Test + @Ignore + public void randomReminderAfterOverdue() { + + } + + @Test + @Ignore + public void dueDateBeforeOverdue() { + + } } diff --git a/src/androidTest/java/org/tasks/makers/TaskMaker.java b/src/androidTest/java/org/tasks/makers/TaskMaker.java index dd9a3e942..97bab5d50 100644 --- a/src/androidTest/java/org/tasks/makers/TaskMaker.java +++ b/src/androidTest/java/org/tasks/makers/TaskMaker.java @@ -9,8 +9,8 @@ import com.todoroo.astrid.data.Task; import org.tasks.time.DateTime; import static com.natpryce.makeiteasy.Property.newProperty; +import static org.tasks.date.DateTimeUtils.newDateTime; import static org.tasks.makers.Maker.make; -import static org.tasks.time.DateTimeUtils.currentTimeMillis; public class TaskMaker { @@ -18,10 +18,12 @@ public class TaskMaker { public static Property TITLE = newProperty(); public static Property DUE_DATE = newProperty(); public static Property DUE_TIME = newProperty(); + public static Property PRIORITY = newProperty(); public static Property REMINDER_LAST = newProperty(); public static Property RANDOM_REMINDER_PERIOD = newProperty(); public static Property HIDE_TYPE = newProperty(); public static Property REMINDERS = newProperty(); + public static Property CREATION_TIME = newProperty(); public static Property COMPLETION_TIME = newProperty(); public static Property DELETION_TIME = newProperty(); public static Property SNOOZE_TIME = newProperty(); @@ -44,6 +46,11 @@ public class TaskMaker { task.setId(id); } + int priority = lookup.valueOf(PRIORITY, -1); + if (priority >= 0) { + task.setImportance(priority); + } + DateTime dueDate = lookup.valueOf(DUE_DATE, (DateTime) null); if (dueDate != null) { task.setDueDate(Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, dueDate.getMillis())); @@ -89,7 +96,8 @@ public class TaskMaker { task.setReminderPeriod(randomReminderPeriod); } - task.setCreationDate(currentTimeMillis()); + DateTime creationTime = lookup.valueOf(CREATION_TIME, newDateTime()); + task.setCreationDate(creationTime.getMillis()); return task; }; diff --git a/src/main/java/com/todoroo/astrid/reminders/ReminderService.java b/src/main/java/com/todoroo/astrid/reminders/ReminderService.java index 325bce4f1..a1f522a05 100644 --- a/src/main/java/com/todoroo/astrid/reminders/ReminderService.java +++ b/src/main/java/com/todoroo/astrid/reminders/ReminderService.java @@ -19,10 +19,10 @@ import org.tasks.jobs.JobManager; import org.tasks.jobs.JobQueue; import org.tasks.jobs.Reminder; import org.tasks.preferences.Preferences; +import org.tasks.reminders.Random; import org.tasks.time.DateTime; import java.util.List; -import java.util.Random; import javax.inject.Inject; @@ -57,22 +57,23 @@ public final class ReminderService { /** flag for an alarm reminder */ public static final int TYPE_ALARM = 4; - private static final Random random = new Random(); private static final long NO_ALARM = Long.MAX_VALUE; private final JobQueue jobs; + private final Random random; private final Preferences preferences; private long now = -1; // For tracking when reminders might be scheduled all at once @Inject ReminderService(Preferences preferences, JobManager jobManager) { - this(preferences, JobQueue.newReminderQueue(preferences, jobManager)); + this(preferences, JobQueue.newReminderQueue(preferences, jobManager), new Random()); } - ReminderService(Preferences preferences, JobQueue jobs) { + ReminderService(Preferences preferences, JobQueue jobs, Random random) { this.preferences = preferences; this.jobs = jobs; + this.random = random; } private static final int MILLIS_PER_HOUR = 60 * 60 * 1000; @@ -139,7 +140,6 @@ public final class ReminderService { // notifications at due date long whenDueDate = calculateNextDueDateReminder(task); - // notifications after due date long whenOverdue = calculateNextOverdueReminder(task); diff --git a/src/main/java/org/tasks/reminders/Random.java b/src/main/java/org/tasks/reminders/Random.java new file mode 100644 index 000000000..bb63a4c09 --- /dev/null +++ b/src/main/java/org/tasks/reminders/Random.java @@ -0,0 +1,10 @@ +package org.tasks.reminders; + +public class Random { + + private static final java.util.Random random = new java.util.Random(); + + public float nextFloat() { + return random.nextFloat(); + } +} diff --git a/src/main/java/org/tasks/time/DateTime.java b/src/main/java/org/tasks/time/DateTime.java index 6d62b4ebe..74e5c0829 100644 --- a/src/main/java/org/tasks/time/DateTime.java +++ b/src/main/java/org/tasks/time/DateTime.java @@ -189,6 +189,10 @@ public class DateTime { return subtract(Calendar.DATE, days); } + public DateTime minusHours(int hours) { + return subtract(Calendar.HOUR, hours); + } + public DateTime minusMinutes(int minutes) { return subtract(Calendar.MINUTE, minutes); }