mirror of https://github.com/tasks/tasks
Use android-job for most scheduling
parent
b485b139c5
commit
c02164ad7a
@ -0,0 +1,139 @@
|
||||
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);
|
||||
}});
|
||||
}
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
package org.tasks.jobs;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.tasks.Freeze;
|
||||
import org.tasks.Snippet;
|
||||
import org.tasks.preferences.Preferences;
|
||||
|
||||
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.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class JobQueueTest {
|
||||
|
||||
private static final long ONE_MINUTE = TimeUnit.MINUTES.toMillis(1);
|
||||
|
||||
private JobQueue<Reminder> queue;
|
||||
private Preferences preferences;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
preferences = mock(Preferences.class);
|
||||
when(preferences.adjustForQuietHours(anyLong())).then(returnsFirstArg());
|
||||
queue = new JobQueue<>(preferences);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void twoJobsAtSameTime() {
|
||||
queue.add(new Reminder(1, 1, 0));
|
||||
queue.add(new Reminder(2, 1, 0));
|
||||
|
||||
assertEquals(2, queue.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rescheduleForFirstJob() {
|
||||
assertTrue(queue.add(new Reminder(1, 1, 0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dontRescheduleForLaterJobs() {
|
||||
queue.add(new Reminder(1, 1, 0));
|
||||
|
||||
assertFalse(queue.add(new Reminder(2, 2, 0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rescheduleForNewerJob() {
|
||||
queue.add(new Reminder(1, 2, 0));
|
||||
|
||||
assertTrue(queue.add(new Reminder(1, 1, 0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rescheduleWhenCancelingOnlyJob() {
|
||||
queue.add(new Reminder(1, 2, 0));
|
||||
|
||||
assertTrue(queue.cancel(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rescheduleWhenCancelingFirstJob() {
|
||||
queue.add(new Reminder(1, 1, 0));
|
||||
queue.add(new Reminder(2, 2, 0));
|
||||
|
||||
assertTrue(queue.cancel(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dontRescheduleWhenCancelingLaterJob() {
|
||||
queue.add(new Reminder(1, 1, 0));
|
||||
queue.add(new Reminder(2, 2, 0));
|
||||
|
||||
assertFalse(queue.cancel(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nextScheduledTimeIsZeroWhenQueueIsEmpty() {
|
||||
when(preferences.adjustForQuietHours(anyLong())).thenReturn(1234L);
|
||||
|
||||
assertEquals(0, queue.nextScheduledTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adjustNextScheduledTimeForQuietHours() {
|
||||
when(preferences.adjustForQuietHours(anyLong())).thenReturn(1234L);
|
||||
queue.add(new Reminder(1, 1, 1));
|
||||
|
||||
assertEquals(1234, queue.nextScheduledTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void overdueJobsAreReturned() {
|
||||
long now = currentTimeMillis();
|
||||
|
||||
queue.add(new Reminder(1, now, TYPE_DUE));
|
||||
queue.add(new Reminder(2, now + ONE_MINUTE, TYPE_DUE));
|
||||
|
||||
Freeze.freezeAt(now).thawAfter(new Snippet() {{
|
||||
assertEquals(
|
||||
singletonList(new Reminder(1, now, TYPE_DUE)),
|
||||
queue.removeOverdueJobs());
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void overdueJobsAreRemoved() {
|
||||
long now = currentTimeMillis();
|
||||
|
||||
queue.add(new Reminder(1, now, TYPE_DUE));
|
||||
queue.add(new Reminder(2, now + ONE_MINUTE, TYPE_DUE));
|
||||
|
||||
Freeze.freezeAt(now).thawAfter(new Snippet() {{
|
||||
queue.removeOverdueJobs();
|
||||
}});
|
||||
|
||||
assertEquals(
|
||||
singletonList(new Reminder(2, now + ONE_MINUTE, TYPE_DUE)),
|
||||
queue.getJobs());
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.todoroo.astrid.reminders;
|
||||
|
||||
import com.todoroo.astrid.data.Task;
|
||||
|
||||
public interface AlarmScheduler {
|
||||
void createAlarm(Task task, long time, int type);
|
||||
|
||||
void clear();
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
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();
|
||||
}
|
||||
}
|
@ -1,25 +1,22 @@
|
||||
package org.tasks.injection;
|
||||
|
||||
import org.tasks.location.GeofenceTransitionsIntentService;
|
||||
import org.tasks.scheduling.BackupIntentService;
|
||||
import org.tasks.scheduling.CalendarNotificationIntentService;
|
||||
import org.tasks.scheduling.GeofenceSchedulingIntentService;
|
||||
import org.tasks.scheduling.RefreshSchedulerIntentService;
|
||||
import org.tasks.scheduling.ReminderSchedulerIntentService;
|
||||
import org.tasks.scheduling.NotificationSchedulerIntentService;
|
||||
import org.tasks.scheduling.SchedulerIntentService;
|
||||
|
||||
import dagger.Subcomponent;
|
||||
|
||||
@Subcomponent(modules = IntentServiceModule.class)
|
||||
public interface IntentServiceComponent {
|
||||
void inject(ReminderSchedulerIntentService reminderSchedulerIntentService);
|
||||
|
||||
void inject(RefreshSchedulerIntentService refreshSchedulerIntentService);
|
||||
void inject(SchedulerIntentService schedulerIntentService);
|
||||
|
||||
void inject(GeofenceSchedulingIntentService geofenceSchedulingIntentService);
|
||||
|
||||
void inject(CalendarNotificationIntentService calendarNotificationIntentService);
|
||||
|
||||
void inject(BackupIntentService backupIntentService);
|
||||
|
||||
void inject(GeofenceTransitionsIntentService geofenceTransitionsIntentService);
|
||||
|
||||
void inject(NotificationSchedulerIntentService notificationSchedulerIntentService);
|
||||
}
|
||||
|
@ -0,0 +1,58 @@
|
||||
package org.tasks.jobs;
|
||||
|
||||
import com.todoroo.astrid.alarms.AlarmFields;
|
||||
import com.todoroo.astrid.data.Metadata;
|
||||
|
||||
public class Alarm implements JobQueueEntry {
|
||||
private final long alarmId;
|
||||
private final long taskId;
|
||||
private final long time;
|
||||
|
||||
public Alarm(Metadata metadata) {
|
||||
this(metadata.getId(), metadata.getTask(), metadata.getValue(AlarmFields.TIME));
|
||||
}
|
||||
|
||||
public Alarm(long alarmId, long taskId, Long time) {
|
||||
this.alarmId = alarmId;
|
||||
this.taskId = taskId;
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getId() {
|
||||
return alarmId;
|
||||
}
|
||||
|
||||
public long getTaskId() {
|
||||
return taskId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Alarm alarm = (Alarm) o;
|
||||
|
||||
return alarmId == alarm.alarmId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (int) (alarmId ^ (alarmId >>> 32));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Alarm{" +
|
||||
"alarmId=" + alarmId +
|
||||
", taskId=" + taskId +
|
||||
", time=" + time +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package org.tasks.jobs;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.evernote.android.job.Job;
|
||||
import com.todoroo.astrid.alarms.AlarmService;
|
||||
import com.todoroo.astrid.dao.TaskDao;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.reminders.ReminderService;
|
||||
|
||||
import org.tasks.Notifier;
|
||||
import org.tasks.preferences.Preferences;
|
||||
|
||||
public class AlarmJob extends Job {
|
||||
|
||||
public static final String TAG = "job_alarm";
|
||||
|
||||
private final Preferences preferences;
|
||||
private final AlarmService alarmService;
|
||||
private final Notifier notifier;
|
||||
private final TaskDao taskDao;
|
||||
|
||||
public AlarmJob(Preferences preferences, AlarmService alarmService, Notifier notifier, TaskDao taskDao) {
|
||||
this.preferences = preferences;
|
||||
this.alarmService = alarmService;
|
||||
this.notifier = notifier;
|
||||
this.taskDao = taskDao;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Result onRunJob(Params params) {
|
||||
try {
|
||||
if (!preferences.isCurrentlyQuietHours()) {
|
||||
for (Alarm alarm : alarmService.removePastDueAlarms()) {
|
||||
Task task = taskDao.fetch(alarm.getTaskId(), Task.REMINDER_LAST);
|
||||
if (task != null && task.getReminderLast() < alarm.getTime()) {
|
||||
notifier.triggerTaskNotification(alarm.getTaskId(), ReminderService.TYPE_ALARM);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Result.SUCCESS;
|
||||
} finally {
|
||||
alarmService.scheduleNextJob();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package org.tasks.jobs;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
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 org.tasks.Broadcaster;
|
||||
import org.tasks.Notifier;
|
||||
import org.tasks.injection.ApplicationScope;
|
||||
import org.tasks.injection.ForApplication;
|
||||
import org.tasks.preferences.Preferences;
|
||||
import org.tasks.scheduling.RefreshScheduler;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@ApplicationScope
|
||||
public class JobCreator implements com.evernote.android.job.JobCreator {
|
||||
|
||||
private final Context context;
|
||||
private final Notifier notifier;
|
||||
private final JobManager jobManager;
|
||||
private final Broadcaster broadcaster;
|
||||
private final TasksXmlExporter tasksXmlExporter;
|
||||
private final Preferences preferences;
|
||||
private final RefreshScheduler refreshScheduler;
|
||||
private final AlarmService alarmService;
|
||||
private final TaskDao taskDao;
|
||||
private final ReminderAlarmScheduler reminderAlarmScheduler;
|
||||
|
||||
@Inject
|
||||
public JobCreator(@ForApplication Context context, Notifier notifier, JobManager jobManager,
|
||||
Broadcaster broadcaster, TasksXmlExporter tasksXmlExporter,
|
||||
Preferences preferences, RefreshScheduler refreshScheduler,
|
||||
AlarmService alarmService, TaskDao taskDao, ReminderAlarmScheduler reminderAlarmScheduler) {
|
||||
this.context = context;
|
||||
this.notifier = notifier;
|
||||
this.jobManager = jobManager;
|
||||
this.broadcaster = broadcaster;
|
||||
this.tasksXmlExporter = tasksXmlExporter;
|
||||
this.preferences = preferences;
|
||||
this.refreshScheduler = refreshScheduler;
|
||||
this.alarmService = alarmService;
|
||||
this.taskDao = taskDao;
|
||||
this.reminderAlarmScheduler = reminderAlarmScheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Job create(String tag) {
|
||||
switch (tag) {
|
||||
case ReminderJob.TAG:
|
||||
return new ReminderJob(preferences, reminderAlarmScheduler, notifier);
|
||||
case AlarmJob.TAG:
|
||||
return new AlarmJob(preferences, alarmService, notifier, taskDao);
|
||||
case RefreshJob.TAG:
|
||||
return new RefreshJob(refreshScheduler, broadcaster);
|
||||
case MidnightRefreshJob.TAG:
|
||||
return new MidnightRefreshJob(broadcaster, jobManager);
|
||||
case BackupJob.TAG:
|
||||
return new BackupJob(context, jobManager, tasksXmlExporter, preferences);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package org.tasks.jobs;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.evernote.android.job.JobCreator;
|
||||
import com.evernote.android.job.JobRequest;
|
||||
|
||||
import org.tasks.injection.ApplicationScope;
|
||||
import org.tasks.injection.ForApplication;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
|
||||
import static org.tasks.time.DateTimeUtils.nextMidnight;
|
||||
|
||||
@ApplicationScope
|
||||
public class JobManager {
|
||||
|
||||
private final com.evernote.android.job.JobManager jobManager;
|
||||
|
||||
@Inject
|
||||
public JobManager(@ForApplication Context context) {
|
||||
jobManager = com.evernote.android.job.JobManager.create(context);
|
||||
jobManager.cancelAll();
|
||||
}
|
||||
|
||||
public void addJobCreator(JobCreator jobCreator) {
|
||||
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)
|
||||
.setExact(Math.max(time - currentTimeMillis(), 5000))
|
||||
.setUpdateCurrent(cancelCurrent)
|
||||
.build()
|
||||
.schedule();
|
||||
}
|
||||
|
||||
public void scheduleRefresh(long time, boolean cancelExisting) {
|
||||
new JobRequest.Builder(RefreshJob.TAG)
|
||||
.setExact(Math.max(time - currentTimeMillis(), 5000))
|
||||
.setUpdateCurrent(cancelExisting)
|
||||
.build()
|
||||
.schedule();
|
||||
}
|
||||
|
||||
public void scheduleMidnightRefresh(boolean cancelExisting) {
|
||||
scheduleMidnightJob(MidnightRefreshJob.TAG, cancelExisting);
|
||||
}
|
||||
|
||||
public void scheduleMidnightBackup(boolean cancelExisting) {
|
||||
scheduleMidnightJob(BackupJob.TAG, cancelExisting);
|
||||
}
|
||||
|
||||
private void scheduleMidnightJob(String tag, boolean cancelExisting) {
|
||||
long now = System.currentTimeMillis();
|
||||
new JobRequest.Builder(tag)
|
||||
.setExact(nextMidnight(now) - now)
|
||||
.setUpdateCurrent(cancelExisting)
|
||||
.build()
|
||||
.schedule();
|
||||
}
|
||||
|
||||
public void cancelAlarms() {
|
||||
jobManager.cancelAllForTag(AlarmJob.TAG);
|
||||
}
|
||||
|
||||
public void cancelRefreshes() {
|
||||
jobManager.cancelAllForTag(RefreshJob.TAG);
|
||||
}
|
||||
|
||||
public void cancelReminders() {
|
||||
jobManager.cancelAllForTag(ReminderJob.TAG);
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package org.tasks.jobs;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Ordering;
|
||||
import com.google.common.collect.TreeMultimap;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
import org.tasks.preferences.Preferences;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
|
||||
import static com.google.common.collect.Iterables.filter;
|
||||
import static com.google.common.collect.Lists.newArrayList;
|
||||
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;
|
||||
|
||||
public JobQueue(Preferences preferences) {
|
||||
this.preferences = preferences;
|
||||
}
|
||||
|
||||
public boolean add(T entry) {
|
||||
boolean result = jobs.isEmpty() || entry.getTime() < firstTime();
|
||||
jobs.put(entry.getTime(), entry);
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return jobs.isEmpty();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
jobs.clear();
|
||||
}
|
||||
|
||||
public boolean cancel(long id) {
|
||||
boolean reschedule = false;
|
||||
long firstTime = firstTime();
|
||||
List<T> existing = newArrayList(filter(jobs.values(), r -> r.getId() == id));
|
||||
for (T entry : existing) {
|
||||
reschedule |= entry.getTime() == firstTime;
|
||||
jobs.remove(entry.getTime(), entry);
|
||||
}
|
||||
return reschedule;
|
||||
}
|
||||
|
||||
List<T> getJobs() {
|
||||
return ImmutableList.copyOf(jobs.values());
|
||||
}
|
||||
|
||||
public List<T> removeOverdueJobs() {
|
||||
List<T> result = newArrayList();
|
||||
SortedSet<Long> lapsed = jobs.keySet().headSet(currentTimeMillis() + 1);
|
||||
for (Long key : lapsed) {
|
||||
result.addAll(jobs.removeAll(key));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return jobs.size();
|
||||
}
|
||||
|
||||
private long firstTime() {
|
||||
return jobs.isEmpty() ? 0 : jobs.asMap().firstKey();
|
||||
}
|
||||
|
||||
public long nextScheduledTime() {
|
||||
long next = firstTime();
|
||||
return next > 0 ? preferences.adjustForQuietHours(next) : 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package org.tasks.jobs;
|
||||
|
||||
public interface JobQueueEntry {
|
||||
long getId();
|
||||
|
||||
long getTime();
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package org.tasks.jobs;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.evernote.android.job.Job;
|
||||
|
||||
import org.tasks.Broadcaster;
|
||||
|
||||
public class MidnightRefreshJob extends Job {
|
||||
|
||||
public static final String TAG = "job_midnight_refresh";
|
||||
|
||||
private final Broadcaster broadcaster;
|
||||
private final JobManager jobManager;
|
||||
|
||||
public MidnightRefreshJob(Broadcaster broadcaster, JobManager jobManager) {
|
||||
this.broadcaster = broadcaster;
|
||||
this.jobManager = jobManager;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Result onRunJob(Params params) {
|
||||
try {
|
||||
broadcaster.refresh();
|
||||
return Result.SUCCESS;
|
||||
} finally {
|
||||
jobManager.scheduleMidnightRefresh(false);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package org.tasks.jobs;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.evernote.android.job.Job;
|
||||
|
||||
import org.tasks.Broadcaster;
|
||||
import org.tasks.scheduling.RefreshScheduler;
|
||||
|
||||
public class RefreshJob extends Job {
|
||||
|
||||
public static final String TAG = "job_refresh";
|
||||
|
||||
private final RefreshScheduler refreshScheduler;
|
||||
private final Broadcaster broadcaster;
|
||||
|
||||
public RefreshJob(RefreshScheduler refreshScheduler, Broadcaster broadcaster) {
|
||||
this.refreshScheduler = refreshScheduler;
|
||||
this.broadcaster = broadcaster;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Result onRunJob(Params params) {
|
||||
try {
|
||||
broadcaster.refresh();
|
||||
return Result.SUCCESS;
|
||||
} finally {
|
||||
refreshScheduler.scheduleNext();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package org.tasks.jobs;
|
||||
|
||||
public class Reminder implements JobQueueEntry {
|
||||
private final long taskId;
|
||||
private final long time;
|
||||
private final int type;
|
||||
|
||||
public Reminder(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;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Reminder reminder = (Reminder) o;
|
||||
|
||||
if (taskId != reminder.taskId) return false;
|
||||
if (time != reminder.time) return false;
|
||||
return type == reminder.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 "Reminder{" +
|
||||
"taskId=" + taskId +
|
||||
", time=" + time +
|
||||
", type=" + type +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package org.tasks.jobs;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.evernote.android.job.Job;
|
||||
import com.todoroo.astrid.reminders.ReminderAlarmScheduler;
|
||||
|
||||
import org.tasks.Notifier;
|
||||
import org.tasks.preferences.Preferences;
|
||||
|
||||
public class ReminderJob extends Job {
|
||||
|
||||
public static final String TAG = "job_reminder";
|
||||
|
||||
private final Preferences preferences;
|
||||
private final ReminderAlarmScheduler reminderAlarmScheduler;
|
||||
private final Notifier notifier;
|
||||
|
||||
public ReminderJob(Preferences preferences, ReminderAlarmScheduler reminderAlarmScheduler, Notifier notifier) {
|
||||
this.preferences = preferences;
|
||||
this.reminderAlarmScheduler = reminderAlarmScheduler;
|
||||
this.notifier = notifier;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Result onRunJob(Params params) {
|
||||
try {
|
||||
if (!preferences.isCurrentlyQuietHours()) {
|
||||
for (Reminder reminder : reminderAlarmScheduler.removePastReminders()) {
|
||||
notifier.triggerTaskNotification(reminder.getId(), reminder.getType());
|
||||
}
|
||||
}
|
||||
return Result.SUCCESS;
|
||||
} finally {
|
||||
reminderAlarmScheduler.scheduleNextJob();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package org.tasks.receivers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import org.tasks.Broadcaster;
|
||||
import org.tasks.injection.BroadcastComponent;
|
||||
import org.tasks.injection.InjectingBroadcastReceiver;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class RefreshReceiver extends InjectingBroadcastReceiver {
|
||||
|
||||
@Inject Broadcaster broadcaster;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
super.onReceive(context, intent);
|
||||
|
||||
Timber.d("onReceive(context, %s)", intent);
|
||||
|
||||
broadcaster.refresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void inject(BroadcastComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package org.tasks.receivers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import org.tasks.Notifier;
|
||||
import org.tasks.injection.BroadcastComponent;
|
||||
import org.tasks.injection.InjectingBroadcastReceiver;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class TaskNotificationReceiver extends InjectingBroadcastReceiver {
|
||||
|
||||
private static final ExecutorService executorService = Executors.newSingleThreadExecutor();
|
||||
|
||||
public static final String ID_KEY = "id"; //$NON-NLS-1$
|
||||
public static final String EXTRAS_TYPE = "type"; //$NON-NLS-1$
|
||||
|
||||
@Inject Notifier notifier;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, final Intent intent) {
|
||||
super.onReceive(context, intent);
|
||||
|
||||
executorService.execute(() -> notifier.triggerTaskNotification(
|
||||
intent.getLongExtra(ID_KEY, 0),
|
||||
intent.getIntExtra(EXTRAS_TYPE, (byte) 0)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void inject(BroadcastComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
package org.tasks.scheduling;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import org.tasks.injection.InjectingIntentService;
|
||||
import org.tasks.preferences.Preferences;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
|
||||
import static org.tasks.time.DateTimeUtils.nextMidnight;
|
||||
import static org.tasks.time.DateTimeUtils.printTimestamp;
|
||||
|
||||
public abstract class MidnightIntentService extends InjectingIntentService {
|
||||
|
||||
private static final long PADDING = SECONDS.toMillis(1);
|
||||
|
||||
private final String name;
|
||||
|
||||
MidnightIntentService(String name) {
|
||||
super(name);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
super.onHandleIntent(intent);
|
||||
|
||||
Context context = getApplicationContext();
|
||||
Preferences preferences = new Preferences(context);
|
||||
AlarmManager alarmManager = new AlarmManager(context, preferences);
|
||||
|
||||
String lastRunPreference = getLastRunPreference();
|
||||
long lastRun = lastRunPreference == null ? 0 : preferences.getLong(lastRunPreference, 0);
|
||||
long nextRun = nextMidnight(lastRun);
|
||||
long now = currentTimeMillis();
|
||||
|
||||
if (nextRun <= now) {
|
||||
nextRun = nextMidnight(now);
|
||||
Timber.d("%s running now [nextRun=%s]", name, printTimestamp(nextRun));
|
||||
if (!isNullOrEmpty(getLastRunPreference())) {
|
||||
preferences.setLong(getLastRunPreference(), now);
|
||||
}
|
||||
run();
|
||||
} else {
|
||||
Timber.d("%s will run at %s [lastRun=%s]", name, printTimestamp(nextRun), printTimestamp(lastRun));
|
||||
}
|
||||
|
||||
PendingIntent pendingIntent = PendingIntent.getService(this, 0, new Intent(this, this.getClass()), PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
alarmManager.noWakeup(nextRun + PADDING, pendingIntent);
|
||||
}
|
||||
|
||||
abstract void run();
|
||||
|
||||
String getLastRunPreference() {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
package org.tasks.scheduling;
|
||||
|
||||
import com.todoroo.andlib.sql.Criterion;
|
||||
import com.todoroo.astrid.dao.TaskDao;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
|
||||
import org.tasks.Broadcaster;
|
||||
import org.tasks.injection.IntentServiceComponent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
|
||||
import static org.tasks.time.DateTimeUtils.nextMidnight;
|
||||
|
||||
public class RefreshSchedulerIntentService extends MidnightIntentService {
|
||||
|
||||
@Inject Broadcaster broadcaster;
|
||||
@Inject RefreshScheduler refreshScheduler;
|
||||
@Inject TaskDao taskDao;
|
||||
|
||||
public RefreshSchedulerIntentService() {
|
||||
super(RefreshSchedulerIntentService.class.getSimpleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
void run() {
|
||||
scheduleApplicationRefreshes();
|
||||
broadcaster.refresh();
|
||||
}
|
||||
|
||||
public void scheduleApplicationRefreshes() {
|
||||
long now = currentTimeMillis();
|
||||
long midnight = nextMidnight(now);
|
||||
Criterion criterion = Criterion.or(
|
||||
Criterion.and(Task.HIDE_UNTIL.gt(now), Task.HIDE_UNTIL.lt(midnight)),
|
||||
Criterion.and(Task.DUE_DATE.gt(now), Task.DUE_DATE.lt(midnight)));
|
||||
taskDao.selectActive(criterion, refreshScheduler::scheduleRefresh);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void inject(IntentServiceComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package org.tasks.scheduling;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
import com.todoroo.andlib.sql.Criterion;
|
||||
import com.todoroo.astrid.dao.TaskDao;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
|
||||
import org.tasks.injection.InjectingIntentService;
|
||||
import org.tasks.injection.IntentServiceComponent;
|
||||
import org.tasks.jobs.JobManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
import static java.lang.System.currentTimeMillis;
|
||||
|
||||
public class SchedulerIntentService extends InjectingIntentService {
|
||||
|
||||
@Inject TaskDao taskDao;
|
||||
@Inject JobManager jobManager;
|
||||
@Inject RefreshScheduler refreshScheduler;
|
||||
|
||||
public SchedulerIntentService() {
|
||||
super(SchedulerIntentService.class.getSimpleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
super.onHandleIntent(intent);
|
||||
|
||||
Timber.d("onHandleIntent(%s)", intent);
|
||||
|
||||
jobManager.scheduleMidnightBackup(true);
|
||||
jobManager.scheduleMidnightRefresh(true);
|
||||
|
||||
refreshScheduler.clear();
|
||||
long now = currentTimeMillis();
|
||||
taskDao.selectActive(
|
||||
Criterion.or(Task.HIDE_UNTIL.gt(now), Task.DUE_DATE.gt(now)),
|
||||
refreshScheduler::scheduleRefresh);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void inject(IntentServiceComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<full-backup-content>
|
||||
<exclude domain="sharedpref" path="evernote_jobs.xml" />
|
||||
<exclude domain="database" path="evernote_jobs.db" />
|
||||
</full-backup-content>
|
Loading…
Reference in New Issue