Reminder test improvements

pull/127/merge
Alex Baker 7 years ago
parent c02164ad7a
commit f46aebb784

@ -1,71 +0,0 @@
package com.todoroo.astrid.reminders;
import android.support.test.runner.AndroidJUnit4;
import com.todoroo.astrid.data.Task;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.tasks.preferences.Preferences;
import org.tasks.time.DateTime;
import static android.support.test.InstrumentationRegistry.getTargetContext;
import static com.natpryce.makeiteasy.MakeItEasy.with;
import static com.todoroo.astrid.data.Task.NOTIFY_AT_DEADLINE;
import static com.todoroo.astrid.reminders.ReminderService.NO_ALARM;
import static junit.framework.Assert.assertEquals;
import static org.tasks.makers.TaskMaker.DUE_DATE;
import static org.tasks.makers.TaskMaker.DUE_TIME;
import static org.tasks.makers.TaskMaker.REMINDERS;
import static org.tasks.makers.TaskMaker.REMINDER_LAST;
import static org.tasks.makers.TaskMaker.newTask;
@RunWith(AndroidJUnit4.class)
public class NotifyAtDeadlineTest {
private ReminderService reminderService;
@Before
public void setUp() {
Preferences preferences = new Preferences(getTargetContext(), null);
reminderService = new ReminderService(preferences, null);
}
@Test
public void testNoReminderWhenNoDueDate() {
Task task = newTask(with(REMINDERS, NOTIFY_AT_DEADLINE));
assertEquals(NO_ALARM, reminderService.calculateNextDueDateReminder(task));
}
@Test
public void testNoReminderWhenNotifyAtDeadlineFlagNotSet() {
Task task = newTask(with(DUE_TIME, new DateTime(2014, 1, 24, 19, 23)));
assertEquals(NO_ALARM, reminderService.calculateNextDueDateReminder(task));
}
@Test
public void testScheduleReminderAtDueTime() {
final DateTime dueDate = new DateTime(2014, 1, 24, 19, 23);
Task task = newTask(with(DUE_TIME, dueDate), with(REMINDERS, NOTIFY_AT_DEADLINE));
assertEquals(dueDate.plusSeconds(1).getMillis(), reminderService.calculateNextDueDateReminder(task));
}
@Test
public void testScheduleReminderAtDefaultDueTime() {
final DateTime dueDate = new DateTime(2015, 12, 29, 12, 0);
Task task = newTask(with(DUE_DATE, dueDate), with(REMINDERS, NOTIFY_AT_DEADLINE));
assertEquals(dueDate.withHourOfDay(18).getMillis(),
reminderService.calculateNextDueDateReminder(task));
}
@Test
public void testNoReminderIfAlreadyRemindedPastDueDate() {
final DateTime dueDate = new DateTime(2015, 12, 29, 19, 23);
Task task = newTask(
with(DUE_TIME, dueDate),
with(REMINDER_LAST, dueDate.plusSeconds(1)),
with(REMINDERS, NOTIFY_AT_DEADLINE));
assertEquals(NO_ALARM, reminderService.calculateNextDueDateReminder(task));
}
}

@ -1,139 +0,0 @@
package com.todoroo.astrid.reminders;
import android.support.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.tasks.Freeze;
import org.tasks.Snippet;
import org.tasks.jobs.JobManager;
import org.tasks.jobs.Reminder;
import org.tasks.makers.TaskMaker;
import org.tasks.preferences.Preferences;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static com.natpryce.makeiteasy.MakeItEasy.with;
import static com.todoroo.astrid.reminders.ReminderService.TYPE_DUE;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static junit.framework.Assert.assertEquals;
import static org.mockito.AdditionalAnswers.returnsFirstArg;
import static org.mockito.Matchers.anyLong;
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.makers.TaskMaker.newTask;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
@RunWith(AndroidJUnit4.class)
public class ReminderAlarmSchedulerTest {
private static final long ONE_MINUTE = TimeUnit.MINUTES.toMillis(1);
private JobManager jobManager;
private ReminderAlarmScheduler scheduler;
@Before
public void before() {
jobManager = mock(JobManager.class);
Preferences preferences = mock(Preferences.class);
when(preferences.adjustForQuietHours(anyLong())).then(returnsFirstArg());
scheduler = new ReminderAlarmScheduler(jobManager, preferences);
}
@After
public void after() {
verifyNoMoreInteractions(jobManager);
}
@Test
public void scheduleFirstReminder() {
long now = currentTimeMillis();
scheduler.createAlarm(newTask(with(TaskMaker.ID, 1L)), now, 0);
verify(jobManager).scheduleReminder(now, true);
}
@Test
public void dontScheduleLaterReminder() {
long now = currentTimeMillis();
scheduler.createAlarm(newTask(with(TaskMaker.ID, 1L)), now, 0);
scheduler.createAlarm(newTask(with(TaskMaker.ID, 1L)), now + ONE_MINUTE, 0);
verify(jobManager).scheduleReminder(now, true);
}
@Test
public void rescheduleNewerReminder() {
long now = currentTimeMillis();
scheduler.createAlarm(newTask(with(TaskMaker.ID, 1L)), now, 0);
scheduler.createAlarm(newTask(with(TaskMaker.ID, 2L)), now - ONE_MINUTE, 0);
InOrder order = inOrder(jobManager);
order.verify(jobManager).scheduleReminder(now, true);
order.verify(jobManager).scheduleReminder(now - ONE_MINUTE, true);
}
@Test
public void removeLastReminderCancelsJob() {
long now = currentTimeMillis();
scheduler.createAlarm(newTask(with(TaskMaker.ID, 1L)), now, 0);
scheduler.createAlarm(newTask(with(TaskMaker.ID, 1L)), 0, 0);
InOrder order = inOrder(jobManager);
order.verify(jobManager).scheduleReminder(now, true);
order.verify(jobManager).cancelReminders();
}
@Test
public void removePastRemindersReturnsPastReminder() {
long now = currentTimeMillis();
Freeze.freezeAt(now).thawAfter(new Snippet() {{
scheduler.createAlarm(newTask(with(TaskMaker.ID, 1L)), now, TYPE_DUE);
List<Reminder> reminders = scheduler.removePastReminders();
verify(jobManager).scheduleReminder(now, true);
assertEquals(singletonList(new Reminder(1, now, TYPE_DUE)), reminders);
}});
}
@Test
public void dontRescheduleForSecondJobAtSameTime() {
long now = currentTimeMillis();
scheduler.createAlarm(newTask(with(TaskMaker.ID, 1L)), now, TYPE_DUE);
scheduler.createAlarm(newTask(with(TaskMaker.ID, 2L)), now, TYPE_DUE);
verify(jobManager).scheduleReminder(now, true);
}
@Test
public void removePastRemindersReturnsPastRemindersAtSameTime() {
long now = currentTimeMillis();
Freeze.freezeAt(now).thawAfter(new Snippet() {{
scheduler.createAlarm(newTask(with(TaskMaker.ID, 1L)), now, TYPE_DUE);
scheduler.createAlarm(newTask(with(TaskMaker.ID, 2L)), now, TYPE_DUE);
List<Reminder> reminders = scheduler.removePastReminders();
verify(jobManager).scheduleReminder(now, true);
assertEquals(asList(new Reminder(1, now, TYPE_DUE), new Reminder(2, now, TYPE_DUE)), reminders);
}});
}
}

@ -1,41 +1,68 @@
/**
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.reminders;
import android.support.test.runner.AndroidJUnit4;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import org.junit.After;
import org.junit.Before;
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;
import org.tasks.injection.TestComponent;
import org.tasks.jobs.JobQueue;
import org.tasks.jobs.Reminder;
import org.tasks.preferences.Preferences;
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.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 junit.framework.Assert.fail;
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.tasks.Freeze.freezeClock;
import static org.tasks.Freeze.thaw;
import static org.tasks.date.DateTimeUtils.newDateTime;
import static org.tasks.makers.TaskMaker.COMPLETION_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.RANDOM_REMINDER_PERIOD;
import static org.tasks.makers.TaskMaker.REMINDERS;
import static org.tasks.makers.TaskMaker.REMINDER_LAST;
import static org.tasks.makers.TaskMaker.SNOOZE_TIME;
import static org.tasks.makers.TaskMaker.newTask;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
@RunWith(AndroidJUnit4.class)
public class ReminderServiceTest extends InjectingTestCase {
@Inject TaskDao taskDao;
@Inject ReminderService reminderService;
@Inject Preferences preferences;
@Override
public void setUp() {
super.setUp();
freezeClock();
private ReminderService service;
private JobQueue<Reminder> jobs;
@Before
public void before() {
jobs = mock(JobQueue.class);
service = new ReminderService(preferences, jobs);
}
@After
public void after() {
verifyNoMoreInteractions(jobs);
}
@Override
@ -43,235 +70,246 @@ public class ReminderServiceTest extends InjectingTestCase {
component.inject(this);
}
@After
public void tearDown() {
thaw();
@Test
public void dontScheduleDueDateReminderWhenFlagNotSet() {
service.scheduleAlarm(null, newTask(with(ID, 1L), with(DUE_TIME, newDateTime())));
verify(jobs).cancel(1);
}
@Test
public void testNoReminders() {
reminderService.setScheduler(new NoAlarmExpected());
Task task = new Task();
task.setTitle("water");
task.setReminderFlags(0);
task.setReminderPeriod(0L);
taskDao.save(task);
reminderService.scheduleAlarm(taskDao, task);
public void dontScheduleDueDateReminderWhenTimeNotSet() {
service.scheduleAlarm(null, newTask(with(ID, 1L), with(REMINDERS, NOTIFY_AT_DEADLINE)));
verify(jobs).cancel(1);
}
@Test
public void testDueDates() {
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);
}
});
// test due date in the past
final Task task = new Task();
task.setTitle("water");
task.setDueDate(DateUtilities.now() - DateUtilities.ONE_DAY);
task.setReminderFlags(Task.NOTIFY_AT_DEADLINE);
taskDao.save(task);
// test due date in the future
task.setDueDate(DateUtilities.now() + DateUtilities.ONE_DAY);
taskDao.save(task);
assertTrue(((AlarmExpected) reminderService.getScheduler()).alarmCreated);
public void schedulePastDueDate() {
Task task = newTask(
with(ID, 1L),
with(DUE_TIME, newDateTime().minusDays(1)),
with(REMINDERS, NOTIFY_AT_DEADLINE));
service.scheduleAlarm(null, task);
InOrder order = inOrder(jobs);
order.verify(jobs).cancel(1);
order.verify(jobs).add(new Reminder(1, task.getDueDate(), ReminderService.TYPE_DUE));
}
@Test
public void testRandom() {
// test random
final Task task = new Task();
task.setTitle("water");
task.setReminderPeriod(DateUtilities.ONE_WEEK);
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() + 1.2 * DateUtilities.ONE_WEEK);
assertEquals(type, ReminderService.TYPE_RANDOM);
}
});
taskDao.save(task);
assertTrue(((AlarmExpected) reminderService.getScheduler()).alarmCreated);
public void scheduleFutureDueDate() {
Task task = newTask(
with(ID, 1L),
with(DUE_TIME, newDateTime().plusDays(1)),
with(REMINDERS, NOTIFY_AT_DEADLINE));
service.scheduleAlarm(null, task);
InOrder order = inOrder(jobs);
order.verify(jobs).cancel(1);
order.verify(jobs).add(new Reminder(1, task.getDueDate(), ReminderService.TYPE_DUE));
}
@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);
public void scheduleReminderAtDefaultDueTime() {
DateTime now = newDateTime();
Task task = newTask(
with(ID, 1L),
with(DUE_DATE, now),
with(REMINDERS, NOTIFY_AT_DEADLINE));
service.scheduleAlarm(null, task);
InOrder order = inOrder(jobs);
order.verify(jobs).cancel(1);
order.verify(jobs).add(new Reminder(1, now.startOfDay().withHourOfDay(18).getMillis(), ReminderService.TYPE_DUE));
}
@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);
public void dontScheduleReminderForCompletedTask() {
Task task = newTask(
with(ID, 1L),
with(DUE_TIME, newDateTime().plusDays(1)),
with(COMPLETION_TIME, newDateTime()),
with(REMINDERS, NOTIFY_AT_DEADLINE));
service.scheduleAlarm(null, task);
verify(jobs).cancel(1);
}
@Test
public void testSnoozeReminders() {
thaw(); // TODO: get rid of this
// test due date and snooze in the future
final Task task = new Task();
task.setTitle("spacemen");
task.setDueDate(DateUtilities.now() + 5000L);
task.setReminderFlags(Task.NOTIFY_AT_DEADLINE);
task.setReminderSnooze(DateUtilities.now() + DateUtilities.ONE_WEEK);
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_WEEK - 1000L);
assertTrue(time < DateUtilities.now() + DateUtilities.ONE_WEEK + 1000L);
assertEquals(type, ReminderService.TYPE_SNOOZE);
}
});
taskDao.save(task);
assertTrue(((AlarmExpected) reminderService.getScheduler()).alarmCreated);
// snooze in the past
task.setReminderSnooze(DateUtilities.now() - DateUtilities.ONE_WEEK);
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() + 5000L);
assertEquals(type, ReminderService.TYPE_DUE);
}
});
taskDao.save(task);
assertTrue(((AlarmExpected) reminderService.getScheduler()).alarmCreated);
}
public void dontScheduleReminderForDeletedTask() {
Task task = newTask(
with(ID, 1L),
with(DUE_TIME, newDateTime().plusDays(1)),
with(DELETION_TIME, newDateTime()),
with(REMINDERS, NOTIFY_AT_DEADLINE));
service.scheduleAlarm(null, task);
// --- helper classes
verify(jobs).cancel(1);
}
public class NoAlarmExpected implements AlarmScheduler {
@Override
public void createAlarm(Task task, long time, int type) {
if(time == 0 || time == Long.MAX_VALUE)
return;
fail("created alarm, no alarm expected (" + type + ": " + newDateTime(time));
}
@Test
public void dontScheduleDueDateReminderWhenAlreadyReminded() {
DateTime now = newDateTime();
Task task = newTask(
with(ID, 1L),
with(DUE_TIME, now),
with(REMINDER_LAST, now.plusSeconds(1)),
with(REMINDERS, NOTIFY_AT_DEADLINE));
@Override
public void clear() {
service.scheduleAlarm(null, task);
}
verify(jobs).cancel(1);
}
public class AlarmExpected implements AlarmScheduler {
public boolean alarmCreated = false;
@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));
}
@Override
public void createAlarm(Task task, long time, int type) {
alarmCreated = true;
}
@Test
public void ignoreLapsedSnoozeTime() {
Task task = newTask(
with(ID, 1L),
with(DUE_TIME, newDateTime()),
with(SNOOZE_TIME, newDateTime().minusMinutes(5)),
with(REMINDERS, NOTIFY_AT_DEADLINE));
service.scheduleAlarm(null, task);
InOrder order = inOrder(jobs);
order.verify(jobs).cancel(1);
order.verify(jobs).add(new Reminder(1, task.getDueDate(), ReminderService.TYPE_DUE));
@Override
public void clear() {
}
}
@Test
public void scheduleRandomReminder() {
freezeClock().thawAfter(new Snippet() {{
Task task = newTask(
with(ID, 1L),
with(RANDOM_REMINDER_PERIOD, TimeUnit.DAYS.toMillis(7)));
service.scheduleAlarm(null, task);
ArgumentCaptor<Reminder> captor = ArgumentCaptor.forClass(Reminder.class);
InOrder order = inOrder(jobs);
order.verify(jobs).cancel(1);
order.verify(jobs).add(captor.capture());
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 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);
// }
}

@ -2,9 +2,11 @@ package org.tasks.jobs;
import android.support.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.tasks.Freeze;
import org.tasks.Snippet;
import org.tasks.preferences.Preferences;
@ -14,11 +16,12 @@ import java.util.concurrent.TimeUnit;
import static com.todoroo.astrid.reminders.ReminderService.TYPE_DUE;
import static java.util.Collections.singletonList;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.AdditionalAnswers.returnsFirstArg;
import static org.mockito.Matchers.anyLong;
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.time.DateTimeUtils.currentTimeMillis;
@ -26,15 +29,23 @@ import static org.tasks.time.DateTimeUtils.currentTimeMillis;
public class JobQueueTest {
private static final long ONE_MINUTE = TimeUnit.MINUTES.toMillis(1);
private static final String TAG = "test";
private JobQueue<Reminder> queue;
private JobManager jobManager;
private Preferences preferences;
@Before
public void before() {
preferences = mock(Preferences.class);
when(preferences.adjustForQuietHours(anyLong())).then(returnsFirstArg());
queue = new JobQueue<>(preferences);
jobManager = mock(JobManager.class);
queue = new JobQueue<>(preferences, jobManager, TAG);
}
@After
public void after() {
verifyNoMoreInteractions(jobManager);
}
@Test
@ -42,33 +53,44 @@ public class JobQueueTest {
queue.add(new Reminder(1, 1, 0));
queue.add(new Reminder(2, 1, 0));
verify(jobManager).schedule(TAG, 1, true);
assertEquals(2, queue.size());
}
@Test
public void rescheduleForFirstJob() {
assertTrue(queue.add(new Reminder(1, 1, 0)));
queue.add(new Reminder(1, 1, 0));
verify(jobManager).schedule(TAG, 1, true);
}
@Test
public void dontRescheduleForLaterJobs() {
queue.add(new Reminder(1, 1, 0));
queue.add(new Reminder(2, 2, 0));
assertFalse(queue.add(new Reminder(2, 2, 0)));
verify(jobManager).schedule(TAG, 1, true);
}
@Test
public void rescheduleForNewerJob() {
queue.add(new Reminder(1, 2, 0));
queue.add(new Reminder(1, 1, 0));
assertTrue(queue.add(new Reminder(1, 1, 0)));
InOrder order = inOrder(jobManager);
order.verify(jobManager).schedule(TAG, 2, true);
order.verify(jobManager).schedule(TAG, 1, true);
}
@Test
public void rescheduleWhenCancelingOnlyJob() {
queue.add(new Reminder(1, 2, 0));
queue.cancel(1);
assertTrue(queue.cancel(1));
InOrder order = inOrder(jobManager);
order.verify(jobManager).schedule(TAG, 2, true);
order.verify(jobManager).cancel(TAG);
}
@Test
@ -76,7 +98,11 @@ public class JobQueueTest {
queue.add(new Reminder(1, 1, 0));
queue.add(new Reminder(2, 2, 0));
assertTrue(queue.cancel(1));
queue.cancel(1);
InOrder order = inOrder(jobManager);
order.verify(jobManager).schedule(TAG, 1, true);
order.verify(jobManager).schedule(TAG, 2, true);
}
@Test
@ -84,7 +110,9 @@ public class JobQueueTest {
queue.add(new Reminder(1, 1, 0));
queue.add(new Reminder(2, 2, 0));
assertFalse(queue.cancel(2));
queue.cancel(2);
verify(jobManager).schedule(TAG, 1, true);
}
@Test
@ -99,7 +127,7 @@ public class JobQueueTest {
when(preferences.adjustForQuietHours(anyLong())).thenReturn(1234L);
queue.add(new Reminder(1, 1, 1));
assertEquals(1234, queue.nextScheduledTime());
verify(jobManager).schedule(TAG, 1234, true);
}
@Test
@ -109,6 +137,8 @@ public class JobQueueTest {
queue.add(new Reminder(1, now, TYPE_DUE));
queue.add(new Reminder(2, now + ONE_MINUTE, TYPE_DUE));
verify(jobManager).schedule(TAG, now, true);
Freeze.freezeAt(now).thawAfter(new Snippet() {{
assertEquals(
singletonList(new Reminder(1, now, TYPE_DUE)),
@ -123,6 +153,8 @@ public class JobQueueTest {
queue.add(new Reminder(1, now, TYPE_DUE));
queue.add(new Reminder(2, now + ONE_MINUTE, TYPE_DUE));
verify(jobManager).schedule(TAG, now, true);
Freeze.freezeAt(now).thawAfter(new Snippet() {{
queue.removeOverdueJobs();
}});
@ -131,4 +163,26 @@ public class JobQueueTest {
singletonList(new Reminder(2, now + ONE_MINUTE, TYPE_DUE)),
queue.getJobs());
}
@Test
public void clearShouldCancelExisting() {
queue.add(new Reminder(1, 1, 0));
queue.clear();
InOrder order = inOrder(jobManager);
order.verify(jobManager).schedule(TAG, 1, true);
order.verify(jobManager).cancel(TAG);
assertEquals(0, queue.size());
}
@Test
public void ignoreInvalidCancel() {
long now = currentTimeMillis();
queue.add(new Reminder(1, now, TYPE_DUE));
queue.cancel(2);
verify(jobManager).schedule(TAG, now, true);
}
}

@ -1,5 +1,6 @@
package org.tasks.makers;
import com.google.common.base.Strings;
import com.natpryce.makeiteasy.Instantiator;
import com.natpryce.makeiteasy.Property;
import com.natpryce.makeiteasy.PropertyValue;
@ -9,15 +10,21 @@ import org.tasks.time.DateTime;
import static com.natpryce.makeiteasy.Property.newProperty;
import static org.tasks.makers.Maker.make;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
public class TaskMaker {
public static Property<Task, Long> ID = newProperty();
public static Property<Task, String> TITLE = newProperty();
public static Property<Task, DateTime> DUE_DATE = newProperty();
public static Property<Task, DateTime> DUE_TIME = newProperty();
public static Property<Task, DateTime> REMINDER_LAST = newProperty();
public static Property<Task, Long> RANDOM_REMINDER_PERIOD = newProperty();
public static Property<Task, Integer> HIDE_TYPE = newProperty();
public static Property<Task, Integer> REMINDERS = newProperty();
public static Property<Task, DateTime> COMPLETION_TIME = newProperty();
public static Property<Task, DateTime> DELETION_TIME = newProperty();
public static Property<Task, DateTime> SNOOZE_TIME = newProperty();
@SafeVarargs
public static Task newTask(PropertyValue<? super Task, ?>... properties) {
@ -27,6 +34,11 @@ public class TaskMaker {
private static final Instantiator<Task> instantiator = lookup -> {
Task task = new Task();
String title = lookup.valueOf(TITLE, (String) null);
if (!Strings.isNullOrEmpty(title)) {
task.setTitle(title);
}
long id = lookup.valueOf(ID, Task.NO_ID);
if (id != Task.NO_ID) {
task.setId(id);
@ -42,6 +54,21 @@ public class TaskMaker {
task.setDueDate(Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, dueTime.getMillis()));
}
DateTime completionTime = lookup.valueOf(COMPLETION_TIME, (DateTime) null);
if (completionTime != null) {
task.setCompletionDate(completionTime.getMillis());
}
DateTime deletedTime = lookup.valueOf(DELETION_TIME, (DateTime) null);
if (deletedTime != null) {
task.setDeletionDate(deletedTime.getMillis());
}
DateTime snoozeTime = lookup.valueOf(SNOOZE_TIME, (DateTime) null);
if (snoozeTime != null) {
task.setReminderSnooze(snoozeTime.getMillis());
}
int hideType = lookup.valueOf(HIDE_TYPE, -1);
if (hideType >= 0) {
task.setHideUntil(task.createHideUntil(hideType, 0));
@ -57,6 +84,13 @@ public class TaskMaker {
task.setReminderLast(reminderLast.getMillis());
}
long randomReminderPeriod = lookup.valueOf(RANDOM_REMINDER_PERIOD, 0L);
if (randomReminderPeriod > 0) {
task.setReminderPeriod(randomReminderPeriod);
}
task.setCreationDate(currentTimeMillis());
return task;
};
}

@ -48,13 +48,11 @@ public class AlarmService {
private final JobQueue<Alarm> jobs;
private final MetadataDao metadataDao;
private final JobManager jobManager;
@Inject
public AlarmService(MetadataDao metadataDao, JobManager jobManager, Preferences preferences) {
this.metadataDao = metadataDao;
this.jobManager = jobManager;
jobs = new JobQueue<>(preferences);
jobs = JobQueue.newAlarmQueue(preferences, jobManager);
}
public void getAlarms(long taskId, Callback<Metadata> callback) {
@ -77,7 +75,7 @@ public class AlarmService {
metadata.add(item);
}
boolean changed = synchronizeMetadata(taskId, metadata, m -> cancelAlarm(m.getId()));
boolean changed = synchronizeMetadata(taskId, metadata, m -> jobs.cancel(m.getId()));
if(changed) {
scheduleAlarms(taskId);
@ -103,7 +101,6 @@ public class AlarmService {
public void clear() {
jobs.clear();
jobManager.cancelAlarms();
}
/**
@ -131,17 +128,9 @@ public class AlarmService {
Alarm alarm = new Alarm(metadata);
long time = alarm.getTime();
if(time == 0 || time == NO_ALARM) {
cancelAlarm(alarm.getId());
jobs.cancel(alarm.getId());
} else {
if (jobs.add(alarm)) {
scheduleNext(true);
}
}
}
private void cancelAlarm(Long alarmId) {
if (jobs.cancel(alarmId)) {
scheduleNext(true);
jobs.add(alarm);
}
}
@ -197,13 +186,7 @@ public class AlarmService {
}
public void scheduleNextJob() {
scheduleNext(false);
}
private void scheduleNext(boolean cancelCurrent) {
if (!jobs.isEmpty()) {
jobManager.scheduleAlarm(jobs.nextScheduledTime(), cancelCurrent);
}
jobs.scheduleNext();
}
public List<Alarm> removePastDueAlarms() {

@ -1,9 +0,0 @@
package com.todoroo.astrid.reminders;
import com.todoroo.astrid.data.Task;
public interface AlarmScheduler {
void createAlarm(Task task, long time, int type);
void clear();
}

@ -1,74 +0,0 @@
package com.todoroo.astrid.reminders;
import com.todoroo.astrid.data.Task;
import org.tasks.injection.ApplicationScope;
import org.tasks.jobs.JobManager;
import org.tasks.jobs.JobQueue;
import org.tasks.jobs.Reminder;
import org.tasks.preferences.Preferences;
import java.util.List;
import javax.inject.Inject;
import static com.todoroo.astrid.reminders.ReminderService.NO_ALARM;
@ApplicationScope
public class ReminderAlarmScheduler implements AlarmScheduler {
private final JobQueue<Reminder> jobs;
private final JobManager jobManager;
@Inject
public ReminderAlarmScheduler(JobManager jobManager, Preferences preferences) {
this.jobManager = jobManager;
jobs = new JobQueue<>(preferences);
}
/**
* Create an alarm for the given task at the given type
*/
@Override
public void createAlarm(Task task, long time, int type) {
long taskId = task.getId();
if(taskId == Task.NO_ID) {
return;
}
if (time == 0 || time == NO_ALARM) {
if (jobs.cancel(taskId)) {
scheduleNext(true);
}
} else {
Reminder reminder = new Reminder(taskId, time, type);
if (jobs.add(reminder)) {
scheduleNext(true);
}
}
}
@Override
public void clear() {
jobs.clear();
jobManager.cancelReminders();
}
public void scheduleNextJob() {
scheduleNext(false);
}
private void scheduleNext(boolean cancelCurrent) {
if (jobs.isEmpty()) {
if (cancelCurrent) {
jobManager.cancelReminders();
}
} else {
jobManager.scheduleReminder(jobs.nextScheduledTime(), cancelCurrent);
}
}
public List<Reminder> removePastReminders() {
return jobs.removeOverdueJobs();
}
}

@ -16,13 +16,10 @@ import android.preference.PreferenceManager;
import android.provider.Settings;
import android.support.annotation.NonNull;
import com.todoroo.astrid.alarms.AlarmService;
import org.tasks.R;
import org.tasks.activities.ColorPickerActivity;
import org.tasks.activities.TimePickerActivity;
import org.tasks.dialogs.ColorPickerDialog;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.ActivityComponent;
import org.tasks.injection.InjectingPreferenceActivity;
import org.tasks.preferences.ActivityPermissionRequestor;
@ -52,10 +49,8 @@ public class ReminderPreferences extends InjectingPreferenceActivity {
@Inject Device device;
@Inject ActivityPermissionRequestor permissionRequestor;
@Inject PermissionChecker permissionChecker;
@Inject DialogBuilder dialogBuilder;
@Inject Preferences preferences;
@Inject ThemeCache themeCache;
@Inject AlarmService alarmService;
private CheckBoxPreference fieldMissedCalls;

@ -15,21 +15,19 @@ import com.todoroo.astrid.data.Task;
import org.tasks.R;
import org.tasks.injection.ApplicationScope;
import org.tasks.jobs.JobManager;
import org.tasks.jobs.JobQueue;
import org.tasks.jobs.Reminder;
import org.tasks.preferences.Preferences;
import org.tasks.time.DateTime;
import java.util.List;
import java.util.Random;
import javax.inject.Inject;
import static org.tasks.date.DateTimeUtils.newDateTime;
/**
* Data service for reminders
*
* @author Tim Su <tim@todoroo.com>
*
*/
@ApplicationScope
public final class ReminderService {
@ -60,38 +58,36 @@ public final class ReminderService {
public static final int TYPE_ALARM = 4;
private static final Random random = new Random();
private static final long NO_ALARM = Long.MAX_VALUE;
// --- instance variables
private AlarmScheduler scheduler;
private final JobQueue<Reminder> jobs;
private final Preferences preferences;
private long now = -1; // For tracking when reminders might be scheduled all at once
private final Preferences preferences;
@Inject
ReminderService(Preferences preferences, ReminderAlarmScheduler scheduler) {
ReminderService(Preferences preferences, JobManager jobManager) {
this(preferences, JobQueue.newReminderQueue(preferences, jobManager));
}
ReminderService(Preferences preferences, JobQueue<Reminder> jobs) {
this.preferences = preferences;
this.scheduler = scheduler;
this.jobs = jobs;
}
private static final int MILLIS_PER_HOUR = 60 * 60 * 1000;
// --- reminder scheduling logic
/**
* Schedules all alarms
*/
public void scheduleAllAlarms(TaskDao taskDao) {
now = DateUtilities.now(); // Before mass scheduling, initialize now variable
Query query = Query.select(NOTIFICATION_PROPERTIES).where(Criterion.and(
TaskCriteria.isActive(),
Criterion.or(Task.REMINDER_FLAGS.gt(0), Task.REMINDER_PERIOD.gt(0))));
taskDao.forEach(query, task -> scheduleAlarm(task, null));
taskDao.forEach(query, task -> scheduleAlarm(null, task));
now = -1; // Signal done with now variable
}
public void clear() {
scheduler.clear();
jobs.clear();
}
private long getNowValue() {
@ -99,29 +95,25 @@ public final class ReminderService {
return (now == -1 ? DateUtilities.now() : now);
}
public static final long NO_ALARM = Long.MAX_VALUE;
/**
* Schedules alarms for a single task
*/
public void scheduleAlarm(TaskDao taskDao, Task task) {
scheduleAlarm(task, taskDao);
public List<Reminder> getPastReminders() {
return jobs.removeOverdueJobs();
}
private void clearAllAlarms(Task task) {
scheduler.createAlarm(task, NO_ALARM, 0);
public void scheduleNextJob() {
jobs.scheduleNext();
}
private void scheduleAlarm(Task task, TaskDao taskDao) {
public void scheduleAlarm(TaskDao taskDao, Task task) {
if(task == null || !task.isSaved()) {
return;
}
// read data if necessary
long taskId = task.getId();
if(taskDao != null) {
for(Property<?> property : NOTIFICATION_PROPERTIES) {
if(!task.containsValue(property)) {
task = taskDao.fetch(task.getId(), NOTIFICATION_PROPERTIES);
task = taskDao.fetch(taskId, NOTIFICATION_PROPERTIES);
if(task == null) {
return;
}
@ -132,7 +124,8 @@ public final class ReminderService {
// 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.
clearAllAlarms(task);
jobs.cancel(taskId);
if(task.isCompleted() || task.isDeleted()) {
return;
}
@ -164,15 +157,13 @@ public final class ReminderService {
// snooze trumps all
if(whenSnooze != NO_ALARM) {
scheduler.createAlarm(task, whenSnooze, TYPE_SNOOZE);
jobs.add(new Reminder(taskId, whenSnooze, TYPE_SNOOZE));
} else if(whenRandom < whenDueDate && whenRandom < whenOverdue) {
scheduler.createAlarm(task, whenRandom, TYPE_RANDOM);
jobs.add(new Reminder(taskId, whenRandom, TYPE_RANDOM));
} else if(whenDueDate < whenOverdue) {
scheduler.createAlarm(task, whenDueDate, TYPE_DUE);
jobs.add(new Reminder(taskId, whenDueDate, TYPE_DUE));
} else if(whenOverdue != NO_ALARM) {
scheduler.createAlarm(task, whenOverdue, TYPE_OVERDUE);
} else {
scheduler.createAlarm(task, 0, 0);
jobs.add(new Reminder(taskId, whenOverdue, TYPE_OVERDUE));
}
}
@ -238,7 +229,7 @@ public final class ReminderService {
* If the date was indicated to not have a due time, we read from
* preferences and assign a time.
*/
long calculateNextDueDateReminder(Task task) {
private long calculateNextDueDateReminder(Task task) {
if(task.hasDueDate() && task.isNotifyAtDeadline()) {
long dueDate = task.getDueDate();
long lastReminder = task.getReminderLast();
@ -285,14 +276,4 @@ public final class ReminderService {
}
return NO_ALARM;
}
// --- alarm manager alarm creation
public void setScheduler(AlarmScheduler scheduler) {
this.scheduler = scheduler;
}
public AlarmScheduler getScheduler() {
return scheduler;
}
}

@ -6,7 +6,7 @@ import com.evernote.android.job.Job;
import com.todoroo.astrid.alarms.AlarmService;
import com.todoroo.astrid.backup.TasksXmlExporter;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.reminders.ReminderAlarmScheduler;
import com.todoroo.astrid.reminders.ReminderService;
import org.tasks.Broadcaster;
import org.tasks.Notifier;
@ -29,13 +29,13 @@ public class JobCreator implements com.evernote.android.job.JobCreator {
private final RefreshScheduler refreshScheduler;
private final AlarmService alarmService;
private final TaskDao taskDao;
private final ReminderAlarmScheduler reminderAlarmScheduler;
private final ReminderService reminderService;
@Inject
public JobCreator(@ForApplication Context context, Notifier notifier, JobManager jobManager,
Broadcaster broadcaster, TasksXmlExporter tasksXmlExporter,
Preferences preferences, RefreshScheduler refreshScheduler,
AlarmService alarmService, TaskDao taskDao, ReminderAlarmScheduler reminderAlarmScheduler) {
AlarmService alarmService, TaskDao taskDao, ReminderService reminderService) {
this.context = context;
this.notifier = notifier;
this.jobManager = jobManager;
@ -45,14 +45,14 @@ public class JobCreator implements com.evernote.android.job.JobCreator {
this.refreshScheduler = refreshScheduler;
this.alarmService = alarmService;
this.taskDao = taskDao;
this.reminderAlarmScheduler = reminderAlarmScheduler;
this.reminderService = reminderService;
}
@Override
public Job create(String tag) {
switch (tag) {
case ReminderJob.TAG:
return new ReminderJob(preferences, reminderAlarmScheduler, notifier);
return new ReminderJob(preferences, reminderService, notifier);
case AlarmJob.TAG:
return new AlarmJob(preferences, alarmService, notifier, taskDao);
case RefreshJob.TAG:

@ -28,16 +28,8 @@ public class JobManager {
jobManager.addJobCreator(jobCreator);
}
public void scheduleAlarm(long time, boolean cancelCurrent) {
new JobRequest.Builder(AlarmJob.TAG)
.setExact(Math.max(time - currentTimeMillis(), 5000))
.setUpdateCurrent(cancelCurrent)
.build()
.schedule();
}
public void scheduleReminder(long time, boolean cancelCurrent) {
new JobRequest.Builder(ReminderJob.TAG)
public void schedule(String tag, long time, boolean cancelCurrent) {
new JobRequest.Builder(tag)
.setExact(Math.max(time - currentTimeMillis(), 5000))
.setUpdateCurrent(cancelCurrent)
.build()
@ -69,15 +61,11 @@ public class JobManager {
.schedule();
}
public void cancelAlarms() {
jobManager.cancelAllForTag(AlarmJob.TAG);
public void cancel(String tag) {
jobManager.cancelAllForTag(tag);
}
public void cancelRefreshes() {
jobManager.cancelAllForTag(RefreshJob.TAG);
}
public void cancelReminders() {
jobManager.cancelAllForTag(ReminderJob.TAG);
}
}

@ -17,26 +17,37 @@ import static org.tasks.time.DateTimeUtils.currentTimeMillis;
public class JobQueue<T extends JobQueueEntry> {
private final TreeMultimap<Long, T> jobs = TreeMultimap.create(Ordering.natural(), (l, r) -> Longs.compare(l.getId(), r.getId()));
private final Preferences preferences;
private final JobManager jobManager;
private final String tag;
public JobQueue(Preferences preferences) {
this.preferences = preferences;
public static JobQueue<Reminder> newReminderQueue(Preferences preferences, JobManager jobManager) {
return new JobQueue<>(preferences, jobManager, ReminderJob.TAG);
}
public boolean add(T entry) {
boolean result = jobs.isEmpty() || entry.getTime() < firstTime();
jobs.put(entry.getTime(), entry);
return result;
public static JobQueue<Alarm> newAlarmQueue(Preferences preferences, JobManager jobManager) {
return new JobQueue<>(preferences, jobManager, AlarmJob.TAG);
}
JobQueue(Preferences preferences, JobManager jobManager, String tag) {
this.preferences = preferences;
this.jobManager = jobManager;
this.tag = tag;
}
public boolean isEmpty() {
return jobs.isEmpty();
public void add(T entry) {
boolean reschedule = jobs.isEmpty() || entry.getTime() < firstTime();
jobs.put(entry.getTime(), entry);
if (reschedule) {
scheduleNext(true);
}
}
public void clear() {
jobs.clear();
jobManager.cancel(tag);
}
public boolean cancel(long id) {
public void cancel(long id) {
boolean reschedule = false;
long firstTime = firstTime();
List<T> existing = newArrayList(filter(jobs.values(), r -> r.getId() == id));
@ -44,11 +55,9 @@ public class JobQueue<T extends JobQueueEntry> {
reschedule |= entry.getTime() == firstTime;
jobs.remove(entry.getTime(), entry);
}
return reschedule;
}
List<T> getJobs() {
return ImmutableList.copyOf(jobs.values());
if (reschedule) {
scheduleNext(true);
}
}
public List<T> removeOverdueJobs() {
@ -60,16 +69,34 @@ public class JobQueue<T extends JobQueueEntry> {
return result;
}
public int size() {
return jobs.size();
public void scheduleNext() {
scheduleNext(false);
}
private void scheduleNext(boolean cancelCurrent) {
if (jobs.isEmpty()) {
if (cancelCurrent) {
jobManager.cancel(tag);
}
} else {
jobManager.schedule(tag, nextScheduledTime(), cancelCurrent);
}
}
private long firstTime() {
return jobs.isEmpty() ? 0 : jobs.asMap().firstKey();
}
public long nextScheduledTime() {
long nextScheduledTime() {
long next = firstTime();
return next > 0 ? preferences.adjustForQuietHours(next) : 0;
}
int size() {
return jobs.size();
}
List<T> getJobs() {
return ImmutableList.copyOf(jobs.values());
}
}

@ -3,7 +3,7 @@ package org.tasks.jobs;
import android.support.annotation.NonNull;
import com.evernote.android.job.Job;
import com.todoroo.astrid.reminders.ReminderAlarmScheduler;
import com.todoroo.astrid.reminders.ReminderService;
import org.tasks.Notifier;
import org.tasks.preferences.Preferences;
@ -13,12 +13,12 @@ public class ReminderJob extends Job {
public static final String TAG = "job_reminder";
private final Preferences preferences;
private final ReminderAlarmScheduler reminderAlarmScheduler;
private final ReminderService reminderService;
private final Notifier notifier;
public ReminderJob(Preferences preferences, ReminderAlarmScheduler reminderAlarmScheduler, Notifier notifier) {
public ReminderJob(Preferences preferences, ReminderService reminderService, Notifier notifier) {
this.preferences = preferences;
this.reminderAlarmScheduler = reminderAlarmScheduler;
this.reminderService = reminderService;
this.notifier = notifier;
}
@ -27,13 +27,13 @@ public class ReminderJob extends Job {
protected Result onRunJob(Params params) {
try {
if (!preferences.isCurrentlyQuietHours()) {
for (Reminder reminder : reminderAlarmScheduler.removePastReminders()) {
for (Reminder reminder : reminderService.getPastReminders()) {
notifier.triggerTaskNotification(reminder.getId(), reminder.getType());
}
}
return Result.SUCCESS;
} finally {
reminderAlarmScheduler.scheduleNextJob();
reminderService.scheduleNextJob();
}
}
}

Loading…
Cancel
Save